Skip to content

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。