SpringBoot Lecture 25: SpringBoot's use of TypeHandler

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.

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 :

img

And this will cause errors in front-end parsing, so we need to use typeHandler to convert it:

img

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:
img

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);
    }
}

img

img

The test is successful, check the database:

img

img

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;
        }
		}
	...

Custom 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;
    }
}

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

Guess you like

Origin blog.csdn.net/qq_28959087/article/details/131631685