插件开发简介
- MyBatis在四大对象的创建过程中,都会有插件进行介入。插件可以利用动态代理机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截的效果。
- MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用。
- 默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
-
Executor
(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) -
ParameterHandler
(getParameterObject, setParameters) -
ResultSetHandler
(handleResultSets, handleOutputParameters) -
StatementHandler
(prepare, parameterize, batch, update, query)
插件开发步骤
– 1)、编写插件实现Interceptor接口,并使用@Intercepts注解完成插件签名
–2)、在全局配置文件中注册插件
小案例
MyFirstPlugin.java
import java.util.Properties;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
/**
* 完成插件签名:
* 告诉MyBatis当前插件用来拦截哪个对象的哪个方法
*/
@Intercepts(
{
@Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
})
public class MyFirstPlugin implements Interceptor{
/**
* intercept:拦截:
* 拦截目标对象的目标方法的执行;
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
// TODO Auto-generated method stub
System.out.println("MyFirstPlugin...intercept:"+invocation.getMethod());
//动态的改变一下sql运行的参数:以前1号员工,实际从数据库查询3号员工
Object target = invocation.getTarget();
System.out.println("当前拦截到的对象:"+target);
//拿到:StatementHandler==>ParameterHandler===>parameterObject
//拿到target的元数据
MetaObject metaObject = SystemMetaObject.forObject(target);
Object value = metaObject.getValue("parameterHandler.parameterObject");
System.out.println("sql语句用的参数是:"+value);
//修改完sql语句要用的参数
metaObject.setValue("parameterHandler.parameterObject", 11);
//执行目标方法
Object proceed = invocation.proceed();
//返回执行后的返回值
return proceed;
}
/**
* plugin:
* 包装目标对象的:包装:为目标对象创建一个代理对象
*/
@Override
public Object plugin(Object target) {
// TODO Auto-generated method stub
//我们可以借助Plugin的wrap方法来使用当前Interceptor包装我们目标对象
System.out.println("MyFirstPlugin...plugin:mybatis将要包装的对象"+target);
Object wrap = Plugin.wrap(target, this);
//返回为当前target创建的动态代理
return wrap;
}
/**
* setProperties:
* 将插件注册时 的property属性设置进来
*/
@Override
public void setProperties(Properties properties) {
// TODO Auto-generated method stub
System.out.println("插件配置的信息:"+properties);
}
}
mybatis-config.xml
<!--plugins:注册插件 -->
<plugins>
<plugin interceptor="com.atguigu.mybatis.dao.MyFirstPlugin">
<property name="username" value="root"/>
<property name="password" value="123456"/>
</plugin>
<plugin interceptor="com.atguigu.mybatis.dao.MySecondPlugin"></plugin>
</plugins>
/**
* 插件原理
* 在四大对象创建的时候
* 1、每个创建出来的对象不是直接返回的,而是
* interceptorChain.pluginAll(parameterHandler);
* 2、获取到所有的Interceptor(拦截器)(插件需要实现的接口);
* 调用interceptor.plugin(target);返回target包装后的对象
* 3、插件机制,我们可以使用插件为目标对象创建一个代理对象;AOP(面向切面)
* 我们的插件可以为四大对象创建出代理对象;
* 代理对象就可以拦截到四大对象的每一个执行;
*
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
*/
/**
* 插件编写:
* 1、编写Interceptor的实现类
* 2、使用@Intercepts注解完成插件签名
* 3、将写好的插件注册到全局配置文件中
*
*/
@Test
public void testPlugin(){
}
插件原理
- 按照插件注解声明,按照插件配置顺序调用插件plugin方法,生成被拦截对象的动态代理
- 多个插件依次生成目标对象的代理对象,层层包裹,先声明的先包裹;形成代理链
- 目标方法执行时依次从外到内执行插件的intercept方法。
- 多个插件情况下,我们往往需要在某个插件中分离出目标对象。可以借助MyBatis提供的SystemMetaObject类来进行获取最后一层的h以及target属性的值
Interceptor接口
•Intercept:拦截目标方法执行
•plugin:生成动态代理对象,可以使用MyBatis提供的Plugin类的wrap方法
•setProperties:注入插件配置时设置的属性
日志工厂
如果一个数据库操作,出现了异常,我们需要排错。日志就是最好的助手!
曾经用:sout 、debug
现在有:日志工厂!
- SLF4J
- LOG4J 【掌握】
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
- STDOUT_LOGGING 【掌握】
- NO_LOGGING
在Mybatis中具体使用那个一日志实现,可以在设置中设定!
STDOUT_LOGGING标准日志输出
在mybatis核心配置文件中,配置我们的日志!
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
Log4j
什么是Log4j?
- Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
- 我们也可以控制每一条日志的输出格式;
- 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
- 通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
- 先导入log4j的包
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
- log4j.properties
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/kuang.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
- 配置log4j为日志的实现
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
- Log4j的使用!,直接测试运行刚才的查询
- 简单使用
- 在要使用Log4j 的类中,导入包 import org.apache.log4j.Logger;
- 日志对象,参数为当前类的class
static Logger logger = Logger.getLogger(UserDaoTest.class);
- 日志级别
logger.info("info:进入了testLog4j");
logger.debug("debug:进入了testLog4j");
logger.error("error:进入了testLog4j");
分页
使用Limit分页
语法:SELECT * from user limit startIndex,pageSize;
SELECT * from user limit 3; #[0,n]
使用Mybatis实现分页,核心SQL
- 接口
//分页
List<User> getUserByLimit(Map<String,Integer> map);
- Mapper.xml
<!--//分页-->
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
select * from mybatis.user limit #{startIndex},#{pageSize}
</select>
- 测试
@Test
public void getUserByLimit(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put("startIndex",1);
map.put("pageSize",2);
List<User> userList = mapper.getUserByLimit(map);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
RowBounds分页(自带的)
不再使用SQL实现分页
- 接口
//分页2
List<User> getUserByRowBounds();
- mapper.xml
<!--分页2-->
<select id="getUserByRowBounds" resultMap="UserMap">
select * from mybatis.user
</select>
- 测试
@Test
public void getUserByRowBounds(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
//RowBounds实现
RowBounds rowBounds = new RowBounds(1, 2);
//通过Java代码层面实现分页
List<User> userList = sqlSession.selectList("com.kuang.dao.UserMapper.getUserByRowBounds",null,rowBounds);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
分页插件(PageHelper)
PageHelper是MyBatis中非常方便的第三方分页插件。
- 官方文档:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/README_zh.md
- 我们可以对照官方文档的说明,快速的使用插件
使用步骤
- 1、导入相关包pagehelper-x.x.x.jar和jsqlparser-0.9.5.jar。
- 2、在MyBatis全局配置文件中配置分页插件。
- 3、使用PageHelper提供的方法进行分页
- 4、可以使用更强大的PageInfo封装返回结果
案例:
//获取第1页,10条内容,默认查询总数count
PageHelper.startPage(1, 10);
List<User> list = userMapper.selectAll();
//用PageInfo对结果进行包装
PageInfo page = new PageInfo(list);
//测试PageInfo全部属性
//PageInfo包含了非常全面的分页属性
assertEquals(1, page.getPageNum());
assertEquals(10, page.getPageSize());
assertEquals(1, page.getStartRow());
assertEquals(10, page.getEndRow());
assertEquals(183, page.getTotal());
assertEquals(19, page.getPages());
assertEquals(1, page.getFirstPage());
assertEquals(8, page.getLastPage());
assertEquals(true, page.isFirstPage());
assertEquals(false, page.isLastPage());
assertEquals(false, page.isHasPreviousPage());
assertEquals(true, page.isHasNextPage());
测试二
PageHelper.startPage(1, 4);
List<Book> books=bookService.queryAllBook();
PageInfo<Book> pageInfo=new PageInfo<Book>(books);
for (Book c : pageInfo.getList()) {
System.out.println(c);
}
注意
PageHelper.startPage一定要在查询前面
实战
/**
* 分页查询
*/
@RequestMapping(value="/allBook",method=RequestMethod.GET)
public String pageList(@RequestParam(value = "page", defaultValue = "1") Integer page, Model model){
//获取指定页数据,大小为8
Integer pageSize=4;
//分页查询
PageHelper.startPage(page, pageSize);
List<Book> list = bookServiceImpl.queryAllBook();
//使用PageInfo包装数据
PageInfo<Book> pageInfo=new PageInfo<Book>(list);
model.addAttribute("pageInfo", pageInfo);
return "allBook";
}
<div class="row">
<div class="col-md-6">
第${pageInfo.pageNum}页,共${pageInfo.pages}页,共${pageInfo.total}条记录
</div>
<div class="col-md-6 offset-md-4">
<nav aria-label="Page navigation example">
<ul class="pagination pagination-sm">
<li class="page-item"><a class="page-link" href="${pageContext.request.contextPath}/allBook?page=1">首页</a></li>
<c:if test="${pageInfo.hasPreviousPage}">
<li class="page-item"><a class="page-link"
href="${pageContext.request.contextPath}/allBook?page=${pageInfo.pageNum-1}">上一页</a></li>
</c:if>
<c:forEach items="${pageInfo.navigatepageNums}" var="page">
<c:if test="${page==pageInfo.pageNum}">
<li class="page-item active"><a class="page-link" href="#">${page}</a></li>
</c:if>
<c:if test="${page!=pageInfo.pageNum}">
<li class="page-item"><a class="page-link"
href="${pageContext.request.contextPath}/allBook?page=${page}">${page}</a></li>
</c:if>
</c:forEach>
<c:if test="${pageInfo.hasNextPage}">
<li class="page-item"><a class="page-link"
href="${pageContext.request.contextPath}/allBook?page=${pageInfo.pageNum+1}">下一页</a></li>
</c:if>
<li class="page-item"><a class="page-link" href="${pageContext.request.contextPath}/allBook?page=${pageInfo.pages}">末页</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
PageInfo
//当前页
private int pageNum;
//每页的数量
private int pageSize;
//当前页的数量
private int size;
//由于startRow和endRow不常用,这里说个具体的用法
//可以在页面中"显示startRow到endRow 共size条数据"
//当前页面第一个元素在数据库中的行号
private int startRow;
//当前页面最后一个元素在数据库中的行号
private int endRow;
//总记录数
private long total;
//总页数
private int pages;
//结果集
private List<T> list;
//第一页
private int firstPage;
//前一页
private int prePage;
//是否为第一页
private boolean isFirstPage = false;
//是否为最后一页
private boolean isLastPage = false;
//是否有前一页
private boolean hasPreviousPage = false;
//是否有下一页
private boolean hasNextPage = false;
//导航页码数
private int navigatePages;
//所有导航页号
private int[] navigatepageNums;