一、插件的简介
1、简介
- 与其称为Mybatis插件,不如叫Mybatis拦截器,更加符合其功能定位,
- 实际上它就是一个拦截器,应用代理模式,在方法级别上进行拦截。
2、作用
- 在四大对象创建的时候,都会进行拦截
- 在四大方法创建之前&后,来做一些想做的事情(类似于AOP)
- 插件会包每一个对象的创建过程
- 四大对象:
- 执行器Executor(update、query、commit、rollback等方法);
- 参数处理器ParameterHandler(getParameterObject、setParameters方法);
- 结果集处理器ResultSetHandler(handleResultSets、handleOutputParameters等方法);
- SQL语法构建器StatementHandler(prepare、parameterize、batch、update、query等方法);
3、应用场景
- 分页功能
- mybatis的分页默认是基于内存分页的(查出所有,再截取),数据量大的情况下效率较低,不过使用mybatis插件可以改变该行为,只需要拦截StatementHandler类的prepare方法,改变要执行的SQL语句为分页语句即可;
- 公共字段统一赋值
一般业务系统都会有创建者,创建时间,修改者,修改时间四个字段,对于这四个字段的赋值,实际上可以在DAO层统一拦截处理,可以用mybatis插件拦截Executor类的update方法,对相关参数进行统一赋值即可; - 性能监控
- 对于SQL语句执行的性能监控,可以通过拦截Executor类的update, query等方法,用日志记录每个方法执行的时间;
- 其它
- 其实mybatis扩展性还是很强的,基于插件机制,基本上可以控制SQL执行的各个阶段,如执行阶段,参数处理阶段,语法构建阶段,结果集处理阶段,具体可以根据项目业务来实现对应业务逻辑。
二、自定义插件
1、自定义插件的编写
插件的编写步骤:
第一步: 实现Interceptor接口 第二步:在实现类上配置 @Intercepts-->@Signature 指定要拦截的方法 第三步:将写好的插件注册到全局配置文件中 <plugins> --> <pugin > --> <property>
//配置要拦截的方法:
@Intercepts(
{
//可以配置多个连接的方法:type指定四大对象的哪个对象 method指定具体方法 args指定参数列表
@Signature(type = StatementHandler.class,method = "parameterize",args = java.sql.Statement.class)
}
)
public class MyFirstPlugin implements Interceptor {
//拦截:拦截目标对象的目标方法(要拦截四大对象的哪个对象的哪个方法)
public Object intercept(Invocation invocation) throws Throwable {
//放行:执行目标方法
Object proceed = invocation.proceed();
//返回执行后的返回值
return proceed;
}
//包装目标对象的方法
public Object plugin(Object o) {
//1、可以自己写一个动态代理进行包装
//2、mybatis提供了一个包装的方法:第一个参数为要包装的内容,第二个为用哪个拦截器包装
Object wrap = Plugin.wrap(o, this);
//返回包装后的对象
return wrap;
}
//将插件注册时的property属性注册进来
public void setProperties(Properties properties) {
//插件配置的信息
System.out.println("插件配置的信息: "+properties);
}
}
<plugins>
<plugin interceptor="com.zy.util.MyFirstPlugin" >
<!-- 可以传入一些参数 -->
<property name="username" value="root"/>
<property name="password" value="123456"/>
</plugin>
</plugins>
2、多个插件的执行流程
会按照主配置文件中配置的顺序对 四大对象的某些方法 顺序包装
三、PageHelpler分页插件的使用
1、PageHelpler使用步骤:
官方地址:https://pagehelper.github.io/
PageHelpler使用步骤:
第一步:引入jar包 第二步:配置拦截器插件 <plugins> --> <pugin > --> <property> 在property标签中定义参数(参数看官网) 第三步:在代码中使用(官网提供了很多方式)
1、引入jar
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.2.1</version>
</dependency>
2、配置全局文件
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 使用下面的方式配置参数,后面会有所有的参数介绍 -->
<!-- <property name="param1" value="value1"/>-->
</plugin>
</plugins>
3、使用(官网提供的方式有下面几种)
//第一种,RowBounds方式的调用
List<Country> list = sqlSession.selectList("x.y.selectIf", null, new RowBounds(0, 10));
//第二种,Mapper接口方式的调用,推荐这种使用方式。
PageHelper.startPage(1, 10);
List<Country> list = countryMapper.selectIf(1);
//第三种,Mapper接口方式的调用,推荐这种使用方式。
PageHelper.offsetPage(1, 10);
List<Country> list = countryMapper.selectIf(1);
//第四种,参数方法调用
//存在以下 Mapper 接口方法,你不需要在 xml 处理后两个参数
public interface CountryMapper {
List<Country> selectByPageNumSize(
@Param("user") User user,
@Param("pageNum") int pageNum,
@Param("pageSize") int pageSize);
}
//配置supportMethodsArguments=true
//在代码中直接调用:
List<Country> list = countryMapper.selectByPageNumSize(user, 1, 10);
//第五种,参数对象
//如果 pageNum 和 pageSize 存在于 User 对象中,只要参数有值,也会被分页
//有如下 User 对象
public class User {
//其他fields
//下面两个参数名和 params 配置的名字一致
private Integer pageNum;
private Integer pageSize;
}
//存在以下 Mapper 接口方法,你不需要在 xml 处理后两个参数
public interface CountryMapper {
List<Country> selectByPageNumSize(User user);
}
//当 user 中的 pageNum!= null && pageSize!= null 时,会自动分页
List<Country> list = countryMapper.selectByPageNumSize(user);
//第六种,ISelect 接口方式
//jdk6,7用法,创建接口
Page<Country> page = PageHelper.startPage(1, 10).doSelectPage(new ISelect() {
@Override
public void doSelect() {
countryMapper.selectGroupBy();
}
});
//jdk8 lambda用法
Page<Country> page = PageHelper.startPage(1, 10).doSelectPage(()-> countryMapper.selectGroupBy());
//也可以直接返回PageInfo,注意doSelectPageInfo方法和doSelectPage
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(new ISelect() {
@Override
public void doSelect() {
countryMapper.selectGroupBy();
}
});
//对应的lambda用法
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(() -> countryMapper.selectGroupBy());
//count查询,返回一个查询语句的count数
long total = PageHelper.count(new ISelect() {
@Override
public void doSelect() {
countryMapper.selectLike(country);
}
});
//lambda
total = PageHelper.count(()->countryMapper.selectLike(country));
2、案例:
(1)配置好分页插件之后,在要分页的查询方法之前添加: 方法一: //得到每一页的数据 PageHelper.startPage(开始位置,个数); 查询语句 方法二: 使用pageInfo类,可以设置连续显示几个PageHelper分好的页 把查询来的数据list,使用PageInfo对象包装 PageInfo<User> pageInfo = new PageInfo<User>(分好页的数据,连续显示的页数); (2)分页插件还可以查看当前分页的详细信息: Page<Object> page = PageHelper.startPage(开始位置,个数); 通过page.getXXX 拿到各种信息 还可以通过pageInfo拿到各种值
//分页插件的使用
@Test
public void test3()throws Exception{
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("Config.xm"));
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//在查询方法之前调用下面语句,进行分页
Page<Object> page = PageHelper.startPage(1, 5);
//查询方法
List<User> users = mapper.selectByExample(null);
//连续显示几页的设置(3页)
PageInfo<User> pageInfo = new PageInfo<User>(users,3);
for (User i:users)
System.out.println(i.toString());
System.out.println("当前页码:"+page.getPageNum());
System.out.println("总记录数"+page.getTotal());
System.out.println("每页的记录数"+page.getPageSize());
System.out.println("总页码"+page.getPages());
System.out.println("当前页码:"+pageInfo.getPageNum());
System.out.println("总记录数"+pageInfo.getTotal());
System.out.println("每页的记录数"+pageInfo.getPageSize());
System.out.println("总页码"+pageInfo.getPages());
System.out.println("是否第一页:"+pageInfo.isIsFirstPage());
System.out.println("连续显示的页码");
int[] nums = pageInfo.getNavigatepageNums();
for (int i:nums)
System.out.println(i);
}