手写mybatis插件优雅实现字段加密

mybatis优雅实现字段加密

在很多时候,我们都需要字段加密。比如邮箱,密码,电话号码等。通常的项目可能会在插入数据前手动加密,今天给大家带来一个更优雅的做法------通过注解实现。

mybatis的接入点

Mybatis本质上是对JDBC执行流程的封装。简要概括下Mybatis这几个可被代理类的职能。

  • 【 **Executor**】 : 真正执行SQL语句的对象,调用sqlSession的方法时,本质上都是调用executor的方法,还负责获取connection,创建StatementHandler。
  • 【 **StatementHandler**】 : 创建并持有ParameterHandler和ResultSetHandler对象,操作JDBC的statement与进行数据库操作。
  • 【 **ParameterHandler**】: 处理入参,将Java方法上的参数设置到被执行语句中。
  • 【 **ResultSetHandler**】: 处理SQL语句的执行结果,将返回值转换为Java对象。

如何接入自己写的插件

java 复制代码
@Intercepts({@Signature(type = 代理的插件类, method = 类中的方法名, args = {方法参数})})
public class MyInterceptor implements Interceptor {
	Object intercept(Invocation invocation) throws Throwable{
        //invocation就是你要代理的方法。
        //在这里写主要的逻辑操作
        //todo
    }

	default Object plugin(Object target) {
        //是用来生成代理的
        //一般不重写
    	return Plugin.wrap(target, this);
  	}

  	default void setProperties(Properties properties) {
        // NOP
  	}
}

talk is cheap,show me the code

加密

java 复制代码
@Intercepts({@Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class})})
public class MybatisEncryptInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        return invocation;
    }
    @Override
    public Object plugin(Object target) {
        if (target instanceof ParameterHandler) {
            ParameterHandler parameterHandler = (ParameterHandler) target;
            Object parameterObject = parameterHandler.getParameterObject();
            // 进行加密操作
        }
        return target;
    }
    @Override
    public void setProperties(Properties properties) {
    }
}

选择了在ParameterHandler#setParameters方法进行加密。这时就有人会问了为什么不在intercept方法改变参数呢?

我们看一下Invocation类

java 复制代码
public class Invocation {
  private final Object target;
  private final Method method;
  private final Object[] args;
  ...
}

我们注意到三个变量都是用fianl修饰的,我们不能改变他们的值。所以我们在plugin方法进行加密操作。

解密

有加密就有解密

java 复制代码
@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
public class MybatisDecryptInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取执行mysql执行结果
        Object result = invocation.proceed();
        if (result == null) {
            return null;
        }
        //解密result
        return result;
    }
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}

选择了在ResultSetHandler#handleResultSets方法进行加密,ibatis起名确实不错,一看方法名就知道干什么了。

取消加解密

有些特定的场合我们不希望加解密,但是大部分时候又需要加解密,这种情况的话我们就用不了了吗?难道只能手动编码吗qwq。

我以pagehelper为灵感写了一个EncryptHelper。

让我们看看核心逻辑

java 复制代码
@Intercepts({@Signature(type = Executor.class,method = "flushStatements",args = {})})
public class MybatisClearInterceptor implements Interceptor {
    public Object intercept(Invocation invocation) throws Throwable {
        Object result = invocation.proceed();
        if(EncryptHelper.hasSituation()){
            log.info("EncryptClear!!!");
        }
        EncryptHelper.clearEncrypt();
        return result;
    }
    @Override
    public Object plugin(Object target) {
        // 包装目标对象,创建代理对象
        return Plugin.wrap(target, this);
    }
    @Override
    public void setProperties(Properties properties) {
    }
}

在这里我们每次执行sql后就调用 EncryptHelper.clearEncrypt();方法。在EncryptHelper中存放ThreadLocal<EncryptSituation>,加解密时可以根据EncryptHelper中的LOCAL_ENCRYPT判断是否加解密。在执行完一次sql后自动清除。当然你也可以手动管理不启用这个ClearInterceptor。

java 复制代码
public class EncryptHelper implements AutoCloseable {
    private static final ThreadLocal<EncryptSituation> LOCAL_ENCRYPT=new ThreadLocal<>();
	//操作
}

总结

  1. 其中还有一些问题,比如MybatisClearInterceptor会和pagehelper不兼容因为分页查询前会查询总数据量这里会有两个sql,需要更加复杂的判断。
  2. 有些同学会问为什么不在MybatisEncryptInterceptor和MybatisDecryptInterceptor里管理EncryptHelper,而是多写一个MybatisClearInterceptor?有这种疑问的同学可以自己试一试,会发现神奇的事情,我也是试了好久。或者在评论区告诉我,人多的话我写一篇。

关注湫湫喵~

_qiu

相关推荐
郭小李13 小时前
mybatis 动态 sql
java·sql·mybatis
知守观13 小时前
技术考古:iBATIS 2.3.4 时代的持久层,是如何用 Map 玩转 307 个存储过程的?
java·后端·mybatis
captain37614 小时前
MyBatis Plus(1)
java·服务器·mybatis
代码不停19 小时前
MyBatis进阶
spring boot·mybatis
captain3761 天前
MyBatis(入门5)其他查询操作
java·数据库·spring boot·mybatis
captain3761 天前
MyBatis(进阶1)
mybatis
郭小李2 天前
mybatis的 Mapper.xml语法
xml·java·mybatis
m0_639906942 天前
MyBatis 实现分页查询|逻辑分页、物理分页、PageHelper 原理
mybatis
captain3762 天前
MyBatis操作数据库(入门2)
java·数据库·spring boot·后端·spring·mybatis
正儿八经的少年4 天前
MyBatis 一对一、一对多 SQL
数据库·sql·mybatis