mybatis类型转换器如何实现数据加解密
作者:userwyh
mybatis类型转换器数据加解密
背景需求
对表中的某些字段进行加密,查询之后对字段进行解密,业务代码无需关注加解密环节。
mybatis 拦截器 vs 类型转换器
mybatis的拦截器能实现上面的需求,但是会拦截所有的sql语句,如果项目中只是部分sql涉及到加解密操作,还是比较牺牲大局的。实现起来也比较麻烦,特别是单参数查询的时候,比如参数名称name,此时需要对name进行加密就相对困难。如果是多参数,因为底层会将参数封装成Map,可以根据是否含有指定的key进行加密。
mybatis类型转换器也可以实现上面的需求,方案为修改xml里面的配置,指定类型转换器,并在转换器里面实现数据的加解密。
mybatis 类型转换器
java有java的数据类型,数据库有数据库的数据类型,那么我们在往数据库中插入数据的时候是如何把java类型当做数据库类型插入数据库,在从数据库读取数据的时候又是如何把数据库类型当做java类型来处理呢?这中间必然要经过一个类型转换。
mybatis提供了TypeHandler接口,抽象类BaseTypeHandler实现TypeHandler接口,String,Integer,Long等常见的数据类型转换都是基于继承BaseTypeHandler实现,BaseTypeHandler提供了基本的判空等操作。因此我们可以继承BaseTypeHandler或者实现TypeHandler接口完成自定义类型转换,一般建议继承BaseTypeHandler来编码。
数据加解密类型转换
假设加解密采用AES算法,此处对加解密结果做一下缓存,因为一些数据是一样的,如果每次都去加密,也是会消耗一定的时间
public class DataSecurityHandler extends BaseTypeHandler { public static final LoadingCache<String, String> ENCRYPT_CACHE = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build(new CacheLoader<String, String>() { public String load(String parameterValue) { return AES.encrypt(parameterValue); } }); public static final LoadingCache<String, String> DECRYPT_CACHE = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build(new CacheLoader<String, String>() { public String load(String parameterValue) { return AES.decrypt(parameterValue); } }); @Override public void setNonNullParameter(PreparedStatement preparedStatement, int i, Object parameter, JdbcType jdbcType) throws SQLException { String parameterValue = (String) parameter; String resultValue = ENCRYPT_CACHE.getUnchecked(StringUtils.trimToEmpty(parameterValue)); preparedStatement.setString(i, resultValue); } @Override public Object getNullableResult(ResultSet resultSet, String s) throws SQLException { return DECRYPT_CACHE.getUnchecked(StringUtils.trimToEmpty(resultSet.getString(s))); } @Override public Object getNullableResult(ResultSet resultSet, int i) throws SQLException { return DECRYPT_CACHE.getUnchecked(StringUtils.trimToEmpty(resultSet.getString(i))); } @Override public Object getNullableResult(CallableStatement callableStatement, int i) throws SQLException { return DECRYPT_CACHE.getUnchecked(StringUtils.trimToEmpty(callableStatement.getString(i))); } }
使用方法
针对sql语句,在xml配置文件中,对需要加密的字段加上
#{name,jdbcType=VARCHAR,typeHandler=com..........DataSecurityHandler},
针对查询结果,在xml配置文件中,在resultMap里面对需要解密的字段加上
<result column="name" property="name" jdbcType="VARCHAR" typeHandler="com.*..........DataSecurityHandler"/>
mybatis密码解密
mybatis的配置
properties的配置
代码:
package com.finance.entity.batctrl; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.util.Properties; import javax.sql.DataSource; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.codec.binary.Base64; import org.apache.ibatis.datasource.pooled.PooledDataSource; import org.apache.ibatis.io.Resources; import org.apache.ibatis.mapping.Environment; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.apache.ibatis.transaction.TransactionFactory; import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; public class MyBatisSessionFactory { /** * session工厂 */ private final static SqlSessionFactory sqlSessionFactory; /** * 日志类 */ private static Logger logger = LoggerFactory.getLogger(MyBatisSessionFactory.class); static { String resource = "mybatis-configuration.xml"; //Reader reader = null; InputStream inputStream = null; Properties props = new Properties(); try { //reader = Resources.getResourceAsReader(resource); InputStream in = Resources.getResourceAsStream("batch.properties"); props.load(in); String url= props.getProperty("jdbc.url"); String username= props.getProperty("jdbc.username"); String password= props.getProperty("jdbc.password"); logger.debug("befor url -> "+url); logger.debug("befor username -> "+username); logger.debug("befor decode -> "+password); byte[] b = Base64.decodeBase64(password.getBytes()); String decode = new String(b); props.put("url",url); props.put("username",username); props.put("password",decode); logger.debug("after url -> "+url); logger.debug("after username -> "+username); logger.debug("after decode -> "+decode); inputStream = Resources.getResourceAsStream(resource); } catch (IOException e) { } //sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream ,props ); } public static SqlSessionFactory getSessionFactory() { return sqlSessionFactory; } }
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。