需求
有的對(duì)象字段需要存儲(chǔ)為Json狡汉,可以直接轉(zhuǎn)成Json賦值后再保存闽颇。也可以通過Mybatis的TypeHandler自動(dòng)處理。
實(shí)現(xiàn)方案
- 先定義一個(gè)抽象的JsonTypeHandler
public abstract class AbstractJsonTypeHandler extends BaseTypeHandler<Object> {
protected Class<?> classType;
public AbstractJsonTypeHandler(Class<?> classType) {
this.classType = classType;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, toJson(parameter));
}
@Override
public Object getNullableResult(ResultSet rs, String columnName) throws SQLException {
final String json = rs.getString(columnName);
return StringUtils.isBlank(json) ? null : parse(json);
}
@Override
public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
final String json = rs.getString(columnIndex);
return StringUtils.isBlank(json) ? null : parse(json);
}
@Override
public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
final String json = cs.getString(columnIndex);
return StringUtils.isBlank(json) ? null : parse(json);
}
protected abstract Object parse(String json);
protected abstract String toJson(Object obj);
}
- JacksonTypeHandler
簡(jiǎn)單的JacksonTypeHandler,支持?jǐn)?shù)組剩膘,以為泛型擦除,對(duì)List畏梆,Set等集合反序列會(huì)丟失類型奈懒,導(dǎo)致數(shù)據(jù)被反序列化為Map。
@Slf4j
public class JacksonTypeHandler extends AbstractJsonTypeHandler {
private static final ObjectMapper MAPPER = new ObjectMapper();
static {
// 未知字段忽略
MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 不使用科學(xué)計(jì)數(shù)
MAPPER.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true);
// null 值不輸出(節(jié)省內(nèi)存)
MAPPER.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
}
public JacksonTypeHandler(Class<?> classType) {
super(classType);
}
@Override
protected Object parse(String jsonStr) {
if (StringUtils.isBlank(jsonStr)) {
return null;
}
try {
return MAPPER.readValue(jsonStr, classType);
} catch (JsonProcessingException e) {
log.error("parse failed, jsonStr={}", jsonStr, e);
return null;
}
}
@Override
protected String toJson(Object obj) {
if (Objects.isNull(obj)) {
return null;
}
try {
return MAPPER.writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new RuntimeException("Object to json string failed!" + obj, e);
}
}
}
- GenericJacksonTypeHandler
生成json是保存類信息溜畅,這樣反序列化的時(shí)候能正常序列化极祸,包括List和Set怠晴,缺點(diǎn)是生成的Json包含類信息浴捆,另外生成后類和包都不能修改。
public class GenericJacksonTypeHandler extends AbstractJsonTypeHandler {
private static final ObjectMapper MAPPER = new ObjectMapper();
static {
// 未知字段忽略
MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 不使用科學(xué)計(jì)數(shù)
MAPPER.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true);
// null 值不輸出(節(jié)省內(nèi)存)
MAPPER.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
// 配置輸出 反序列化類名的形式
MAPPER.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY
);
}
public GenericJacksonTypeHandler(Class<?> classType) {
super(classType);
}
@Override
protected Object parse(String jsonStr) {
if (StringUtils.isBlank(jsonStr)) {
return null;
}
try {
return MAPPER.readValue(jsonStr, classType);
} catch (JsonProcessingException e) {
log.error("parse failed, jsonStr={}", jsonStr, e);
return null;
}
}
@Override
protected String toJson(Object obj) {
if (Objects.isNull(obj)) {
return null;
}
try {
return MAPPER.writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new RuntimeException("Object to json string failed!" + obj, e);
}
}
}
生成的json如下:
[
"[Lcom.example.tkmybatisdemo.entity.UserInfo;",
[
{
"@class": "com.example.tkmybatisdemo.entity.UserInfo",
"info1": "1111",
"info2": [
"java.util.ArrayList",
[
{
"@class": "com.example.tkmybatisdemo.entity.UserInfo2",
"info21": "2222",
"info22": "33333"
}
]
]
}
]
]
Mybatis TypeHandler 泛型支持的問題
這個(gè)限制mybatis開發(fā)者在寫一個(gè) patch,希望以后能支持泛型滔金,畢竟Array操作上有點(diǎn)不方便。
使用說明
如果使用tk或者mybatis-plus可以通過注解來實(shí)現(xiàn)科阎。
@Data
//tk 標(biāo)識(shí)表名
@Table(name = "user")
//mybatis-plus如果屬性里指定了TypeHandler忿族,需要開啟autoResultMap=true,才會(huì)正常處理
@TableName(autoResultMap = true)
public class User {
//tk指定id列
@Id
private Long id;
private String name;
private Integer age;
//tk默認(rèn)會(huì)忽略Enum類型屬性
//3.5以上可以配置mapper.enum-as-simple-type=true 啟用
//4.0以上可以通過配置@Column或者@ColumnType就不會(huì)忽略了
@Column
private GenderEnum gender;
private String mobile;
//tk指定typehandler
@ColumnType(typeHandler = JacksonTypeHandler.class)
//mybatis-plus指定typehandler
@TableField(typeHandler = JacksonTypeHandler.class)
private UserInfo[] infos;
private Date createTime;
private Date updateTime;
}
定義Mapper
tk或者mybatis-plus道批,在對(duì)象上聲明的typeHandler使用內(nèi)置的函數(shù)都是生效的。但是對(duì)Mapper中自定義的函數(shù)無效椭岩,需要配置@Result指定typeHandler璃赡。
- TK通用mapper
@Repository
public interface UserMapper extends Mapper<User> {
@Results(value = {
@Result(column = "infos", property = "infos", typeHandler = JacksonTypeHandler.class)
})
@Select("select * from user")
List<User> listAll();
}
- Mybatis-Plus
@Repository
public interface User2Mapper extends BaseMapper<User> {
@Results(value = {
@Result(column = "infos", property = "infos", typeHandler = JacksonTypeHandler.class)
})
@Select("select * from user")
List<User> listAll();
}