Mybatis 之 插件


插件编写(plugin) 

步骤:

1. 编写Interceptor的实现类:

MyFirstPlug

2. 使用@Intercepts注解完成插件签名

3. 将写好的插件注册到全局配置文件中

package com.feng.config;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;

import java.util.Properties;

/**
 * @Desc:完成插件签名:
 *        告诉mybatis当前插件用来拦截哪个对象的哪个方法
 * @Param: Sinature type:拦截哪个对象(四大对象)
 *                  method:对象的哪个方法
 *                  args: 当前方法的参数列表
 */
@Intercepts({
        @Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class)
})
public class MyFirstPlug implements Interceptor {

    /**
     * 拦截目标对象的目标方法的执行
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
//TODO ... 动态修改mybatis运行流程;比如sql分页、修改sql使用的参数。。。 //执行目标方法: parameterrize Object proceed = invocation.proceed(); //返回执行后的返回值
//TODO ... return proceed; }
/** * 包装目标对象,为目标对象创建一个代理对象 * @param target * @return */ @Override public Object plugin(Object target) { //借助Plugin的wrap方法来使用当前Interceptor包装目标对象 Object wrap = Plugin.wrap(target, this); //返回为当前target创建的动态代理对象 return wrap; } /** * 将插件注册时的property属性设置进来 * @param properties */ @Override public void setProperties(Properties properties) { System.out.println("插件配置信息:"+properties); String username = (String) properties.get("username"); String password = (String) properties.get("password"); } }

 mybatis-config.xml


   
    
        
            username" value="zhangsan"/>
            
        
    

插件扩展:

插件拦截的场景:

场景一:修改sql使用的参数
package com.feng.config;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

import java.util.Properties;

/**
 * @Desc:完成插件签名:
 *        告诉mybatis当前插件用来拦截哪个对象的哪个方法
 * @Param: Sinature type:拦截哪个对象(四大对象)
 *                  method:对象的哪个方法
 *                  args: 当前方法的参数列表
 */
@Intercepts({
        @Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class)
})
public class MyFirstPlug implements Interceptor {

    /**
     * 拦截目标对象的目标方法的执行
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("intercept:"+invocation.getTarget());
        //动态的改变一下sql的参数:
        Object target = invocation.getTarget();
        System.out.println("当前拦截的对象:"+target);
        //拿到:StatementHandler ==>ParameterHandler ==>parameterObject
        //拿到target的元数据
        MetaObject metaObject = SystemMetaObject.forObject(target);
        Object value = metaObject.getValue("parameterHandler.parameterObject");
        System.out.println("sql语句的参数:"+value);
        //修改sql语句要用的参数
        metaObject.setValue("parameterHandler.parameterObject", 12);
        //返回执行后的返回值
        Object proceed = invocation.proceed();
        return proceed;
    }

    /**
     * 包装目标对象,为目标对象创建一个代理对象
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        //借助Plugin的wrap方法来使用当前Interceptor包装目标对象
        Object wrap = Plugin.wrap(target, this);
        //返回为当前target创建的动态代理对象
        return wrap;
    }

    /**
     * 将插件注册时的property属性设置进来
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
        System.out.println("插件配置信息:"+properties);
        String username = (String) properties.get("username");
        String password = (String) properties.get("password");
    }
}

场景二:PageHelper插件进行分页

a . 引入依赖

在pom.xml文件添加如下依赖:



    com.github.pagehelper
    pagehelper
    4.2.0

b. 在Mybatis配置xml中配置拦截器插件

在mybatis-config.xml文件中添加插件后的内容如下:

<?xml version="1.0" encoding="UTF-8"?>  
  
    

    
    
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
    


 c. 默认jar包中的实现类

package com.github.pagehelper;

import com.github.pagehelper.util.MSUtils;
import com.github.pagehelper.util.StringUtil;
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.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * 最简单的分页插件拦截器
 */
@SuppressWarnings("rawtypes")
@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class PaginationInterceptor implements Interceptor {
    protected Dialect dialect;
    protected Field additionalParametersField;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //获取拦截方法的参数
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameterObject = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
        List resultList;
        //调用方法判断是否需要进行分页,如果不需要,直接返回结果
        if (!dialect.skip(ms, parameterObject, rowBounds)) {
            ResultHandler resultHandler = (ResultHandler) args[3];
            //当前的目标对象
            Executor executor = (Executor) invocation.getTarget();
            BoundSql boundSql = ms.getBoundSql(parameterObject);
            //反射获取动态参数
            Map additionalParameters = (Map) additionalParametersField.get(boundSql);
            //判断是否需要进行 count 查询
            if (dialect.beforeCount(ms, parameterObject, rowBounds)) {
                //创建 count 查询的缓存 key
                CacheKey countKey = executor.createCacheKey(ms, parameterObject, RowBounds.DEFAULT, boundSql);
                countKey.update("_Count");
                //根据当前的 ms 创建一个返回值为 Long 类型的 ms
                MappedStatement countMs = MSUtils.newCountMappedStatement(ms);
                //调用方言获取 count sql
                String countSql = dialect.getCountSql(ms, boundSql, parameterObject, rowBounds, countKey);
                BoundSql countBoundSql = new BoundSql(ms.getConfiguration(), countSql, boundSql.getParameterMappings(), parameterObject);
                //当使用动态 SQL 时,可能会产生临时的参数,这些参数需要手动设置到新的 BoundSql 中
                for (String key : additionalParameters.keySet()) {
                    countBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
                }
                //执行 count 查询
                Object countResultList = executor.query(countMs, parameterObject, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);
                Long count = (Long) ((List) countResultList).get(0);
                //处理查询总数
                dialect.afterCount(count, parameterObject, rowBounds);
                if (count == 0L) {
                    //当查询总数为 0 时,直接返回空的结果
                    return dialect.afterPage(new ArrayList(), parameterObject, rowBounds);
                }
            }
            //判断是否需要进行分页查询
            if (dialect.beforePage(ms, parameterObject, rowBounds)) {
                //生成分页的缓存 key
                CacheKey pageKey = executor.createCacheKey(ms, parameterObject, rowBounds, boundSql);
                //处理参数对象
                parameterObject = dialect.processParameterObject(ms, parameterObject, boundSql, pageKey);
                //调用方言获取分页 sql
                String pageSql = dialect.getPageSql(ms, boundSql, parameterObject, rowBounds, pageKey);
                BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameterObject);
                //设置动态参数
                for (String key : additionalParameters.keySet()) {
                    pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
                }
                //执行分页查询
                resultList = executor.query(ms, parameterObject, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
            } else {
                resultList = new ArrayList();
            }
        } else {
            args[2] = RowBounds.DEFAULT;
            resultList = (List) invocation.proceed();
        }
        //返回默认查询
        return dialect.afterPage(resultList, parameterObject, rowBounds);
    }

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

    @Override
    public void setProperties(Properties properties) {
        String dialectClassStr = properties.getProperty("dialect");
        if (StringUtil.isEmpty(dialectClassStr)) {
            throw new RuntimeException("使用 PaginationInterceptor 分页插件时,必须设置 dialect 属性");
        }
        try {
            Class dialectClass = Class.forName(dialectClassStr);
            dialect = (Dialect) dialectClass.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("初始化 dialect [" + dialectClassStr + "]时出错:" + e.getMessage());
        }
        dialect.setProperties(properties);
        try {
            //反射获取 BoundSql 中的 additionalParameters 属性
            additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters");
            additionalParametersField.setAccessible(true);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }
}

 d.使用page

 场景三:批量操作

与Spring整合进行批量操作

    
    
        
        
        
        
        
    
    
    
        
        executorType" ref="BATCH">
    

java中使用:
@Autowired
     private SqlSession sqlSession;

 场景四:存储过程

 场景五:typeHandler处理枚举

可以通过自定义TypeHandler的形式来在设置参数或取出结果集的时候自定义参数封装策略

步骤:

1. 实现TypeHandler接口或继承BaseTypeHandler

2. 使用@MappedTypes定义处理的java类型

3. 在自定义结果集标签或参数处理的时候声明使用自定义TypeHandler进行处理

    或在全局配置TypeHandler要处理的javaType

多插件编写:


   
    
        MyFirstPlug">
            
            
        
        MySecondPlug">