Mybatis 插件
1.1 插件介绍
介绍:
Mybatis作为一个应用广泛的ORM开源框架,其拥有很大的灵活性,在四大组件(Excutor、 StatementHandler、ParameterHandler、ResultHandler)处理了简单易用的插件扩展机制,Mybatis对持 久层的操作就是借助于四大核心对象。Mybatis支持用插件对四大核心对象进行拦截,对Mybatis来说插 件就是拦截器,用来增强核心对象的功能,增强功能本质上是借助于底层的动态代理实现的,换句话 说,Mybatis中的四大对象都是代理对象
Mybatis所允许拦截的方法如下:
执行器Executor(update、query、commit、rollback等方法);
参数处理器ParameterHandler(getParameterObject、setParameters方法);
结果集处理器ResultSetHandler(handleResultSets、handleOutputParameters等方法);
SQL语法构建器StatementHandler(prepare、parameterize、batch、update、query等方法);
1.2 原理
在四大对象创建的时候,
1. 每个创建出来的对象不是直接返回的,而是interceptorChain.pluginAll(parameterHandler);
2. 获取到的所有interceptor(拦截器){插件需要实现的接口};调用interceptor.plugin(target);
返回target包装后的对象;
3. 插件机制,我们可以使用插件为目标对象;AOP(面向切面)我们的插件可以为四大对象创建出代理对象
,代理对象就可以拦截到四大对象的每一个执行;
简单截取Executor具体实现类获取代码:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? this.defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Object executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (this.cacheEnabled) { // 二级缓存Executor
executor = new CachingExecutor((Executor)executor);
}
// 每个Executor实例,都会被经过interceptorChain进行处理
Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
return executor;
}
拦截原理参考设计模式 JDK动态代理+责任链设计模式
在实际开发过程中,我们经常使用的Mybaits插件就是分页插件了,
通过分页插件我们可以在不用写count语句和limit的 情况下就可以获取分页
后的数据,给我们开发带来很大的便利。除了分页,插件使用场景主要还有
更新数据库的通用字段,分库分表,加解密等的处理。
1.3 自定义插件
1.3.1 Mybatis插件接口-interceptor
intercept方法,插件的核心方法
plugin方法,生成target的代理对象
setProperties方法,传递插件所需参数
1.3.2 自定义插件
设计实现一个自定义插件:
package com.lg.plugin;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.sql.Connection;
import java.util.Properties;
@Intercepts({ // 注意这个大花括号,也就是说这里可以多个@Signature对多个地方进行拦截,都用这个拦截器
@Signature(type = StatementHandler.class, // 拦截哪个类
method = "prepare", // 拦截哪个方法
args = {Connection.class,Integer.class}) // 为避免拦截到重载的方法,传入拦截参数,入参必须和拦截方法入参类型一致
})
public class MyPlugin implements Interceptor {
// 由以上原理可看出,目标对象的目标方法被执行时,每次都会执行 intercept 方法
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("对方法进行增强...");
return invocation.proceed();
}
// 将拦截的目标类对象,作为入参,传入拦截器,拦截器进行代理,生成代理对象
/**
* 包装目标对象,为目标对象创建代理对象
* @param target 为拦截的对象
* @return 代理对象
*/
public Object plugin(Object target) {
return Plugin.wrap(target,this);
}
//获取配置文件的属性
// 插件初始化的时候调用,也只调用一次,插件配置的属性从这里进来
public void setProperties(Properties properties) {
System.out.println("接收到传入的参数为==>" + properties);
}
}
1.4 Mybatis插件机制-pageHelper
开发步骤:
- 导入通用PageHelper的坐标
- 在mybatis核心配置文件中配置PageHelper插件
- 测试分页数据获取
①导入通用PageHelper坐标
<!--分页助手-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>3.7.5</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>0.9.1</version>
</dependency>
②在mabatis核心配置文件中配置 PageHelper 插件
<plugins>
<plugin interceptor="com.github.pagehelper.PageHelper">
<property name="dialect" value="mysql"/>
</plugin>
</plugins>
③ 测试分页代码实现
// 测试分页
@Test
public void testPage(){
PageHelper.startPage(1,3);
StuDao mapper = sqlSession.getMapper(StuDao.class);
List<Stu> stus = mapper.selectList();
for (Stu stu:
stus) {
System.out.println(stu.toString());
}
PageInfo<Stu> stuPageInfo = new PageInfo(stus);
System.out.println("总页码==>" + stuPageInfo.getPages());
System.out.println("总条数==>" + stuPageInfo.getTotal());
System.out.println("当前页==>" + stuPageInfo.getPageNum());
}
撸结果:
1.5 Mybatis通用插件Mapper
什么是通用Mapper?
通用Mapper是为了解决单表的增删改查,基于Mybatis的插件机制,开发人员不需要编写sql,不需要在DAO中增加方法,只要写好实体类,就能支持相应的增删改查方法
如何使用?
-
- 导入通用mapper依赖
<!--通用Mapper-->
<!-- https://mvnrepository.com/artifact/tk.mybatis/mapper -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>3.4.5</version>
</dependency>
-
- 在mabatis核心配置文件中配置 mapper插件
<plugin interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor">
<!--指定当前通用mapper接口使用的是哪个-->
<property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
</plugin>
-
- 实体类设置主键,设置表名
package com.lg.pojo;
import javax.persistence.*;
/**
* 学生信息表
*/
@Table(name = "Stu") // 映射某张表
public class Stu {
@Override
public String toString() {
return "Stu{" +
"sid=" + sid +
", sname='" + sname + '\'' +
", gender='" + gender + '\'' +
", phone='" + phone + '\'' +
", hobby='" + hobby + '\'' +
", info='" + info + '\'' +
'}';
}
@Id // 对应的是注解id
@GeneratedValue(strategy = GenerationType.IDENTITY) // mysql是 IDENTITY 自增,Oracle是 SEQUENCE 序列
private int sid;
@Column(name = "sname") // 名称一致,则不用写
private String sname;
private String gender;
private String phone;
private String hobby;
private String info;
public int getSid() {
return sid;
}
public void setSid(int sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getHobby() {
return hobby;
}
public void setHobby(String hobby) {
this.hobby = hobby;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}
④定义通用Mapper
// 只需继承 Mapper 接口即可
public interface StuMapper_Plugin extends Mapper<Stu> {
}
测试类:
// 测试通用Mapper插件
@Test
public void testMapper(){
StuMapper_Plugin mapper = sqlSession.getMapper(StuMapper_Plugin.class);
Stu stu = new Stu();
stu.setSid(313);
Stu stu1 = mapper.selectOne(stu);
System.out.println(stu1.toString());
// 2. Example方法
Example example = new Example(Stu.class);
example.createCriteria().andEqualTo("sid",313);
mapper.selectByExample(example);
}