浅谈mybatis-plugin

在技术探讨之前,想提一个疑问,为啥要解读源码和核心技术呢?其实初级人员一般会觉得使用就行,没必要用那么多时间研读源码和流程。但是个人觉得解读源码有以下优点:
1、深入了解java,对自己相关整体知识的回归
2、拥抱设计模式,体验开源技术的设计思路
3、自主设计和数学逻辑思维提升
4、学会总结,提升自我

简介

    mybatis插件是核心部分,在实际应用中有一点使用价值,比如物理分页查询,批量操作,数据库脚本监听和过滤,参数修改,日志监控等。mybatis插件时允许对点的映射语句进行拦截调用,默认情况下是方法拦截,如一下方法:
Executor(update,query,flushStatements,commit,rollback,getTransaction, close, isClosed)
ParameterHandler(getParameterObject,setParameters)
ResultSetHandler(handleResultSets,handleOutputParameters)
StatementHandler(prepare,parameterize,batch,update,query)

使用方式

    通过@Intercepts注解使用生效,@Signature定义拦截属性,Signature属性解析

  1. type 拦截的类型,class类型
  2. method 拦截的方法,string类型
  3. args方法参数类型,class数组类型,因为防止重载
import org.apache.ibatis.executor.Executor;
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;

@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class MyBatchExecutor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 前置处理
        Object returnObject = invocation.proceed(); // 也可以直接执行自己定义的处理器
        // 后置处理
        return returnObject;
    }
}

常用两种配置,一种xml配置,一种java配置,不管是那种配置,都是configuration#addInterceptor方法加入拦截器
在xml中定义

<!-- mybatis-config.xml -->
<plugins>
  <plugin interceptor="**.**.MyBatchExecutor" />
</plugins>

或者居于注解中引用拦截器

import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatisConfig {
    @Bean
    public ConfigurationCustomizer mybatisConfigurationCustomizer() {
        return new ConfigurationCustomizer() {
            @Override
            public void customize(org.apache.ibatis.session.Configuration configuration) {
                configuration.addInterceptor(new MyBatchExecutor());
            }
        };
    }
}

拦截器原理

interceptor装载

在这里插入图片描述

         mybati其实是一种all-in-one,也就是所有数据配置归根于Configuration,具体初始化时居于xml解析存放到configuration中。另一种居于spring方式,构建工厂SqlSessionFactoryBean来构建SqlSessionFactory,比如spring boot的自动装配MybatisAutoConfiguration。
        

interceptor拦截

在这里插入图片描述
上面一图解答为啥说拦截器只针对四个地方拦截。此处是使用了动态代理。事先设置好所有拦截器,然后再调用方法前调用拦截器。
分别两步:
1、生成动态代理。
2、代理对象反射获取,方法判断后直接执行。
在这里插入图片描述

拦截器使用案例

这里编写批量操作拦截器案例

批量插件

    我们都知道mybatis的Executor有三种,SimpleExecutor、ReuseExecutor、BatchExecutor,其实BatchExecutor已经实现的批量操作,但是不够好用。接下来我们定义物理批量操作,我们已map对应的id以Batch结束时,实现物理批量操作

import org.apache.ibatis.executor.BatchExecutor;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.transaction.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;


public class BatchAdaptor extends BatchExecutor {
    private Logger log = LoggerFactory.getLogger(BatchAdaptor.class);

    public BatchAdaptor(Configuration configuration, Transaction transaction) {
        super(configuration, transaction);
    }

    @Override
    public int update(MappedStatement ms, Object parameter) throws SQLException {
        if (parameter == null) {
            return super.update(ms, parameter);
        }
        Object params = null;
        if (Map.class.isAssignableFrom(parameter.getClass())) { // DefaultSqlSession#wrapCollection
            final Map<String, Object> paramMap = (Map<String, Object>) parameter;
            if (paramMap.size() == 1) { // Map中array
                if (paramMap.get("array") != null) {
                    params = paramMap.get("array");
                } else {
                    params = paramMap.values().iterator().next();
                }
            } else if (paramMap.size() == 2) {
                params = paramMap.get("collection");
            }
        } else if (parameter instanceof Iterable || parameter.getClass().isArray()) {
            params = parameter;
        } else {
            params = Collections.singletonList(parameter);
        }
        final Iterable<?> paramIterable = toIterable(params);
        try {
            for (Object obj : paramIterable) {
                super.update(ms, obj); // addBatch
            }
            List<BatchResult> batchResults = doFlushStatements(false); // executeBatch
            if (batchResults == null || batchResults.size() == 0) {
                return 0;
            }
            return resolveUpdateResult(batchResults);
        } catch (Exception e) {
            log.error("batch execute", e);
            doFlushStatements(true);
            /**
             * 批量插入,则报异常
             */
            if ("INSERT".equalsIgnoreCase(ms.getSqlCommandType().name())) {
                throw e;
            }
            return 0;
        }
    }

    /**
     * 返回批量结果成功数
     *
     * @param batchResults
     * @return
     */
    private int resolveUpdateResult(final List<BatchResult> batchResults) {
        int result = 0;
        for (BatchResult batchResult : batchResults) {
            int[] updateCounts = batchResult.getUpdateCounts();
            if (updateCounts == null || updateCounts.length == 0) {
                continue;
            }
            for (int updateCount : updateCounts) {
                result += updateCount;
            }
        }
        return result;
    }

    /**
     * 统一转换
     *
     * @param params array或者Collections
     * @return
     */
    private Iterable<?> toIterable(final Object params) {
        if (params == null) {
            return Collections.emptyList();
        }
        Iterable<?> paramIterable;
        if (params instanceof Iterable) {
            paramIterable = (Iterable<?>) params;
        } else if (params.getClass().isArray()) {
            Object[] array = (Object[]) params;
            paramIterable = Arrays.asList(array);
        } else {
            paramIterable = Collections.singletonList(params);
        }
        return paramIterable;
    }
}

拦截器编写

import org.apache.ibatis.executor.BatchExecutor;
import org.apache.ibatis.executor.Executor;
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.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.sql.SQLException;

@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class MyBatchInterceptor implements Interceptor {

    private Logger log = LoggerFactory.getLogger(MyBatchInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //check argument
        if (invocation.getArgs()[1] == null) {
            return invocation.proceed();
        }
        final MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        // 是否需要批处理标识
        if (!mappedStatement.getId().endsWith("Batch")) {
            return invocation.proceed();
        }
        // 若是批处理,则不做操作
        if (BatchExecutor.class.isAssignableFrom(invocation.getTarget().getClass())) {
            return invocation.proceed();
        }
        Executor executor = (Executor) invocation.getTarget();
        // 创建批处理对象
        final BatchExecutor batchExecutor = new BatchAdaptor(getConfiguration(executor), executor.getTransaction());
        try {
            return batchExecutor.update(mappedStatement, invocation.getArgs()[1]);
        } catch (SQLException e) {
            log.error("batch excute", e);
            batchExecutor.flushStatements(true);
            throw e;
        }
    }

    /**
     * 获取配置文件
     *
     * @param executor
     * @return
     */
    public Configuration getConfiguration(Executor executor) {
        Field configField = ReflectionUtils.findField(executor.getClass(), "configuration");
        if (configField == null) { // CachingExecutor
            configField = ReflectionUtils.findField(executor.getClass(), "delegate");
            if (!configField.isAccessible()) {
                configField.setAccessible(true);
            }
            executor = (Executor) ReflectionUtils.getField(configField, executor);
            configField = ReflectionUtils.findField(executor.getClass(), "configuration");
            if (!configField.isAccessible()) {
                configField.setAccessible(true);
            }
        }
        // 获取配置文件
        return (Configuration) ReflectionUtils.getField(configField, executor);
    }
}

拦截器使用

@Configuration
public class MybatisConfig {
    @Bean
    public MyBatchInterceptor myBatchInterceptor() {
        return new MyBatchInterceptor();
    }
}

@Mapper
public interface UserTableMapper {
    @Update("update user set name=#{name},msg=#{msg} where id=#{id}")
    int updateBatch(List<UserTable> userTableList);
}

总结&反思

  1. JDK动态代理充分利用,如Plugin#wrap构建动态代理,同时学习代理模式
  2. java反射机制,获取属性并构造类ReflectionUtils
  3. spring boot的自动装配,AutoConfiguration的start设计
  4. 使用mybatis拦截器可以做一些批量操作,针对执行的四种点进行拦截特殊处理(parameterHandler,statementHandler,resultSetHandler,execute)
  5. mybatis的四种执行体Executor
  6. mybatis-plus版本更多关于批处理的增强版等之类。

参看文献

【1】mybatis3描述和源码

猜你喜欢

转载自blog.csdn.net/soft_z1302/article/details/111150461
今日推荐