使用场景
在使用MyBatisPlus时往往会遇见JavaType与JDBCType之间的类型转换或一些敏感数据内容进行编码处理的场景时,就可以使用 BaseTypeHandler,其进行类型转换的原理类似于拦截器。
比如:
- 实体类中字段存储类型为Map<String,?>,而映射到数据库的字段类型是varchar。
- 用户名、密码、身份证号展示给客户端页面时需要进行特殊编码处理。
BaseTypeHandler 抽象类
这里需要提到一个MyBatis中一个非常重要的泛型接口TypeHandler(类型处理器),用于处理参数类型,包括入参形式和返回结果集相关参数的转换。从源码中看到该接口定义了四个方法,需要子类去继承并实现以下方法:
- setParameter() 方法:使用TypeHandler通过PreparedStatement对象进行设置SQL参数的时候使用的具体方法,其中i是参数在SQL的下标,parameter是参数,jdbcType是数据库类型。
- getResult() 方法:从JDBC结果集中获取数据进行转换,用于为设置字段对应的结果集,可以通过字段的名字和下标来设置对应的结果集。该方法也是BaseTypeHandler类的一个抽象方法,通过子类的getNullableResult()方法实现结果集的填充。
BaseTypeHandler 其实就是实现了TypeHandler接口本身的方法外,还扩展了相关的方法,后续MyBatis包中的类型处理器都是他的实现类,为了满足我们的需求,创建自定义类去实现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;
}
/**
* 当参数 parameter 和 jdbcType 同时为空时,MyBatis 将抛出异常。如果能明确 jdbcType,则会进行空设置;如果参数不为空,那么它将采用 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 方法获取的。如果判断为空,则返回 null。
*/
@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
* Column 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;
}
直接上代码:首先需要在实体类中标记需要进行类型转换的字段,然后是在Mapper.xml中对映射字段添加typeHandler属性
SysUser 系统用户实体
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "用户表(SysUser)表实体类")
@TableName(value = "sys_user", autoResultMap = true)//必要的注解, 其意思类似生成策略,autoResultMap并不能使用在自定义的方法上,只在MyBatis Plus内置方法上生效
public class SysUser extends Model<SysUser> {
private static final long serialVersionUID = 1L;
/**
* 用户ID,主键
*/
@TableId(value = "id", type = IdType.AUTO)
@ApiModelProperty(value = "用户ID,主键")
private Long id;
/**
* 登录账号
*/
@ApiModelProperty(value = "登录账号")
private String username;
/**
* 密码
*/
@ApiModelProperty(value = "密码")
private String password;
/**
* 用户邮箱
*/
@TableField(value = "email", typeHandler = BaseMybatisDataEncoderHandler.class)//标注需要类型转换的字段
@ApiModelProperty(value = "用户邮箱")
private String email;
/**
* 手机号码
*/
@TableField(value = "phone_number", typeHandler = BaseMybatisDataEncoderHandler.class)
@ApiModelProperty(value = "手机号码")
private String phoneNumber;
/**
* 身份证
*/
@TableField(value = "phone_number", typeHandler = BaseMybatisDataEncoderHandler.class)
@ApiModelProperty(value = "身份证")
private String idcard;
}
SysUserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="eca.basicservice.admin.biz.mapper.SysUserMapper">
<resultMap id="BaseResultMap" type="eca.basicservice.admin.api.vo.UserVo">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="email" property="email"
typeHandler="cn.common.mybatis.handler.BaseMybatisDataEncoderHandler"/>
<result column="phone_number" property="phoneNumber"
typeHandler="cn.common.mybatis.handler.BaseMybatisDataEncoderHandler"/>
<result column="idcard" property="phoneNumber"
typeHandler="cn.common.mybatis.handler.BaseMybatisDataEncoderHandler"/>
</resultMap>
</mapper>
BaseMybatisDataEncoderHandler 数据编码处理器
@MappedTypes({String.class})//MappedTypes映射类型,标记当前输入字段JavaType类型为String
@MappedJdbcTypes(JdbcType.VARCHAR)//MappedJdbcTypes:映射JDBC类型,表示输入字段需要映射到JDBC中的类型
public class BaseMybatisDataEncoderHandler extends BaseTypeHandler<String> {
/**
* 这里放置数据编码和解码时的密钥,一般定义在yml中映射的ConfigProperties对象中
*/
private final String ENCODE_KEY="123456";
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, String s, JdbcType jdbcType) throws SQLException {
preparedStatement.setString(i, encrypt(s));
}
@Override
public String getNullableResult(ResultSet resultSet, String columnName) throws SQLException {
String data = resultSet.getString(columnName);
return StrUtil.isBlank(data) ? null : decrypt(data);
}
@Override
public String getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException {
String data = resultSet.getString(columnIndex);
return StrUtil.isBlank(data) ? null : decrypt(data);
}
@Override
public String getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
String data = callableStatement.getString(columnIndex);
return StrUtil.isBlank(data) ? null : decrypt(data);
}
/**
* 明文加密
*
* @param plaintext
* @return
*/
private String encrypt(String plaintext) {
return AesUtils.encrypt(plaintext, ENCODE_KEY.trim());
}
/**
* 密文解密
*
* @param ciphertext
* @return
*/
private String decrypt(String ciphertext) {
return AesUtils.decrypt(ciphertext, ENCODE_KEY.trim());
}
}