SpringBoot Lecture 25: SpringBoot's use of TypeHandler
This article is the 25th lecture of SpringBoot . SpringBoot uses TypeHandler. TypeHandler is used when the field type in the SpringBoot entity class is inconsistent with the field type in the database.
Article Directory
1. Usage scenarios
TypeHandler, a type converter, is a processor that converts types in the database to types in Java.
Sometimes, the field type when we store is inconsistent with the field type finally stored in the database: for example, we pass in a list in springboot, but it needs to be saved in the database as VARCHAR; another example is that we pass in a JSON type in springboot, and the field is stored in the database as String; or the database is of JSON type, but java can’t find the corresponding JSON type (directly using JSONObject will report an error), and typeHandler can also be used.
In general, we can force String to store the field, but sometimes there will be problems when querying data: the typical JSON string data of String type, when returned to the front end as String type, will result in backslashes due to two JSON parsings :
And this will cause errors in front-end parsing, so we need to use typeHandler to convert it:
In short, typeHandler is used when the field type in the springboot backend entity class is inconsistent with the field type in the database.
2. Use TypeHandler under MyBatis
First, we need to write a typeHandler (take JSON to VARCHAR as an example):
@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;
}
}
Note that the TypeHandler needs to be placed under the mapper folder (that is, put it together with the mapper class, otherwise it will not be found)
Then, in the mapper.xml file, write the resultmap mapping field and specify which fields need to use 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>
Then, in the entity class, set the field type to JSONObject:
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_EMPTY) //空值不返回
public class IllnessDTO implements Serializable {
private JSONObject Weight;
private String Drug_Date;
}
Then, you can successfully map the data.
Looking at the database, a JSON string of String type is saved:
3. Use TypeHandler under MyBatis plus
When using mybatis plus, we can also use the mybatis method to write resultMap in the xml file to map fields; we can also choose to use it in the form of mybatis plus annotations.
First, we still need to write a typeHandler class:
@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));
}
}
Note: We need to specify the location of the package where the typeHandler is located in the application.yml file, otherwise it will not be found:
mybatis-plus:
type-handlers-package: com.seven.demo.typerHandler
Then, we can configure it in the entity class:
@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;
}
Then, we can convert the type.
Now we can write an interface test:
@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);
}
}
The test is successful, check the database:
Successfully saved the character type json string.
4. MyBatis practical scenario - TypeHandler handles enumeration
Custom TypeHandler
We can customize the parameter encapsulation strategy when setting parameters or fetching result sets by customizing TypeHandler.
step:
- 1. Implement TypeHandler interface or inherit BaseTypeHandler
- 2. Use @MappedTypes to define the java type to be processed, and use @MappedJdbcTypes to define the jdbcType type
- 3. When customizing the result set label or parameter processing, declare the javaType to be processed by custom TypeHandler or configure TypeHandler globally.
Example: I want the database to save the status codes 100, 200 instead of the default 0, 1 or the name of the enumeration
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;
}
}
...
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;
}
}
Register a custom TypeHandler:
1. Register in the MyBatis global configuration file
<configuration>
<typeHandlers>
<typeHandler handler="com.lun.c09.other.typehandler.MyEnumEmpStatusTypeHandler"
javaType="com.lun.c09.other.bean.EmpStatus"/>
</typeHandlers>
...
2. You can also tell MyBatis what type of processor to use when processing a field
- keep:
#{empStatus,typeHandler=xxxx}
- Inquire:
<resultMap type="com.lun.bean.Employee" id="MyEmp">
<id column="id" property="id"/>
<result column="empStatus" property="empStatus" typeHandler="xxxxx"/>
</resultMap>
Note : If you modify the TypeHandler at the parameter position, you should ensure that the TypeHandler used for saving data and querying data is the same.
5. The use of TypeHandler in the commodity center
The usage scenario is to convert the String type field in db to Json type
Definition of 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);
}
}
Registration of TypeHandler
<resultMap id="ItemAttributeMap" type="ItemAttributeDO">
typeHandler="ListOtherAttributeHandler"
</resultMap>
Use of TypeHandler
@TableField(value = "key_attributes", typeHandler = ListOtherAttributeHandler.class)
private List<OtherAttribute> keyAttributes;
6. Bailongma's use of TypeHandler
definition
/**
* 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);
}
}
register
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
use
@Getter
@Setter
@Accessors(chain = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class FareAttachmentExtraInfo {
/**
* 承运id
*/
private Long subOrderCarrierId;
/**
* 司机id
*/
private Long driverId;
}
@Getter
@Setter
@Accessors(chain = true)
public class FareAttachmentDO {
private Long id;
/**
* 租户id
*/
private Long tenantId;
/**
* 附件扩展信息
*/
private FareAttachmentExtraInfo extraInfo;
}
Through the above steps, the string field in jdbc is gracefully converted into a Json object