目录
数据因为设计加密,所以保存的用数据都是加密后放到数据库中的,闲来无事,探寻下大佬的加密方式和逻辑。
利用类型解析器自动给数据加密
首先展示如何加密的:
定义另一个类 AesTypeHandler 继承抽象类mybatis BaseTypeHandler
public class AesTypeHandler extends BaseTypeHandler<Object> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
try {
if (StringUtils.isEmpty(parameter)) {
return;
}
//这里对参数做了加密处理
ps.setString(i, AESUtil.encrypt((String) parameter));
} catch (Exception e) {
throw new RuntimeException("mybatis数据加密异常!");
}
}
@Override
public Object getNullableResult(ResultSet rs, String columnName) throws SQLException {
String colResult = rs.getString(columnName);
try {
if (StringUtils.isEmpty(colResult)) {
return colResult;
}
if (colResult.startsWith(EncryptConstant.PERSISTENCE_DATA_ENCRYPT_HEAD)) {
//这里对参数做了解密处理
return AESUtil.decrypt(。。。));
}
return colResult;
} catch (Exception e) {
throw new RuntimeException("mybatis数据解密异常!");
}
}
@Override
public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String colResult = rs.getString(columnIndex);
try {
if (StringUtils.isEmpty(colResult)) {
return colResult;
}
if (colResult.startsWith(EncryptConstant.PERSISTENCE_DATA_ENCRYPT_HEAD)) {
//这里对参数做了解密处理
return AESUtil.decrypt(。。。));
}
return colResult;
} catch (Exception e) {
throw new RuntimeException("mybatis数据解密异常!");
}
}
@Override
public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String colResult = cs.getString(columnIndex);
AESUtil.decrypt(。。。));
return colResult;
}
}
然后再*mapper.xml中使用的话,如下:
对于加密字段需要手动在ResultMap中指定需要加急的字段的类型处理器
<result column="name" property="name" jdbcType="VARCHAR" typeHandler="com.XXX.mapper.type.AesTypeHandler" />
<result column="address" property="address" jdbcType="VARCHAR" typeHandler="com.XXX.mapper.type.AesTypeHandler" />
<result column="flag" property="flag" jdbcType="VARCHAR" />
....
这里查询出来的数据就是经过解密的,如果是插入,那也是需要在插入的时候增加的
jdbcType="VARCHAR" typeHandler="com.XXX.mapper.type.AesTypeHandler"
源码探寻
首先看下这个自定义的类型处理器关系
我们知道(网上搜索一下):
类型处理器 TypeHandler
MyBatis 中的 TypeHandler 类型处理器用于 JavaType 与 JdbcType 之间的转换,用于 PreparedStatement 设置参数值和从 ResultSet 或 CallableStatement 中取出一个值。MyBatis 内置了大部分基本类型的类型处理器,所以对于基本类型可以直接处理,当我们需要处理其他类型的时候就需要自定义类型处理器。
MyBatis 内置的 TypeHandler
在 MyBatis 的 TypeHandlerRegistry 类型中,可以看到内置的类型处理器。内置处理器比较多,这里整理常见的一些。
BooleanTypeHandler:用于 java 类型 boolean,jdbc 类型 bit、boolean
ByteTypeHandler:用于 java 类型 byte,jdbc 类型 TINYINT
ShortTypeHandler:用于 java 类型 short,jdbc 类型 SMALLINT
IntegerTypeHandler:用于 INTEGER 类型
LongTypeHandler:用于 long 类型
FloatTypeHandler:用于 FLOAT 类型
DoubleTypeHandler:用于 double 类型
StringTypeHandler:用于 java 类型 string,jdbc 类型 CHAR、VARCHAR
ArrayTypeHandler:用于 jdbc 类型 ARRAY
BigDecimalTypeHandler:用于 java 类型 BigDecimal,jdbc 类型 REAL、DECIMAL、NUMERIC
DateTypeHandler:用于 java 类型 Date,jdbc 类型 TIMESTAMP
DateOnlyTypeHandler:用于 java 类型 Date,jdbc 类型 DATE
TimeOnlyTypeHandler:用于 java 类型 Date,jdbc 类型 TIME
对于常见的 Enum 类型,内置了 EnumTypeHandler 进行 Enum 名称的转换和 EnumOrdinalTypeHandler 进行 Enum 序数的转换。这两个类型处理器没有在 TypeHandlerRegistry 中注册,如果需要使用必须手动配置。
————————————————
> 版权声明:本文为CSDN博主「May的博客」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
> 原文链接:https://blog.csdn.net/lmb55/article/details/90380309
然后看下基础类型处理器 BaseTypeHandler 的方法:
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
/**
* @deprecated Since 3.5.0 - See https://github.com/mybatis/mybatis-3/issues/1203. This field will remove future.
*/
@Deprecated
protected Configuration configuration;
/**
* Sets the configuration.
*
* @param c
* the new configuration
* @deprecated Since 3.5.0 - See https://github.com/mybatis/mybatis-3/issues/1203. This property will remove future.
*/
@Deprecated
public void setConfiguration(Configuration c) {
this.configuration = c;
}
//setParameter:这个很明显就是在调用sql的对参数的处理,setNonNullParameter是一个抽象方法, 很明显给后续实现类自定以处理的,所以加密的逻辑是写在这个实现类中的
@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null) {
if (jdbcType == null) {
throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
}
try {
ps.setNull(i, jdbcType.TYPE_CODE);
} catch (SQLException e) {
throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
+ "Cause: " + e, e);
}
} else {
try {
setNonNullParameter(ps, i, parameter, jdbcType);
} catch (Exception e) {
throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different configuration property. "
+ "Cause: " + e, e);
}
}
}
//下面这三个是获取三种不同的实现结果,里面分别调用了这个类的三个抽象方法:getNullableResult getNullableResult getNullableResult
//和上面的类似,也是在sql执行之后,再返回结果到调用者那里之前,对类型做一个转换。
@Override
public T getResult(ResultSet rs, String columnName) throws SQLException {
try {
return getNullableResult(rs, columnName);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set. Cause: " + e, e);
}
}
@Override
public T getResult(ResultSet rs, int columnIndex) throws SQLException {
try {
return getNullableResult(rs, columnIndex);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set. Cause: " + e, e);
}
}
@Override
public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
try {
return getNullableResult(cs, columnIndex);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column #" + columnIndex + " from callable statement. Cause: " + e, e);
}
}
//抽象方法,自定义实现
public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* Gets the nullable result.
*
* @param rs
* the rs
* @param columnName
* Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code>
* @return the nullable result
* @throws SQLException
* the SQL exception
*/
//抽象方法,自定义实现
public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
//抽象方法,自定义实现
public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;
//抽象方法,自定义实现
public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;
我们现在知道了 mybatis在这里提供一个接口,可以给用户自定义类型处理器,然后如何使用,我们也知道了,但是还有一个问题,那就是这里 setParameter 和 getResult 具体是在上面时候调用的呢?这里我们一个一个去探寻一下:
sql执行前的类型处理点
所以我们直接进入TypeHandler<T> 最顶层的父接口:
public interface TypeHandler<T> {
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* Gets the result.
*
* @param rs
* the rs
* @param columnName
* Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code>
* @return the result
* @throws SQLException
* the SQL exception
*/
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
点击方法 setParameter 发现进去了类SqlRunner的私有方法
private void setParameters(PreparedStatement ps, Object... args) throws SQLException {
for (int i = 0, n = args.length; i < n; i++) {
if (args[i] == null) {
throw new SQLException("SqlRunner requires an instance of Null to represent typed null values for JDBC compatibility");
} else if (args[i] instanceof Null) {
((Null) args[i]).getTypeHandler().setParameter(ps, i + 1, null, ((Null) args[i]).getJdbcType());
} else {
TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(args[i].getClass());
if (typeHandler == null) {
throw new SQLException("SqlRunner could not find a TypeHandler instance for " + args[i].getClass());
} else {
typeHandler.setParameter(ps, i + 1, args[i], null);
}
}
}
}
继续点击方法 setParameters 进入了SqlRunner的 selectAll 方法
public List<Map<String, Object>> selectAll(String sql, Object... args) throws SQLException {
try (PreparedStatement ps = connection.prepareStatement(sql)) {
setParameters(ps, args);
try (ResultSet rs = ps.executeQuery()) {
return getResults(rs);
}
}
}
继续往上:找到 SqlRunner的 selectOne方法
public Map<String, Object> selectOne(String sql, Object... args) throws SQLException {
List<Map<String, Object>> results = selectAll(sql, args);
if (results.size() != 1) {
throw new SQLException("Statement returned " + results.size() + " results where exactly one (1) was expected.");
}
return results.get(0);
}
好像有点熟悉了,再往上,没有了,难道这个是最上面的调用吗,网上找一下关于SqlRunner的介绍:
SqlRunner sqlRunner = new SqlRunner(connection);
String sql = new SQL()
.SELECT("*")
.FROM("person")
.WHERE("person_id = ?")
.toString();
Map<String, Object> resultMap = sqlRunner.selectOne(sql, 1);
System.out.println(resultMap);
原来这里就可以直接使用了,SqlRunner是一个数据库连接工具类,
[具体看这里] https://www.cnblogs.com/javaDeveloper/p/13141415.html
但是好像不是我们需要找到的 SqlSession 中的啊,
好吧,我们从头开始一次:在 TypeHandler 的方法 setParameter 引用中 ,之前我们走的时候SqlRunner的路子,现在我们走 DefaultParameterHandler
于是找到了:
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
再往上:找setParameters在哪里调用的
很明显,看名称,我们进入 PreparedStatementHandler 进入了方法:
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
继续往上,
如果我们对mybatis多一些了解的话,看到这几个Executor结尾的,就知道了,这几个都是实现接口Executor
我们进入简单的 SimpleExecutor
的方法 prepareStatement 一看名字就是做查询前准备的。。。。。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
再往上:
还是在这个SimpleExecutor中有三个方法都调用了这个方法
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
@Override
protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
Cursor<E> cursor = handler.queryCursor(stmt);
stmt.closeOnCompletion();
return cursor;
}
随便选一个doUpdate进入:
进入了BaseExecutor类(也是实现 接口Executor)方法 update(MappedStatement ms, Object parameter)
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
clearLocalCache();
return doUpdate(ms, parameter);
}
再往上,如下图:
我们发现了一个叫做 DefaultSqlSession 的东西,DefaultSqlSession 就是SqlSession接口的默认实现类,DefaultSelsession 方法
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
我们知道,使用基础的访问数据库是这样的:
String source = "mybatis-config.xml";
Reader reader = Resources.getResourceAsReader(source);
sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = sqlSessionFactoryBuilder.build(reader);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
Integer count = userDao.getCount();
后台使用动态代理实现了,其中SqlSession的默认实现就是DefaultSqlSession,相关mapper的数据库操作就是DefaultSqlSession的操作了,如何看出是动态代理的, 请点击
sqlSession.getMapper(UserDao.class);
进去看看,最终会得到:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
相信看到这里: return mapperProxyFactory.newInstance(sqlSession); 应该就明白了
sql执行后的类型处理点
这里就不具体说了,最后的上溯点是: CallableStatementHandler 类,其中这两个方法里面的 resultSetHandler.handleOutputParameters(cs); 包含了执行结果后的参数类型转换
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
CallableStatement cs = (CallableStatement) statement;
cs.execute();
List<E> resultList = resultSetHandler.handleResultSets(cs);
resultSetHandler.handleOutputParameters(cs);
return resultList;
}
@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
CallableStatement cs = (CallableStatement) statement;
cs.execute();
Cursor<E> resultList = resultSetHandler.handleCursorResultSets(cs);
resultSetHandler.handleOutputParameters(cs);
return resultList;
}
总结:多搞搞,多深入,有好处。