mybatisjson字段处理
前⾔
最近遇到了使⽤mysql的json类型字段的解析问题,之前的开发的时候,刚开始⽤的就是mybatis,为了解决json字段的问题,有的同事是把json字段映射成Java⾥的String,⼿动在业务代码⾥转化,也有同事尝试⽤typeHandler⾃动解析,可惜没成功,最近我接受这部分代码,花了⼀天的时间才完成⾃动解析的配置。
⽬的
最终的⽬的是希望json字段能⾃动映射成java对象。
基本情况说明
Java表对应的java实体
TeacherDO  {
private Student student;
get(); // 省略
set(); // 省略
}
表:
create table teacher (
student json // 省略
)
<select  resultType="teacher">
select student from teacher
</select>
<insert>
insert into teacher (student)
values(#{student)
</insert>
只写了关键的内容,其它都忽略。
问题
如果在上述情况下使⽤,使⽤会报错
batis.anslateExceptionIfPossible(MyBatisExceptionTranslator.java:78)
batis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:440)
at com.sun.proxy.$Proxy175.selectList(Unknown Source)
batis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:223)
at org.apache.ibatis.uteForMany(MapperMethod.java:147)
at org.apache.ibatis.ute(MapperMethod.java:80)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:57)
at com.sun.proxy.$Proxy176.findBy(Unknown Source)
这个错误信息⾮常清晰,student字段的类型错误,⽆法匹配,原因也很明确,表中是json 字段,接收对象中student是对象Student。
开始解决
基于以上错误信息,我的第⼀想法是mybatis是不是还不⽀持json字段⾃动转对象,我知道了官⽹的typ
eHandler的说明(),
从官⽹说明来看,实际是不⽀持⾃动转化。
因此,开始考虑实现⼀个⾃定义的typeHandler来解决。
resultset 遍历
现在我需要决定需要创建⼏个JSONTypeHandler,因为⾃定义typeHandler⼀般都是继承下⾯这个类:public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
/**
* @deprecated Since 3.5.0 - See github/mybatis/mybatis-3/issues/1203. This field will remove future.  */
@Deprecated
protected Configuration configuration;
/
/ 省略
}
⾃定义实现的时候需要决定⾃⼰的typeHandler要解决的类型是什么,也就是泛型T。
有两种实现⽅式:
第⼀种、指定具体的java类型:
public class StudentTypeHandler extends  BaseTypeHandler<Student> {
// 省略
}
第⼆种、不指定具体的T,仍然使⽤泛型,通过配置javaType指定java类型
public class JsonTypeHandler<T extends Object> extends  BaseTypeHandler<T> {
// 省略
}
考虑到未来可能有更多的json字段,因此决定使⽤第⼆种,完整的JsonTypeHandler :
batis.handler;
import java.io.IOException;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
slf4j.Slf4j;
import org.ptions.PersistenceException;
import org.pe.BaseTypeHandler;
import org.pe.JdbcType;
import org.pe.MappedJdbcTypes;
/**
* Jackson 实现 JSON 字段类型处理器
@Slf4j
@MappedJdbcTypes(JdbcType.VARCHAR)
public class JacksonTypeHandler<T extends Object> extends BaseTypeHandler<T> {
private static ObjectMapper objectMapper;
private Class<T> type;
static {
objectMapper = new ObjectMapper();
}
public CommonJacksonTypeHandler(Class<T> type) {
if (log.isTraceEnabled()) {
}
if (null == type) {
throw new PersistenceException("Type argument cannot be null");
}
}
private T parse(String json) {
try {
if (json == null || json.length() == 0) {
return null;
}
adValue(json, type);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private String toJsonString(T obj) {
try {
return objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
@Override
public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
return String(columnName));
}
@Override
public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return String(columnIndex));
}
@Override
public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return String(columnIndex));
}
@Override
public void setNonNullParameter(PreparedStatement ps, int columnIndex, T parameter, JdbcType jdbcType)        throws SQLException {
ps.setString(columnIndex, toJsonString(parameter));
}
}
这样就实现了⼀个JsonTypeHandler,把对象转化为字符串(VARCHAR),⽤于解析json字段。开始使⽤
基于以上分析决策,已经实现了typeHandler,现在开始使⽤。
因为⾃定义的typeHandler指定的是java类型是泛型T,所以⽆法使⽤下⾯的配置:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-////DTD Config 3.0//EN" "/dtd/mybatis-3-config.dtd"> <configuration>
/
/ 省略
<typeHandlers>
<typeHandler handler="JacksonTypeHandler"/>
</typeHandlers>
// 省略
</configuration>
为什么⽆法使⽤?
public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
if (javaTypeClass != null) {
try {
Constructor<?> c = Constructor(Class.class);
return (TypeHandler<T>) c.newInstance(javaTypeClass);
} catch (NoSuchMethodException ignored) {
// ignored
} catch (Exception e) {
throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
}
}
try {
// 这⼀步会报错
Constructor<?> c = Constructor();
return (TypeHandler<T>) c.newInstance();
} catch (Exception e) {
throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
}
}
因为使⽤的是泛型,所以mybatis反射通过构造⽅法实例化时会报错,报错原因是没有具体的类型。
既然这种⽆法使⽤,只能在l中使⽤。
<resultMap>
<result column="student" property="student"
typeHandler="Student"
javaType="JacksonTypeHandler"/>
</resultMap>
<select  resultType="teacher">
select student from teacher
</select>
<insert>
insert into teacher (student)
values(#{student ,Student, typeHandler = JacksonTypeHandler)
)
</insert>
javaType⽤于指定,typeHandler的泛型T的具体类型,这样查询和插⼊就都能⾃动解析了。
优化
每个typeHandler的写的时候名字都太长,能不能像alias对象⼀样使⽤昵称?
经过验证,不⾏。
按照上述思路解决,还是⽆法解决问题,如何定位⾃⼰的问题
到mybatis的DefaultResultSetHandler的 applyPropertyMappings ⽅法,这个⽅法⽤来遍历解析查询到的数据
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String colu mnPrefix)
throws SQLException {
final List<String> mappedColumnNames = MappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
final List<ResultMapping> propertyMappings = PropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
String column = Column(), columnPrefix);
if (NestedResultMapId() != null) {
// the user added a column attribute to a nested result map, ignore it
column = null;
}
if (propertyMapping.isCompositeResult()
|| (column != null && UpperCase(Locale.ENGLISH)))
|| ResultSet() != null) {
// TODO 这⼀⾏是解析数据
Object value = ResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
// issue #541 make property optional
final String property = Property();
if (property == null) {
continue;
} else if (value == DEFERRED) {
foundValues = true;
continue;
}
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !SetterType(property).isPrimitive())) {
// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(property, value);
}
}
}
return foundValues;
}
getPropertyMappingValue⽅法:
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, Stri ng columnPrefix)
throws SQLException {
if (NestedQueryId() != null) {
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
} else if (ResultSet() != null) {
addPendingChildRelation(rs, metaResultObject, propertyMapping);  // TODO is that OK?
return DEFERRED;
} else {
// TODO 这⼀步可以确认⾃定义的typeHandler是不是正确的
final TypeHandler<?> typeHandler = TypeHandler();
final String column = Column(), columnPrefix);
// TODO 这⼀步⽤来调⾃定义的typeHandler的数据解析⽅法
Result(rs, column);
}
}

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。