SpringBoot第25讲:SpringBoot对TypeHandler的使用
本文是SpringBoot第25讲,SpringBoot对TypeHandler的使用,TypeHandler就是当SpringBoot 实体类中字段类型和数据库中字段类型不一致时进行使用。
文章目录
- SpringBoot第25讲:SpringBoot对TypeHandler的使用
- 1、使用场景
- 2、MyBatis下使用TypeHandler
- 3、MyBatis plus下使用TypeHandler
- 4、MyBatis实用场景-TypeHandler处理枚举
- 5、商品中心对TypeHandler的使用
- 6、白龙马对TypeHandler的使用
1、使用场景
TypeHandler,类型转换器,就是将数据库中的类型与Java中的类型进行相互转换的处理器。
有时候,我们进行存储时的字段类型和数据库最终存储的字段类型是不一致的:比如我们在springboot传入的是一个list,而在数据库中需要以VARCHAR类型保存;又比如我们在springboot传入一个JSON类型,而数据库中以String类型存储该字段;或者说数据库中是JSON类型,但java无找不到对应的JSON类型(直接使用JSONObject会报错),也可以使用typeHandler。
一般情况,我们可以强转String再存储该字段,但有时候查询数据时会出现问题:较为典型的是String类型的JSON串数据,以String类型返回至前端时,会由于进行两次json解析,导致返回结果反斜杆:
而这会导致前端解析时出错,所以我们需要用typeHandler对其进行转换:
简而言之,typeHandler就是当springboot后端实体类中字段类型和数据库中字段类型不一致时进行使用。
2、MyBatis下使用TypeHandler
首先,我们需要编写一个typeHandler(以JSON转VARCHAR为例):
@MappedTypes(JSONObject.class)
@MappedJdbcTypes(JdbcType.LONGVARCHAR)
public class JsonObjectTypeHandler extends BaseTypeHandler<JSONObject> {
/**
* 插入数据时,将JSONObject转String
* @param ps
* @param i
* @param parameter
* @param jdbcType
* @throws SQLException
*/
@Override
public void setNonNullParameter(PreparedStatement ps, int i, JSONObject parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, JSONObject.toJSONString(parameter));
}
/**
* 根据列名,获取可以为空的结果
* @param rs
* @param columnName
* @return
* @throws SQLException
*/
@Override
public JSONObject getNullableResult(ResultSet rs, String columnName) throws SQLException {
String sqlJson = rs.getString(columnName);
if (null != sqlJson){
return JSONObject.parseObject(sqlJson);
}
return null;
}
/**
* 根据列索引,获取可以为空的结果
* @param rs
* @param columnIndex
* @return
* @throws SQLException
*/
@Override
public JSONObject getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String sqlJson = rs.getString(columnIndex);
if (null != sqlJson){
return JSONObject.parseObject(sqlJson);
}
return null;
}
@Override
public JSONObject getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String sqlJson = cs.getString(columnIndex);
if (null != sqlJson){
return JSONObject.parseObject(sqlJson);
}
return null;
}
}
注意,该TypeHandler需要放在mapper文件夹下(即和mapper类放在一起,否则会找不到)
然后,在mapper.xml文件中,编写resultmap映射字段,并指定哪些字段需要使用typeHandler:
<!--字段映射,将数据库中String类型的json串映射为JSONObject,避免返回前段时两次序列化使得返回结果带反斜杠-->
<resultMap id="illnessMap" type="com.seven.springcloud.dto.IllnessDTO">
<result column="Weight" property="Weight" javaType="com.alibaba.fastjson.JSONObject" jdbcType="VARCHAR"
typeHandler="com.seven.springcloud.mapper.JsonObjectTypeHandler"/>
<result column="Add_date" property="Drug_Date"/>
</resultMap>
<!--查询所有病人的信息-->
<select id="selectUserIllness" resultMap="illnessMap">
select * from user_illness where Account=#{Account}
</select>
然后,实体类中,将字段类型设为JSONObject:
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_EMPTY) //空值不返回
public class IllnessDTO implements Serializable {
private JSONObject Weight;
private String Drug_Date;
}
然后,就可以成功映射数据啦。
查看数据库,保存的是一个String类型的JSON串:
3、MyBatis plus下使用TypeHandler
使用mybatis plus时,我们也可以使用mybatis的方式,xml文件中编写resultMap的形式映射字段;也可以选择以mybatis plus的注解的形式使用。
首先,我们仍然需要编写一个typeHandler类:
@MappedJdbcTypes(JdbcType.VARCHAR) //数据库类型
@MappedTypes({JSONObject.class}) //java数据类型
public class JsonTypeHandler implements TypeHandler<JSONObject> {
@Override
public void setParameter(PreparedStatement ps, int i, JSONObject parameter, JdbcType jdbcType) throws SQLException {
if (parameter!=null){
ps.setString(i, JSONObject.toJSONString(parameter));
}
}
@Override
public JSONObject getResult(ResultSet rs, String columnName) throws SQLException {
return JSONObject.parseObject(rs.getString(columnName));
}
@Override
public JSONObject getResult(ResultSet rs, int columnIndex) throws SQLException {
return JSONObject.parseObject(rs.getString(columnIndex));
}
@Override
public JSONObject getResult(CallableStatement cs, int columnIndex) throws SQLException {
return JSONObject.parseObject(cs.getString(columnIndex));
}
}
注:我们需要再application.yml文件中指定该typeHandler所在的包的位置,否则会找不到:
mybatis-plus:
type-handlers-package: com.seven.demo.typerHandler
然后,我们在实体类进行配置即可:
@TableName(value ="student",autoResultMap = true) //autoResultMap为true才会自动配置resultMap映射字段
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student implements Serializable {
@TableId
private Long id;
private String name;
@TableField(value = "grade", typeHandler = JsonTypeHandler.class) //指定typeHandler进行转换类型
private JSONObject grade;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
然后,我们就可以对类型进行转换了。
下面我们可以编写接口测试一下:
@RestController
public class StudentController {
@Resource
private StudentService studentService;
@PostMapping("/add")
public boolean add(@RequestBody Student student){
return studentService.save(student);
}
@GetMapping("/get")
public Student get(@RequestParam("id")int id){
return studentService.getById(id);
}
}
测试成功,查看数据库:
成功保存字符类型json串。
4、MyBatis实用场景-TypeHandler处理枚举
自定义TypeHandler
我们可以通过自定义TypeHandler的形式来在设置参数或者取出结果集的时候自定义参数封装策略。
步骤:
- 1、实现TypeHandler接口或者继承BaseTypeHandler
- 2、使用@MappedTypes定义处理的java类型,使用@MappedJdbcTypes定义jdbcType类型
- 3、在自定义结果集标签或者参数处理的时候声明使用自定义TypeHandler进行处理或者在全局配置TypeHandler要处理的javaType。
示例:希望数据库保存的是100, 200这些状态码,而不是默认0,1 或者枚举的名字
public enum EmpStatus {
LOGIN(100,"用户登录"),LOGOUT(200,"用户登出"),REMOVE(300,"用户不存在");
private Integer code;
private String msg;
private EmpStatus(Integer code,String msg){
this.code = code;
this.msg = msg;
}
//按照状态码返回枚举对象
public static EmpStatus getEmpStatusByCode(Integer code){
switch (code) {
case 100:
return LOGIN;
case 200:
return LOGOUT;
case 300:
return REMOVE;
default:
return LOGOUT;
}
}
...
自定义Handler
public class MyEnumEmpStatusTypeHandler implements TypeHandler<EmpStatus> {
/**
* 定义当前数据如何保存到数据库中
*/
@Override
public void setParameter(PreparedStatement ps, int i, EmpStatus parameter, JdbcType jdbcType) throws SQLException {
System.out.println("要保存的状态码:" + parameter.getCode());
ps.setString(i, parameter.getCode().toString());
}
@Override
public EmpStatus getResult(ResultSet rs, String columnName) throws SQLException {
//需要根据从数据库中拿到的枚举的状态码返回一个枚举对象
int code = rs.getInt(columnName);
System.out.println("从数据库中获取的状态码:"+code);
EmpStatus status = EmpStatus.getEmpStatusByCode(code);
return status;
}
@Override
public EmpStatus getResult(ResultSet rs, int columnIndex) throws SQLException {
int code = rs.getInt(columnIndex);
System.out.println("从数据库中获取的状态码:"+code);
EmpStatus status = EmpStatus.getEmpStatusByCode(code);
return status;
}
@Override
public EmpStatus getResult(CallableStatement cs, int columnIndex) throws SQLException {
int code = cs.getInt(columnIndex);
System.out.println("从数据库中获取的状态码:"+code);
EmpStatus status = EmpStatus.getEmpStatusByCode(code);
return status;
}
}
注册自定义TypeHandler:
1.MyBatis全局配置文件 中注册
<configuration>
<typeHandlers>
<typeHandler handler="com.lun.c09.other.typehandler.MyEnumEmpStatusTypeHandler"
javaType="com.lun.c09.other.bean.EmpStatus"/>
</typeHandlers>
...
2、也可以在处理某个字段的时候告诉MyBatis用什么类型处理器
- 保存:
#{empStatus,typeHandler=xxxx}
- 查询:
<resultMap type="com.lun.bean.Employee" id="MyEmp">
<id column="id" property="id"/>
<result column="empStatus" property="empStatus" typeHandler="xxxxx"/>
</resultMap>
注意:如果在参数位置修改TypeHandler,应该保证保存数据和查询数据用的TypeHandler是一样的。
5、商品中心对TypeHandler的使用
使用场景是将db中的String类型字段转换为Json类型
TypeHandler的定义
public class ListOtherAttributeHandler extends AbstractJsonTypeHandler<List<OtherAttribute>> {
@Override
protected List<OtherAttribute> parse(String json) {
return ItemPlatformJsonUtil.jsonToList(json, OtherAttribute.class);
}
@Override
protected String toJson(List<OtherAttribute> obj) {
return ItemPlatformJsonUtil.objToJson(obj);
}
}
TypeHandler的注册
<resultMap id="ItemAttributeMap" type="ItemAttributeDO">
typeHandler="ListOtherAttributeHandler"
</resultMap>
TypeHandler的使用
@TableField(value = "key_attributes", typeHandler = ListOtherAttributeHandler.class)
private List<OtherAttribute> keyAttributes;
6、白龙马对TypeHandler的使用
定义
/**
* json字段和java对象转换器
* 为节省存储空间,MappedTypes里的对象设置JsonInclude.Include.NON_NULL
*
* @param <T>
*/
@MappedTypes(value = {FareAttachmentExtraInfo.class})
@MappedJdbcTypes(value = {JdbcType.VARCHAR}, includeNullJdbcType = true)
public class JsonTypeHandler<T> extends BaseTypeHandler<T> {
private final Class<T> clazz;
public JsonTypeHandler(Class<T> clazz) {
if (clazz == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
this.clazz = clazz;
}
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Object o, JdbcType jdbcType) throws SQLException {
preparedStatement.setString(i, JsonUtils.toJson(o));
}
@Override
public T getNullableResult(ResultSet resultSet, String s) throws SQLException {
String value = resultSet.getString(s);
return value == null ? null : JsonUtils.fromJson(value, clazz);
}
@Override
public T getNullableResult(ResultSet resultSet, int i) throws SQLException {
String value = resultSet.getString(i);
return value == null ? null : JsonUtils.fromJson(value, clazz);
}
@Override
public T getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
String value = callableStatement.getString(i);
return value == null ? null : JsonUtils.fromJson(value, clazz);
}
}
注册
mybatis-plus:
configuration:
mapUnderscoreToCamelCase: true
log-impl: ${falcon.mybatis-plus.log}
mapper-locations: classpath*:/mapper/**/*Mapper.xml
type-handlers-package: com.huxun.convoy.finance.dao.handler
使用
@Getter
@Setter
@Accessors(chain = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class FareAttachmentExtraInfo {
/**
* 承运id
*/
private Long subOrderCarrierId;
/**
* 司机id
*/
private Long driverId;
}