mybatis插件Plugin、TypeHandler、二级缓存问题

本文详细探讨了Mybatis的Plugin、TypeHandler和二级缓存。Plugin作为拦截器,用于在SQL执行前修改SQL或进行路由配置,例如分页插件。二级缓存的作用范围是mapper配置文件,其初始化过程也在文中解析。TypeHandler则是Mybatis处理Java类型和JDBC类型转换的关键,包括默认和自定义类型处理器的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文介绍mybatis的三个部分:Plugin、TypeHandler、二级缓存问题。

mybatis插件Plugin使用与原理

插件适用场景:通过拦截mybatis底层执行时的一些方法,可以在sql执行前改写sql,或者做路由配置等等。常用场景如下:分页插件、分表分库等等。。。。。
插件的使用:
1. 插件必须实现org.apache.ibatis.plugin.Interceptor接口。它可以拦截executor、statementHandler、resultHandler、parameterHandler接口。
由下图可见,插件在构建时候的入口只在这四个接口的实现之中。
这里写图片描述
2. 添加@Intercepts,@Signature注解完成对需要拦截的对象和接口的定义。@Signature可以表示唯一的一个方法签名,可以通过拦截多个方法。
3. 将插件配置至mybatis-config.xml文件中

举例一个简单的分页插件:

需要分页的dao层查询方法如下

@MybatisDao
public interface OperationLogDAO {

    List<OperationLogDO> find(OperationLogQuery operationLogQuery, 
        RowBounds rowBounds);
}

分页插件实现类,实现Interceptor接口,添加拦截注解@Intercepts、@Signature

//.....

@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class PaginationInterceptor implements Interceptor {

    //......
    private Dialect dialect;


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        processInterceptor(invocation.getArgs());
        return invocation.proceed();
    }

    // 修改参数,加上LIMIT 参数
    private void processInterceptor(final Object[] queryArgs) {
           //.....获取到rowbounds参数的分页参数,做字符串拼接在sql的最后即可
    }

    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
}

将插件配置添加到mybatis配置文件中

<configuration>
    <plugins>
        <plugin interceptor="com.youzan.beauty.helper.mybatis.PaginationInterceptor">
           <property name="dialectClass" 
            value="com.youzan.beauty.helper.mybatis.MySQLDialect"/>
        </plugin>
    </plugins>
</configuration>

通过上述配置插件的实现便已完成。但是它的原理是怎样的呢?用一句话解释就是:
在实例化executor、statementHandler、resultHandler、parameterHandler时生成的就是一个这些接口实例为目标的一个动态代理对象。仍然以分页插件例子来看,PaginationInterceptor是拦截executor的,所以实例化executor源码如下:

//org.apache.ibatis.session.Configuration#newExecutor源码
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
   executorType = executorType == null ? defaultExecutorType : executorType;
   executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
   Executor executor;
   //.....
   //下面的一句话便是插件嵌入的过程
   executor = (Executor) interceptorChain.pluginAll(executor);
   return executor;
}

//org.apache.ibatis.plugin.InterceptorChain#pluginAll 方法源码如下
//将所有的自定义插件都做判断,满足条件的会生成代理类
public Object pluginAll(Object target) {
   for (Interceptor interceptor : interceptors) {
     target = interceptor.plugin(target);
   }
   return target;
}

//PaginationInterceptor的plugin方法可以直接用mybatis的plugin类的编织方法。
public Object plugin(Object target) {
        return Plugin.wrap(target, this);
}

//org.apache.ibatis.plugin.Plugin#wrap方法源码
public static Object wrap(Object target, Interceptor interceptor) {
    //通过解析intercepter的注解,得到需要拦截的类与方法的map
   Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
   Class<?> type = target.getClass();
   //这里是通过塞选出目标类的接口,用来生成动态代理
   Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
   if (interfaces.length > 0) { 
     //如果是满足目标的类,就为其生成动态代理
     return Proxy.newProxyInstance(
         type.getClassLoader(),
         interfaces,
         new Plugin(target, interceptor, signatureMap));
   }
   return target;
}

//Plugin是实现InvocationHandler的。它的invoke方法源码如下:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    Set<Method> methods = signatureMap.get(method.getDeclaringClass());
    //如果执行的是插件需要拦截的目标方法,则执行插件的intercept方法。否则按原方法执行。
    if (methods != null && methods.contains(method)) {
      return interceptor.intercept(new Invocation(target, method, args));
    }
    return method.invoke(target, args);
  } catch (Exception e) {
    throw ExceptionUtil.unwrapThrowable(e);
  }
}

至此为止,就知道插件是如何实现的。通过生成目标类的动态代理实现。

mybatis的二级缓存问题

以前总听说mybatis二级缓存有问题,但总是记不住什么问题。这一次算是弄清楚了。
先做个总结:
mybatis二级缓存对象作用域是对应一个mapper配置文件的,就是mapper的作用域namespace。

假设有两个mapper文件 aMapper.xml, bMapper.xml。 (他们两的namespace不一样)

当开启二级缓存时,aMapper.xml用的是aCache这个缓存对象,bMapper.xml用的的bCache对象(因为二级缓存对象会保存在
一个mybatis的map里,这个map的key就是namespace,value就是二级缓存对象)。

aMapper有一个查询a表的方法,当第一次查询时会将缓存保存到aCache中,
以便第二次同样的查询直接可以取aCache缓存中的值。

但是如果bMapper对a表做了update/delete/insert方法后,它不会清除aCache的缓存
(更新操作只会清除本作用域下的缓存). 以至于aCache中的数据还是旧的

当再次在aMapper做同样的查询时查出的是旧数据。

二级缓存初始化源码:

<configuration>
    <settings>
        //cacheEnabled表示二级缓存是否开启
        <setting name="cacheEnabled" value="true"/>
        //.....
    </settings>
</configuration>
//初始化二级缓存是在解析mybatis配置文件时候开始的。会将二级缓存对象放入configuration的caches属性中。
public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
      //初始化二级缓存实例时,id就是currentNamespace
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

mybatis的类型处理器typeHandler

根据上一篇mybatis框架解读可知:
mybatis参数和结果处理器ParameterHandler、ResultHandler用来设置参数和返回结果转换,它们的内部实现都是通过TypeHandler。TypeHandler是mybatis中将javatype和jdbctype相互转换的核心处理器。mybatis有大量默认的类型处理器,可以满足大部分需求。即使mapper配置文件不指定类型,它依然可以转化。当时需要转化我们自定义的类型时,则需要自定义类型处理器。自定义类型处理器只需要实现TypeHandler接口即可。

默认类型处理器都会在实例化configuration时注册在configuration的TypeHandlerRegistry属性中。

//org.apache.ibatis.type.TypeHandlerRegistry#TypeHandlerRegistry
 public TypeHandlerRegistry() {
    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());
    //......
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值