前言:
接到需求,要求对敏感信息在数据库加密存储。
这种情况类似于密码在数据库的存在方式,但又不完全一样,因为密码存储使用摘要算法完全足够了,但是很多敏感信息如身份证号等不但需要加密存储,还要求能解密展示原文。所以首先要排除摘要算法,另外因为非对称加密后的密文不一致,不利于操作数据库语句的检索,所以博主使用国密SM4对称加密进行数据的加密。
正文
博主之前文章介绍过国密算法的具体使用方式,这里不再介绍算法的使用,只介绍自定义 javaType 的具体配置方式:
首先创建别名类,用以对应 javaType
import org.apache.ibatis.type.Alias;
/**
* mybatis 数据操作加解密操作工具注解
*
* @author gbx
*/
@Alias("idCardSecret")
public class IdCardSecret {
}
其次创建自定义handler,用以实现具体操作
import com.gbx.physical.common.utils.encrypt.EncryptDefaultGbxUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* mybatis 数据读写加解密 拦截工具
*
* @author gbx
*/
@Slf4j
@MappedTypes(IdCardSecret.class)
public class MyEncrTypeHandler extends BaseTypeHandler<String> {
/**
* 执行写操作时对指定字段 SM4 加密
*
* @throws SQLException 异常
*/
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
try {
String encryptStr = EncryptDefaultGbxUtils.getSm4DataHex(parameter);//加密可参考我之前博文实现
ps.setString(i, encryptStr);
} catch (Exception e) {
ps.setString(i, parameter);
log.error("mybatis加密参数异常,i:" + i + ",parameter:" + parameter);
}
}
/**
* 执行读操作时对指定字段 SM4 解密
*
* @throws SQLException 异常
*/
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
String columnValue = rs.getString(columnName);
try {
columnValue = EncryptDefaultGbxUtils.getSm4Data(columnValue);//解密可参考我之前博文实现
} catch (Exception e) {
log.error("mybatis解密参数异常,columnName:" + columnName + ",columnValue:" + columnValue);
}
return columnValue;
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return null;
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return null;
}
}
把对应的包扫描路径配置在yml文件中:
# MyBatis
mybatis:
# 搜索指定包别名
typeAliasesPackage: com.gbx.**.domain
typeHandlersPackage: com.gbx.physical.common.handler.mybatis.domain
# 配置mapper的扫描,找到所有的mapper.xml映射文件
mapperLocations: classpath*:mapper/**/*Mapper.xml
# 加载全局的配置文件
configLocation: classpath:mybatis/mybatis-config.xml
配置好路径后在mybatis配置类中使其生效:
import com.gbx.physical.common.utils.StringUtils;
import org.apache.ibatis.io.VFS;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
/**
* Mybatis支持*匹配扫描包
*
* @author ruoyi
*/
@Configuration
public class MyBatisConfig {
@Autowired
private Environment env;
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
public static String setTypeAliasesPackage(String typeAliasesPackage) {
ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
List<String> allResult = new ArrayList<String>();
try {
for (String aliasesPackage : typeAliasesPackage.split(",")) {
List<String> result = new ArrayList<String>();
aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN;
Resource[] resources = resolver.getResources(aliasesPackage);
if (resources != null && resources.length > 0) {
MetadataReader metadataReader = null;
for (Resource resource : resources) {
if (resource.isReadable()) {
metadataReader = metadataReaderFactory.getMetadataReader(resource);
try {
result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
if (result.size() > 0) {
HashSet<String> hashResult = new HashSet<String>(result);
allResult.addAll(hashResult);
}
}
if (allResult.size() > 0) {
typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0]));
} else {
throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包");
}
} catch (IOException e) {
e.printStackTrace();
}
return typeAliasesPackage;
}
public Resource[] resolveMapperLocations(String[] mapperLocations) {
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
List<Resource> resources = new ArrayList<Resource>();
if (mapperLocations != null) {
for (String mapperLocation : mapperLocations) {
try {
Resource[] mappers = resourceResolver.getResources(mapperLocation);
resources.addAll(Arrays.asList(mappers));
} catch (IOException e) {
// ignore
}
}
}
return resources.toArray(new Resource[resources.size()]);
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
String typeHandlersPackage = env.getProperty("mybatis.typeHandlersPackage");
String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
String mapperLocations = env.getProperty("mybatis.mapperLocations");
String configLocation = env.getProperty("mybatis.configLocation");
typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
VFS.addImplClass(SpringBootVFS.class);
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setTypeHandlersPackage(typeHandlersPackage);
sessionFactory.setDataSource(dataSource);
sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
return sessionFactory.getObject();
}
}
现在就可以在具体的mapper.xml文件中使用了
PS: 博主使用的是若依框架,有需要的可以做参考