背景

在公司的一个项目中,某表的一些字段是加密存储,需要解密返回给前端,于是我写下这么一串代码,对字段逐个解密

public class Object {
	Field field;
	...

    public void decryptField(){
        address = this.decrypt(address);
        brandModule = this.decrypt(brandModule);
        engineNumber = this.decrypt(engineNumber);
        issueTime = this.decrypt(issueTime);
        licensePlate = this.decrypt(licensePlate);
        licenseType = this.decrypt(licenseType);
        ownerName = this.decrypt(ownerName);
        registerTime = this.decrypt(registerTime);
        vehicleType = this.decrypt(vehicleType);
        vin = this.decrypt(vin);
    }

    private String decrypt(String str){
        return StringUtils.isNotBlank(str) ? AesUtils.decrypt(address) : "";
    }
}

后被同时看到:“你怎么这么做加解密,你去看一下其他人怎么写的...”

遂翻找了同事的代码,了解到了Mybatis的TypeHandler。

什么是TypeHandler

Java对象的字段有对应的Java类型,数据库字段有对应的Jdbc类型。当我们存取数据时,Mybatis会做相应的映射处理,比如String->VARCHAR 、String->DateTime等,这就是TypeHandler的作用。Mybatis内置了大量的TypeHandler用于处理基础数据,详见 mybatis – MyBatis 3 | 配置

自定义TypeHandler

我们有时会需要对数据存取做一些特殊处理,比如“格林威治时间(GMT)转时间戳”,“List转String”等。Mybatis没有提供这些TypeHandler,我们可以自定义对应的TypeHandler。此处使用我遇到的问题“字段加解密”来做示例。

(一)编写自定义TypeHandler

/**
 * 敏感数据处理 mybatis Handler
 *
 * BaseTypeHandler的泛型即是JavaType
 * @MappedJdbcTypes 非必须,不写则Mybatis会根据JavaType使用对应的JdbcType。若指定了JdbcTypes,则只有指定的类型能被此Handler处理。
 */
@MappedJdbcTypes({JdbcType.VARCHAR, JdbcType.CHAR})
public class SensitiveDataHandler extends BaseTypeHandler<String> {

    /** 
	 * 字段加密
     * 用于将Java类型参数设置到预处理语句PreparedStatement中
     */
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, AesUtils.encrypt((String) parameter));
    }

    /**
     * 字段解密
     * 用于将查询结果集ResultSet的columnName列的数据转换成Java类型
     */
    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String columnValue = rs.getString(columnName);
        return this.decrypt(columnValue);
    }

    /**
     * 字段解密
     * 用于将查询结果集ResultSet的第columnIndex列的数据转换成Java类型
     */
    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String columnValue = rs.getString(columnIndex);
        return this.decrypt(columnValue);
    }

    /**
     * 字段解密
     * 用于将存储过程CallableStatement的第columnIndex列的数据转换成Java类型
     */
    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String columnValue = cs.getString(columnIndex);
        return this.decrypt(columnValue);
    }

    /**
     * 字段解密
     *
     */
    private String decrypt(String columnValue) {
        return StringUtils.isBlank(columnValue) ? columnValue : AesUtils.decrypt(columnValue);
    }

}

(二)使用自定义TypeHandler

查询时,在resultMap的字段中指定TypeHandler类路径

  <resultMap id="BaseResultMap" type="com.demo.common.pojo.Object">
    <id column="id" jdbcType="BIGINT" property="id" />
    <result column="field" jdbcType="VARCHAR" property="claimNumber" typeHandler="com.demo.common.mybatis.SensitiveDataHandler" />
    ...
  </resultMap>

  <select id="selectById" parameterType="java.lang.Long" resultMap="BaseResultMap">
    select * from "table"
    where "id" = #{id,jdbcType=BIGINT}
  </select>

新增/修改时,在占位符中指定TypeHandler类路径

<insert id="insert" parameterType="com.demo.common.pojo.Object">
	insert into "table" 
        (id, field1, ...)
	value
        (#{id, jdbcType=BIGINT}, #{field, jdbcType=VARCHAR, typeHandler="com.demo.common.mybatis.SensitiveDataHandler"})
<insert>

此时我们就完成了对指定字段做保存时加密,查询时解密。

总结

屏幕前的程序猿:“说得好!我还是选择手写加解密 0.o

如大家所见,使用自定义TypeHandler这种方式不一定会降低我们的工作量,需要结合实际情况来考虑。在我的项目中,不止一张表的字段需要做加解密,如果都写在业务代码中,不仅大大增加了工作量,还会降低业务逻辑的可读性。这种情况使用自定义TypeHandler绝对是会方便很多的。如果你的数据处理逻辑简单,且不会造成大量重复代码,直接写在代码中就完事了。

文章作者: 像柔风
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 像柔风的个人博客
Mybatis
喜欢就支持一下吧