Home Tags Posts tagged with "自定义"

自定义

一.背景
在很多业务场景下我们需要去拦截sql,达到不入侵原有代码业务处理一些东西,比如:分页操作,数据权限过滤操作,SQL执行时间性能监控等等,这里我们就可以用到Mybatis的拦截器Interceptor

二.Mybatis核心对象介绍
从MyBatis代码实现的角度来看,MyBatis的主要的核心部件有以下几个:

Configuration 初始化基础配置,比如MyBatis的别名等,一些重要的类型对象,如,插件,映射器,ObjectFactory和typeHandler对象,MyBatis所有的配置信息都维持在Configuration对象之中
SqlSessionFactory  SqlSession工厂
SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
StatementHandler   封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
ParameterHandler   负责对用户传递的参数转换成JDBC Statement 所需要的参数,
ResultSetHandler    负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
TypeHandler          负责java数据类型和jdbc数据类型之间的映射和转换
MappedStatement   MappedStatement维护了一条<select|update|delete|insert>节点的封装,
SqlSource            负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
BoundSql 表示动态生成的SQL语句以及相应的参数信息

 

Mybatis拦截器只能拦截四类对象,分别为:Executor、ParameterHandler、StatementHandler、ResultSetHandler,而SQL数据库的操作都是从Executor开始,因此要记录Mybatis数据库操作的耗时,需要拦截Executor类,代码实现如下:

复制代码
/**
 * 数据库操作性能拦截器,记录耗时
 * @Intercepts定义Signature数组,因此可以拦截多个,但是只能拦截类型为:
 *         Executor
 *         ParameterHandler
 *         StatementHandler
 *         ResultSetHandler
 * */
@Intercepts(value = { 
        @Signature (type=Executor.class,
                method="update",
                args={MappedStatement.class,Object.class}),
        @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})})
public class TimerInterceptor implements Interceptor {

    private static final Logger logger = Logger.getLogger(TimerInterceptor.class);
    
    /**
     * 实现拦截的地方
     * */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object target = invocation.getTarget();
        Object result = null;
        if (target instanceof Executor) {
            long start = System.currentTimeMillis();
            Method method = invocation.getMethod();
            /**执行方法*/
            result = invocation.proceed();
            long end = System.currentTimeMillis();
            logger.info("[TimerInterceptor] execute [" + method.getName() + "] cost [" + (end - start) + "] ms");
        }
        return result;
    }

    /**
     * Plugin.wrap生成拦截代理对象
     * */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }

}
复制代码

完成上面的拦截后,需要将该类在Mybatis配置文件中声明,如下:

<plugins><!-- SQL性能拦截器 --><plugin interceptor="com.quar.interceptor.TimerInterceptor" /></plugins>

https://www.bilibili.com/video/BV1LK4y1S7v2?p=4

 

项目地址:Mybatis-Study-04

代码:Desensitizer:

package plugins;

import java.util.function.Function;

public interface Desensitizer extends Function<String,String> {
}
Tuomin注解:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Tuomin {
    TuominStrategy strategy();
}
Tuomin策略(枚举):
public enum TuominStrategy {
    USERNAME(s->s.replaceAll("(\\S)\\S(\\S*)","$1*$2"));
    private  final Desensitizer desensitizer;
    TuominStrategy(Desensitizer d) {
        this.desensitizer = d;
    }

    public Desensitizer getDesensitizer() {
        return desensitizer;
    }
}

Tuomin逻辑:
package plugins;

import org.apache.ibatis.executor.resultset.ResultSetHandler;
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.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.List;
import java.util.stream.Stream;


@Intercepts(@Signature(type= ResultSetHandler.class,method = "handleResultSets",args = Statement.class))
public class TuominPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        List<Object> records = (List<Object>) invocation.proceed();
        records.forEach(this::tuomin);
        return records;
    }
    private void tuomin(Object source){
        Class<?> sourceClass = source.getClass();
        MetaObject metaObject = SystemMetaObject.forObject(source);
        Stream.of(sourceClass.getDeclaredFields()).filter(field -> field.isAnnotationPresent(Tuomin.class)).forEach(field -> doTuomin(metaObject,field));

    }

    private void doTuomin(MetaObject metaObject, Field field) {
        String name = field.getName();
        Object value = metaObject.getValue(name);
        if(String.class == metaObject.getGetterType(name) && value!=null){
            Tuomin tuomin = field.getAnnotation(Tuomin.class);
            TuominStrategy type = tuomin.strategy();
            Object o = type.getDesensitizer().apply((String)value);
            metaObject.setValue(name,o);
        }

    }
}

实体类:
@Getter
//实体类
public class User {
    private int id;
    @Tuomin(strategy = TuominStrategy.USERNAME)
    private String name;
    private String pwd;

    public User(int id, String name, String pwd) {
        this.id = id;
        this.name = name;
        this.pwd = pwd;
    }
}

mybatis核心配置文件:
<plugins>
    <plugin interceptor="plugins.TuominPlugin"></plugin>
</plugins>


插件原理及说明:

版权声明:本文为CSDN博主「湖畔微风」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/hupanfeng/article/details/9247379