BaseTypeHandler是为了在 MyBatis 中自定义 Java 类型(与 JDBC 类型之间的转换逻辑。在实际开发中也可以用来加密或者解密。
首先定义一个LocalStringHandler,让他来继承BaseTypeHandler< String>。代码如下:
public class 在 extends BaseTypeHandler<String> {
public LocalStringHandler() {
}
public void setNonNullParameter(PreparedStatement preparedStatement, int i, String parameter, JdbcType jdbcType) {
try {
preparedStatement.setString(i, Sm4Encryption.INSTANCE.encrypt(parameter));
} catch (Exception var6) {
Exception e = var6;
e.printStackTrace();
throw new BizException(0, e.getMessage());
}
}
public String getNullableResult(ResultSet resultSet, String s) throws SQLException {
String value = resultSet.getString(s);
try {
return value == null ? null : Sm4Encryption.INSTANCE.decrypt(value);
} catch (Exception var5) {
return value;
}
}
public String getNullableResult(ResultSet resultSet, int i) throws SQLException {
String value = resultSet.getString(i);
try {
return value == null ? null : Sm4Encryption.INSTANCE.decrypt(value);
} catch (Exception var5) {
return value;
}
}
public String getNullableResult(CallableStatement call, int i) throws SQLException {
String value = call.getString(i);
try {
return value == null ? null : Sm4Encryption.INSTANCE.decrypt(value);
} catch (Exception var5) {
return value;
}
}
}
接着在需要进行处理的字段上加上LocalStringHandler。
@TableField(typeHandler = LocalStringHandler.class)
private String mobile;
LocalStringHandler的原理:在执行SQL的时候,mybaits会检查字段是否有关联的 TypeHandler。由于在mobile这个字段上加了typeHandler。那么MyBatis 在准备 SQL 语句的参数时会调用 LocalStringHandler 的 setNonNullParameter 方法。而 setNonNullParameter 方法中的PreparedStatement代表了一个预编译的 SQL 语句。mybaits会对这个PreparedStatement进行拦截,并修改sql。在上例中mybaits就会将PreparedStatement中加了@TableField(typeHandler = LocalStringHandler.class)的字段进行加密,然后替换掉sql中的?,?可以理解成一个占位符。
遇到的问题
在单元测试中插入数据时,mobile字段成功加密。在本地的测试数据库中,可以看到加密成功。然后在单元测试中,我将加密的字段中数据库中查询出来发现加密的字段并没有解密成功。
解决方案
在执行插入或者更新时,会成功调用LocalStringHandler的setNonNullParameter方法,并修改preparedStatement中的sql。但是在查询的时候不会,虽然在插入时使用了注解来指定 TypeHandler,但 MyBatis 默认不会在查询时自动使用这个 TypeHandler 来解密数据,除非在结果映射(如 resultMap)中明确指定了。所以我为查询结果写了一个resultMap,并自定义个select方法。resultMap如下:
<resultMap id="FaultReplayResultMap" type="com.sz.ops.pojo.po.fault.FaultReplay">
<id property="id" column="id" javaType="java.lang.Long"/>
<result property="faultCode" column="fault_code" javaType="java.lang.String"/>
<result property="startTime" column="start_time" javaType="java.time.LocalDateTime"/>
<result property="finishTime" column="finish_time" javaType="java.time.LocalDateTime"/>
<result property="faultDuration" column="fault_duration" javaType="java.lang.Long"/>
<result property="reasonClazz" column="reason_clazz" javaType="java.lang.String"/>
<!-- 使用自定义类型处理器解密 reasonDesc -->
<result property="reasonDesc" column="reason_desc" typeHandler="com.local.LocalStringHandler"/>
<!-- 使用自定义类型处理器解密 recoveryDesc -->
<result property="recoveryDesc" column="recovery_desc" typeHandler="com.local.LocalStringHandler"/>
<result property="replayLevel" column="replay_level" javaType="java.lang.String"/>
<result property="mainLiabilityCompany" column="main_liability_company" javaType="java.lang.Integer"/>
<result property="secondLiabilityCompany" column="second_liability_company" javaType="java.lang.Integer"/>
<result property="reservePlan" column="reserve_plan" javaType="java.lang.Integer"/>
<result property="attachment" column="attachment" javaType="java.lang.String"/>
<result property="attachmentName" column="attachment_name" javaType="java.lang.String"/>
<result property="deleted" column="deleted" javaType="java.lang.Boolean"/>
</resultMap>
通过在resultMap的具体字段中中指定typeHandler="com.local.LocalStringHandler"。最终查询结果解密成功。
续上
新解决方案
在@TableName加上autoResultMap = true解密就生效了。