篇一
基于框架中使用的MyBatis-Plus整理了两个方法,一种是MyBatis-Plus自带的TypeHandler,另一种是基于MyBatis的Intercept拦截器。方法一配置简单使用麻烦,方法二配置麻烦使用简单,自己斟酌使用即可。当然方法二适用只使用MyBatis + Springboot的架构。
MyBatis-Plus版本:3.4.0
一、基于MyBatis-Plus自定义类型处理器(TypeHandler)的方法
1、创建TypeHandler
//AES 是工具方法类,按加密需求设置
public class AESEncryptHandler extends BaseTypeHandler {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, AES.encrypt((String)parameter));
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
String columnValue = rs.getString(columnName);
return AES.decrypt(columnValue);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String columnValue = rs.getString(columnIndex);
return AES.decrypt(columnValue);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
String columnValue = cs.getString(columnIndex);
return AES.decrypt(columnValue);
}
}
2、开启MyBatis-Plus的扫描
在配置文件中加入配置
mybatis-plus.type-handlers-package=com.demo.mybatisplusintercept.intercept
配置说明(来源官方文档):
PS:实测不加这个配置也可以使用,未深入,原因不明
3、实体类中加入注解
类上加入注解
[@TableName(autoResultMap ](/TableName(autoResultMap ) = true)
字段上加入注解
[@TableField(typeHandler ](/TableField(typeHandler ) = AESEncryptHandler.class)
4、查询中指定TypeHandler
使用MyBatis-Plus默认方法查询时无需指定,但是当需要使用sql查询的时候,需要指定TypeHandler
注解
XML
二、基于MyBatis的方法(拦截器)
在官方示例(https://mybatis.org/mybatis-3/zh/configuration.html#plugins)中,是直接监听Executor,但是通过文章介绍可以看到,ParameterHandler和ResultSetHandler分别操作入参和查询,所以在此处操作字段的加解密应该更为合理。
1、自定义注解
类注解
@Documented
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptClass{
}
字段注解
@Documented
@Inherited
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptField {
}
2、工具类
package com.demo.mybatisplusintercept.intercept;
import com.demo.mybatisplusintercept.annotation.EncryptDecryptField;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.Objects;
/**
* Domain数据加密工具类
*
*/
public class EncryptDecryptUtils {
/**
* 多field加密方法
*
* @param declaredFields
* @param parameterObject
* @param <T>
* @return
* @throws IllegalAccessException
*/
public static <T> T encrypt(Field[] declaredFields, T parameterObject) throws IllegalAccessException {
for (Field field : declaredFields) {
EncryptDecryptField annotation = field.getAnnotation(EncryptDecryptField.class);
if (Objects.isNull(annotation)) {
continue;
}
encrypt(field, parameterObject);
}
return parameterObject;
}
/**
* 单个field加密方法
*
* @param field
* @param parameterObject
* @param <T>
* @return
* @throws IllegalAccessException
*/
public static <T> T encrypt(Field field, T parameterObject) throws IllegalAccessException {
field.setAccessible(true);
Object object = field.get(parameterObject);
if (object instanceof BigDecimal) {
BigDecimal value = (BigDecimal) object;
long longValue = value.movePointRight(4).subtract(BigDecimal.valueOf(Integer.MAX_VALUE >> 3)).longValue();
field.set(parameterObject, BigDecimal.valueOf(longValue));
} else if (object instanceof Integer) {
} else if (object instanceof Long) {
} else if (object instanceof String) {
//定制String类型的加密算法
String value = (String) object;
field.set(parameterObject, AES.encrypt(value));
}
return parameterObject;
}
/**
* 解密方法
*
* @param result
* @param <T>
* @return
* @throws IllegalAccessException
*/
public static <T> T decrypt(T result) throws IllegalAccessException {
Class<?> parameterObjectClass = result.getClass();
Field[] declaredFields = parameterObjectClass.getDeclaredFields();
decrypt(declaredFields, result);
return result;
}
/**
* 多个field解密方法
*
* @param declaredFields
* @param result
* @throws IllegalAccessException
*/
public static void decrypt(Field[] declaredFields, Object result) throws IllegalAccessException {
for (Field field : declaredFields) {
EncryptDecryptField annotation = field.getAnnotation(EncryptDecryptField.class);
if (Objects.isNull(annotation)) {
continue;
}
decrypt(field, result);
}
}
/**
* 单个field解密方法
*
* @param field
* @param result
* @throws IllegalAccessException
*/
public static void decrypt(Field field, Object result) throws IllegalAccessException {
field.setAccessible(true);
Object object = field.get(result);
if (object instanceof BigDecimal) {
BigDecimal value = (BigDecimal) object;
double doubleValue = value.add(BigDecimal.valueOf(Integer.MAX_VALUE >> 3)).movePointLeft(4).doubleValue();
field.set(result, BigDecimal.valueOf(doubleValue));
} else if (object instanceof Integer) {
} else if (object instanceof Long) {
} else if (object instanceof String) {
//定制String类型的解密算法
String value = (String) object;
field.set(result, AES.decrypt(value));
}
}
}
3、两个拦截器
@Intercepts({
@Signature(type = ParameterHandler.class,method = "setParameters",args = PreparedStatement.class)
})
@ConditionalOnProperty(value = "domain.encrypt",havingValue = "true")
@Slf4j
public class MybatisParameterIntercept implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
//拦截 ParameterHandler 的 setParameters 方法 动态设置参数
if (invocation.getTarget() instanceof ParameterHandler) {
ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
// 反射获取 参数对象
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 = EncryptDecryptUtils.encrypt(declaredFields, parameterObject);
}
}
}
return invocation.proceed();
}
}
@Intercepts({
@Signature(type = ResultSetHandler.class,method = "handleResultSets",args = Statement.class)
})
@ConditionalOnProperty(value = "domain.encrypt",havingValue = "true")
@Component
@Slf4j
public class MybatisResultSetIntercept implements Interceptor {
@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++) {
EncryptDecryptUtils.decrypt(resultList.get(i));
}
}
}else {
if (needToDecrypt(result)){
EncryptDecryptUtils.decrypt(result);
}
}
return result;
}
private boolean needToDecrypt(Object object){
Class<?> objectClass = object.getClass();
EncryptDecryptClass encryptDecryptClass = AnnotationUtils.findAnnotation(objectClass, EncryptDecryptClass.class);
if (Objects.nonNull(encryptDecryptClass)){
return true;
}
return false;
}
}
4、注册拦截器
/**
* 注册mybatis拦截器
*/
@Configuration
@MapperScan("com.demo.mybatisplusintercept.dao")
public class MyBatisPlusConfig {
@Autowired
private ApplicationContext applicationContext;
/**
* 注册MyBatis拦截器
* @param sqlSessionFactory
* @return
*/
@Bean
public String myInterceptor(SqlSessionFactory sqlSessionFactory){
sqlSessionFactory.getConfiguration().addInterceptor(parameterIntercept());
sqlSessionFactory.getConfiguration().addInterceptor(resultSetIntercept());
return "myInterceptor";
}
public MybatisParameterIntercept parameterIntercept(){
return applicationContext.getAutowireCapableBeanFactory().createBean(MybatisParameterIntercept.class);
}
public MybatisResultSetIntercept resultSetIntercept(){
return applicationContext.getAutowireCapableBeanFactory().createBean(MybatisResultSetIntercept.class);
}
}
5、实体类添加注解
@Data
@TableName(value = "user",autoResultMap = true)
@EncryptDecryptClass
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
private Long id;
private String name;
// @TableField(typeHandler = AESEncryptHandler.class)
@EncryptDecryptField
private String mobile;
// @TableField(typeHandler = AESEncryptHandler.class)
@EncryptDecryptField
private String idcard;
}
篇二、修复Mybatis-Plus自带方法不兼容问题
不生效原因
首先准备两个update
方法进行对比,第一个update
方法中**this**
指代的是继承框架方法的**ServiceImpl**
,第二个update2
方法中直接走到数据交互层操作sql去了。
调用第一个**update**
:
可以看到框架的方法进来的时候会出现两个入参,并且神奇的是堆栈地址是同一个。
调用第二个**update2**
:
可以看到parameterObject对象直接就是入参对象。
写到这的时候,突然想到是不是使用@Param注解修饰入参也会这样,尝试了一下,果然:
在篇一的代码中,是直接从parameterObject对象中获取字段是否存在EncryptDecryptClass.class注解,很显然没有,然后在随后的if判断中就直接略过,这边优化一下就行了。
优化
先放代码:
...
public class MybatisParameterIntercept implements Interceptor {
...
//获取参数对象方法名称
private final static String getParameterObjectMethod = "getParameterObject";
@Override
public Object intercept(Invocation invocation) throws Throwable {
//拦截 ParameterHandler 的 setParameters 方法 动态设置参数
if (invocation.getTarget() instanceof ParameterHandler) {
ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
// 反射获取 参数方法
Class<? extends ParameterHandler> aClass = parameterHandler.getClass();
Method getParameterObject = aClass.getDeclaredMethod(getParameterObjectMethod);
Object parameterObject = getParameterObject.invoke(parameterHandler);
if (Objects.nonNull(parameterObject)){
if (parameterObject instanceof MapperMethod.ParamMap){ //mybatis-plus 框架默认多参数
MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) parameterObject;
//避免相同的key重复执行,使用set对象记录堆栈地址
HashSet<Integer> stackSet = new HashSet<>();
for (Object key : paramMap.keySet()) {
//需要使用hashcode判断,equals()方法可能被重写。
if (stackSet.add(System.identityHashCode(paramMap.get(key)))) {
EncryptDecryptUtils.doEncrypt(paramMap.get(key));
}
}
} else {
EncryptDecryptUtils.doEncrypt(parameterObject);
}
}
}
return invocation.proceed();
}
...
}
变动的点:
- 获取parameterObject的路子由直接获取属性到调用对象上的
getParameterObject
方法获取属性。解释一下,实际开发的时候因为版本跟demo不一样,所以官方把属性封装起来了,只能通过getParameterObject
方法获取值。 - 增加判断
**parameterObject instanceof MapperMethod.ParamMap**
。初步观察parameterObject对象的类别为MapperMethod.ParamMap,直接判断进入不同的支线。获取到的ParamMap
很明显是个Map结构对象啦。 - 从之前的截图中发现两个对象的堆栈地址是一致的,所以用stackSet对对象进行去重,防止重复加密,会加密两次哦。
- 原本进行加密的代码抽离到了
doEncrypt
方法中。