如何使用Mybatis的拦截器实现数据加密与解密

点击蓝色“程序猿DD”关注我哟

加个“星标”,不忘签到哦


如何使用Mybatis的拦截器实现数据加密与解密_第1张图片

转载自公众号:日拱一兵



关注我,回复口令获取可获取独家整理的学习资料:

001 :领取《Spring Boot基础教程》

002 :领取《Spring Cloud基础教程》



>>当当大促,160买400的书,点击进入<<

拦截器介绍

Mybatis Interceptor 在 Mybatis 中被当作 Plugin(插件),不知道为什么,但确实是在 org.apache.ibatis.plugin 包下面。

 
   

既然是拦截器,可以拦截哪些内容呢?试想一下...... 当程序写到持久层时,Mybatis 会 执行 指定 SQL 语句,并处理 请求参数  返回值。没错,Mybatis 拦截器可以帮助我们处理上述内容,请看官网的 Plugins 的片段, 内容不多

// 执行	
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)	
// 请求参数处理	
ParameterHandler (getParameterObject, setParameters)	
// 返回结果集处理	
ResultSetHandler (handleResultSets, handleOutputParameters)	
// SQL语句构建	
StatementHandler (prepare, parameterize, batch, update, query)

拦截器的使用

 
   

如果需要实现自定义的拦截器,只需要实现 org.apache.ibatis.plugin.Interceptor 接口,该接口有三个方法:

Object intercept(Invocation invocation) throws Throwable;	
	
Object plugin(Object target);	
	
void setProperties(Properties properties);
我们要实现数据加密,进入数据库的字段不能是真实的数据,但是返回来的数据要真实可用,所以我们需要针对 Parameter 和 ResultSet 两种类型处理,同时为了更灵活的使用,我们需要自定义注解

自定义注解

 
   

类注解,将注解放在实体类上

/**	
 * 需要加解密的类注解	
 */	
@Documented	
@Inherited	
@Target({ ElementType.TYPE })	
@Retention(RetentionPolicy.RUNTIME)	
public @interface EncryptDecryptClass {	
}
字段注解,将注解放在实体字段上
 
   
/**	
 * 加密字段注解	
 */	
@Documented	
@Inherited	
@Target({ ElementType.FIELD })	
@Retention(RetentionPolicy.RUNTIME)	
public @interface EncryptDecryptField {	
	
}
有了这两个注解,我们可以在我们可以标记我们要处理的实体和实体中的字段

自定义参数处理拦截器

 
   

参考官网,通过 @Intercepts  @Signature 的联合使用,指定 ParameterHandler.class 类型,同时通过 @Component注解注入到容器中,即可在设置参数的时候进行拦截,通过自定义接口 IEncryptDecrypt, 根据 Field 的各种类型自定义加密解密算法

@Intercepts({	
        @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),	
})	
@ConditionalOnProperty(value = "domain.encrypt", havingValue = "true")	
@Component	
@Slf4j	
public class ParammeterInterceptor  implements Interceptor {	
	
    @Autowired	
    private IEncryptDecrypt encryptDecrypt;	
	
    @Override	
    public Object intercept(Invocation invocation) throws Throwable {	
	
        log.info("拦截器ParamInterceptor");	
        //拦截 ParameterHandler 的 setParameters 方法 动态设置参数	
        if (invocation.getTarget() instanceof ParameterHandler) {	
            ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();	
            PreparedStatement ps = (PreparedStatement) invocation.getArgs()[0];	
	
            // 反射获取 BoundSql 对象,此对象包含生成的sql和sql的参数map映射	
            /*Field boundSqlField = parameterHandler.getClass().getDeclaredField("boundSql");	
            boundSqlField.setAccessible(true);	
            BoundSql boundSql = (BoundSql) boundSqlField.get(parameterHandler);*/	
	
            // 反射获取 参数对像	
            Field parameterField =	
                    parameterHandler.getClass().getDeclaredField("parameterObject");	
            parameterField.setAccessible(true);	
            Object parameterObject = parameterField.get(parameterHandler);	
            if (Objects.nonNull(parameterObject)){	
                Class parameterObjectClass = parameterObject.getClass();	
                EncryptDecryptClass encryptDecryptClass = AnnotationUtils.findAnnotation(parameterObjectClass, EncryptDecryptClass.class);	
                if (Objects.nonNull(encryptDecryptClass)){	
                    Field[] declaredFields = parameterObjectClass.getDeclaredFields();	
	
                    final Object encrypt = encryptDecrypt.encrypt(declaredFields, parameterObject);	
                }	
            }	
        }	
        return invocation.proceed();	
    }	
	
    @Override	
    public Object plugin(Object o) {	
        return Plugin.wrap(o, this);	
    }	
	
    @Override	
    public void setProperties(Properties properties) {	
	
    }	
}
同样新建结果集拦截器

结果集拦截器

 
   

与参数拦截器基本一样, 只不过类型指定为 ResultSetHandler.class

@Intercepts({	
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args={Statement.class})	
})	
@ConditionalOnProperty(value = "domain.decrypt", havingValue = "true")	
@Component	
@Slf4j	
public class ResultInterceptor implements Interceptor {	
	
    @Autowired	
    private IEncryptDecrypt encryptDecrypt;	
	
    @Override	
    public Object intercept(Invocation invocation) throws Throwable {	
        Object result = invocation.proceed();	
        if (Objects.isNull(result)){	
            return null;	
        }	
	
        if (result instanceof ArrayList) {	
            ArrayList resultList = (ArrayList) result;	
            if (CollectionUtils.isNotEmpty(resultList) && needToDecrypt(resultList.get(0))){	
                for (int i = 0; i < resultList.size(); i++) {	
                    encryptDecrypt.decrypt(resultList.get(i));	
                }	
            }	
        }else {	
            if (needToDecrypt(result)){	
                encryptDecrypt.decrypt(result);	
            }	
        }	
        return result;	
    }	
	
    public boolean needToDecrypt(Object object){	
        Class objectClass = object.getClass();	
        EncryptDecryptClass encryptDecryptClass = AnnotationUtils.findAnnotation(objectClass, EncryptDecryptClass.class);	
        if (Objects.nonNull(encryptDecryptClass)){	
            return true;	
        }	
        return false;	
    }	
	
    @Override	
    public Object plugin(Object target) {	
        return Plugin.wrap(target, this);	
    }	
	
    @Override	
    public void setProperties(Properties properties) {	
	
    }	
}

加密解密接口
 
   

IEncryptDecrypt 接口定义了 加密和解密两个方法:

public interface IEncryptDecrypt {	
	
    /**	
     * 加密方法	
     * @param declaredFields 反射bean成员变量	
     * @param parameterObject Mybatis入参	
     * @param 	
     * @return	
     */	
    public  T encrypt(Field[] declaredFields, T parameterObject) throws IllegalAccessException;	
	
	
    /**	
     * 解密方法	
     * @param result Mybatis 返回值,需要判断是否是ArrayList类型	
     * @param 	
     * @return	
     */	
    public  T decrypt(T result) throws IllegalAccessException;	
}

两个拦截器通过在 YAML 中配置属性,按条件注入,外加自定义加密解密算法,完成全局灵活的配置。

核心代码已上传至 Github Demo

问题彩蛋

也许应对当前的业务,看了该文章满足了当下需求,我们目前只看到了什么是 Mybatis 拦截器,怎样简单使用,拦截器的其他用法以及其他很多为什么都没有解决,关注公众号,回复“人迹罕至” 读完文章 「程序猿为什么要看源码」后 ,我不会满足眼前的这些基本应用,我会有诸多疑问,

  1. 我们日常写 CRUD 的业务,为什么 Executor 中只有 R(query) 和 U(update), 那么C(insert) 和 D(delete) 怎样处理的?

  2. 自定义拦截器是以什么方式被执行的,执行顺序是什么?

  3. 分页也是 Mybatis 拦截器的一种,带有分页的框架是怎样使用拦截器的呢?如 Mybatis Plus, PageHelper

  4. 虽然重写了 Inteceptor 接口的 public void setProperties(Properties properties) 方法,但是并没有写什么业务逻辑,这个方法能怎样使用?

  5. ......

后续文章也会通过读源码的方式逐步解析这些问题,当然你有相关问题也可以留言交流讨论

提高效率工具

依旧推荐在写文章时用到的高效工具,后续相关工具也会在文章中陆续更新,请持续关注

MyBatis Log Plugin

MyBatis Log Plugin 是 Intelligj IDEA 的一个插件,用来从 Mybatis 输出的 log 中提取出当前调用的 SQL 语句,并将参数封装在 SQL 语句中组成完整的 SQL,这样,当我们调试的时候更加清晰方便,可以轻松定位是否 SQL 又问题



推荐阅读

  • Maven 虐我千百遍,我待 Maven 如初恋

  • 初探性能优化:2个月到4小时的性能提升

  • MySQL跑在CentOS 6 和 7上的性能比较

  • Spring Boot 配置文件中的花样,看这一篇足矣!

  • Mybatis的工作原理,你了解过吗?



活动介绍:自律到极致-人生才精致:第8期

活动奖励:《小灰的算法之旅》* 10

扫描下面二维码签到参与

如何使用Mybatis的拦截器实现数据加密与解密_第2张图片

关注我,加个星标,不忘签到哦~


2019

与大家聊聊技术人的斜杠生活

640?wx_fmt=png


⬇️⬇️点一点小惊喜在等你

你可能感兴趣的:(如何使用Mybatis的拦截器实现数据加密与解密)