MyBatis 没有办法像 MyBatis-Plus 一样无需用户介入自动完成数据库 JSON 字段到 Java 对象的转换,TA 需要借助 TypeHandler 机制。
那么 MyBatis 是如何借助 TypeHandler 实现 JSON 字段到 Java 对象转换的呢?
下面我将详细介绍这个过程。
首先,假设有一张名为 t_customer 的表,该表有一个 JSON 类型的字段 config ,如下:
sql
CREATE TABLE `t_customer` (
`id` int NOT NULL AUTO_INCREMENT,
`code` varchar(50) NOT NULL COMMENT '客户编号',
`category` varchar(15) NOT NULL COMMENT '客户类别',
`config` json NOT NULL COMMENT '配置信息,例如:{"recharge": "off", "maxSpaceLimit": 10737418240}',
`create_time` bigint NOT NULL COMMENT '创建时间',
`update_time` bigint NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `code_unique` (`code`)
)
与该表对应的实体类 Customer ,代码如下:
java
@Data
@Entity
@Table(name = "t_customer")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 客户编号
*/
private String code;
/**
* 客户类别
*/
private String category;
/**
* 配置信息
*/
private Config config;
/**
* 创建时间
*/
private Long createTime;
/**
* 更新时间
*/
private Long updateTime;
@Data
public static class Config {
/**
* 是否开启充值功能,on:开启,off:关闭
*/
private String recharge;
/**
* 最大空间限制,单位:B
*/
private Long maxSpaceLimit;
}
}
(注:实体类 Customer 中的 config 属性对应 t_customer 表中的 JSON 类型的 config 字段)
下面我们要为实体类 Customer 中的内部类 Customer.Config 创建一个与之对应的名为 CustomerConfigTypeHandler 的 TypeHandler,代码如下:
java
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(Customer.Config.class)
public class CustomerConfigTypeHandler extends BaseTypeHandler<Customer.Config> {
public CustomerConfigTypeHandler() {}
private static final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Customer.Config parameter, JdbcType jdbcType) throws SQLException {
try {
String json = objectMapper.writeValueAsString(parameter);
ps.setString(i, json);
} catch (JsonProcessingException e) {
throw new SQLException("Error converting Config to JSON", e);
}
}
@Override
public Customer.Config getNullableResult(ResultSet rs, String columnName) throws SQLException {
String json = rs.getString(columnName);
return parseJson(json);
}
@Override
public Customer.Config getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String json = rs.getString(columnIndex);
return parseJson(json);
}
@Override
public Customer.Config getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String json = cs.getString(columnIndex);
return parseJson(json);
}
private Customer.Config parseJson(String json) throws SQLException {
if (json == null || json.isEmpty()) {
return null;
}
try {
return objectMapper.readValue(json, Customer.Config.class);
} catch (Exception e) {
throw new SQLException("Error parsing JSON to Config", e);
}
}
}
最后我们要对与 Customer 对应的 Mapper 接口(假设接口名是 CustomerMapper)进行修改,如下:
- 查询
sql
@Select("SELECT * FROM t_customer WHERE code = #{code}")
@Results({
@Result(property = "config", column = "config", typeHandler = CustomerConfigTypeHandler.class)
})
Optional<Customer> selectByCode(@Param("code") String code);
- 插入
sql
@Insert({
"<script>",
"INSERT INTO t_customer (
code,
category,
config,
create_time,
update_time
) ",
"VALUES (
#{code},
#{category},
#{config, typeHandler=com.jnew.whatcell.dao.handler.CustomerConfigTypeHandler}),
#{createTime},
#{updateTime}
)
"</script>"
})
int insert(Customer customer);
- 更新
sql
@Update({
"<script>",
"UPDATE t_customer",
"SET",
" config = #{config, typeHandler=com.cab5.xxx.dao.handler.CustomerConfigTypeHandler}",
"WHERE code = #{code}",
"</script>"
})
void updateConfigByCode(Customer customer);
至此,我们就可直接使用 Mapper 中更新后的 查询、插入、更新等方法,让 JSON 字段与 Java 对象自动互转了。