简介
mybatis作为一个优秀的ORM框架,其框架本身具有很高的灵活性,有四大组件提供了简单易用的拓展机制。Mybatis支持用插件对这四大核心组件进行拦截,对核心功能进行增强,增强功能的实现就是借助底层的动态代理功能实现的。
Mybatis允许拦截的方法:
1、执行器Executor (update、query、commit、rollback等方法);
2、SQL语法构建器StatementHandler (prepare、parameterize、batch、updates query等方法);
3、参数处理器ParameterHandler (getParameterObject、setParameters方法);
4、结果集处理器ResultSetHandler (handleResultSets、handleOutputParameters等方法);
原理
mybatis是如何进行拦截的呢?我们以parameterHandler为例:
public ParameterHandler newParameterHandler(MappedStatement mappedStatement,
Object object, BoundSql sql, InterceptorChain interceptorChain){
ParameterHandler parameterHandler =
mappedStatement.getLang().createParameterHandler(mappedStatement,object,sql);
parameterHandler = (ParameterHandler)
interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
从代码里面我们能看出来,新建parameterHandler对象不是直接去创建返回的对象,而是使用interceptorChain.pluginAll方法返回的对象。
interceptorChain保存了所有的interceptor拦截器对象,是在mybatis初始化的时候创建的,调用连接器链中的拦截器依次对目标对象进行增强,interceptor.plugin(target)中的target可以理解为四大组件对象,返回的是增强过后的对象。
如果我们想要拦截Executor的query方法,那么我们可以这样定义插件
@Intercepts({
@Signature(
type = Executor.class,
method = "query",
args=
{MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class}
)
})
public class ExeunplePlugin implements Interceptor {
//省略逻辑
}
同时我们还需要在config.xml中进行配置
<plugins>
<plugin interceptor="com.lagou.plugin.ExamplePlugin">
</plugin>
</plugins>
这样mybatis在启动的时候就会去加载插件,并且保存插件实例到interceptorChian中,等启动完成后,我们执行sql的时候,需要先使用defaultSessionFactory创建sqlsession,在创建sqlsession的时候就回去创建executor对象,这个时候就会创建动态代理对象,所以我们就能控制在原本的方法上执行增强方法
自定义插件
1、创建自定义插件
@Intercepts(
@Signature(type = StatementHandler.class,
method="prepare",
args={Connection.class,Integer.class}
)
)
public class mybatis_plugin implements Interceptor {
// 对方法进行增强
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("mybatis增强-------------");
Object proceed = invocation.proceed();// 使用原来的方法
return proceed;
}
// 将拦截器存入拦截链中
@Override
public Object plugin(Object o) {
Object wrap = Plugin.wrap(o, this);
return wrap;
}
// 配置参数
@Override
public void setProperties(Properties properties) {
System.out.println("properties: "+properties);
}
}
2、sqlconnection.xml
<plugin interceptor="plugin.mybatis_plugin"></plugin>
3、mapper接口和mapper.xml
这里写正常的mapper接口和mapper.xml就可以了,这里就不贴代码了
4、客户端调用
@Test
public void pluginQuery(){
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.queryList();
System.out.println("查询数据成功!");
}
5、执行结果
源码分析
执行插件逻辑
plugin实现了 InvocationHandler接口,所以它的invoke方法会拦截所有的方法调用, 并且会对拦截的方法进行检测,判断是否执行插件方法还是执行原来的方法
首先invoke方法判断被拦截的方法是否在被@Signature注解中的方法,如果是就执行插件逻辑
插件逻辑封装在intercept中,该
方法的参数类型为Invocationo Invocation主要用于存储目标类,方法以及方法参数列表
pageHelper分页插件
Mybatis可以使用第三方分页插件,对查询的数据进行分页查询
我们使用pageHelper举例
1、导入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>
2、sqlconnection.xml
<plugin interceptor="com.github.pagehelper.PageHelper">
<property name="dialect" value="mysql"/>
</plugin>
3、客户端测试
@Test
public void pageHelperQuery(){
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
PageHelper.startPage(1, 20);
List<User> users = mapper.queryList();
PageInfo<User> userPageInfo = new PageInfo<>(users);
System.out.println("条数:"+userPageInfo.getSize());
}
4、执行结果
通用Mapper
通用Mapper是为了解决对单表的增删改查,开发人员不需要写sql也不需要写对应的接口,只要写好实体类,就能支持对应的增删改查方法
1、引入依赖
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>3.1.2</version>
</dependency>
2、sqlconnection.xml
<plugin interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor">
<!-- 通用Mapper接口,多个通用接口用逗号隔开 -->
<property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
</plugin>
3、实体类设置主键
@Table(name="user")
public class User implements Serializable {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
private String name;
// 省略get set方法
}
4、定义通用mapper
import mode.User;
import tk.mybatis.mapper.common.Mapper;
public interface commonMapper extends Mapper<User> {
}
5、测试
@Test
public void commonMybatisMapper(){
// 1. 使用通过mapper进行查询
SqlSession sqlSession = sqlSessionFactory.openSession();
commonMapper mapper = sqlSession.getMapper(commonMapper.class);
User user = new User();
user.setId(1);
User selectOneUser = mapper.selectOne(user);
System.out.println(selectOneUser);
// 2.使用模板
Example example = new Example(User.class);
example.createCriteria().andEqualTo("id",1);
List<User> users = mapper.selectByExample(example);
users.forEach(System.out::println);
AtomicReference<Map<String,Boolean>> objectAtomicReference = new AtomicReference<>();
Map<String, Boolean> stringBooleanMap = objectAtomicReference.get();
}