MyBatis 之 自定义 Plugin
mybatis 的插件,实际上是拦截器,通过这些插件可以改变 mybatis 的默认行为。mybatis 可以拦截的对象有:
(1)Executor,执行的 SQL 全过程,包括组装参数、组装结果返回和执行 SQL 的过程等都可以拦截
(2)StatementHandler,执行 SQL 的过程,拦截该对象可以重写执行 SQL 的过程
(3)ParameterHandler,执行 SQL 的参数组装,拦截该对象可以重写组装参数的规则
(4)ResultSetHandler,执行结果的组装,拦截该对象可以重写组装结果的规则
自定义简易分页插件
实现 Interceptor 类
java
package com.mengweijin.learning.mybatis.plugins;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
/**
* 拦截 Executor 的 query 方法
*
* @author mengweijin
*/
@Intercepts({
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
),
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
)
})
@Slf4j
public class PageInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 从 Invocation 中获取参数,索引对应的是 @Signature 注解中 query 方法的参数的位置。
final Object[] args = invocation.getArgs();
final MappedStatement ms = (MappedStatement) args[0];
final Object parameterObject = args[1];
// 封装了逻辑分页的参数 offset, limit,这里也可以不使用这个参数,而是存放到 ThreadLocal 中
// 调用前先在 Service 层设置 offset, limit 到 ThreadLocal, 这里再从 ThreadLocal 中获取
RowBounds rowBounds = (RowBounds) args[2];
// 构造新的 sql, select xxx from xxx where yyy limit offset,limit
final BoundSql boundSql = ms.getBoundSql(parameterObject);
String limitSql = boundSql.getSql() + " limit " + rowBounds.getOffset() + "," + rowBounds.getLimit();
BoundSql pageBoundSql = new BoundSql(
ms.getConfiguration(),
limitSql,
boundSql.getParameterMappings(),
boundSql.getParameterObject());
// 被代理对象
Executor executor = (Executor) invocation.getTarget();
CacheKey cacheKey = executor.createCacheKey(ms, parameterObject, rowBounds, boundSql);
// 调用修改后的 sql 继续执行查询
return executor.query(ms, parameterObject, rowBounds, (ResultHandler) args[3], cacheKey, pageBoundSql);
}
}
在 SpringBoot 中注册到 MyBatis
java
package com.mengweijin.learning.mybatis.plugins;
import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author mengweijin
*/
@Configuration
public class PluginAutoConfiguration {
@Bean
ConfigurationCustomizer mybatisConfigurationCustomizer() {
return configuration -> configuration.addInterceptor(new PageInterceptor());
}
}
使用
xml
<select id="selectByRowBounds" resultType="com.mengweijin.learning.mybatis.entity.User" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from SYS_USER
<include refid="Where_Condition"/>
</select>
java
@Mapper
public interface UserMapper {
List<User> selectByRowBounds(User user, RowBounds rowBounds);
}
java
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/listByRowBounds")
public List<User> listByRowBounds(User user) {
return userMapper.listByRowBounds(user, new RowBounds(0, 10));
}
}
自定义 Plugin 实现数据库读写分离
思路如下: 拦截 Executor.class, 如果是 Select 类型的 SQL,修改数据源为“读库”,否则修改数据源为“写库”,之后,继续执行 SQL。