主流数据库对Json数据类型都有了支持,但是Mybatis中并没有很好地支持,必须自己编写TypeHandler进行处理。最近用pg库时遇到了json类型数据的查询解析问题,也查了不少资料启发很大,但是没有找到满足我需求的相关资料。所以对代码进行了跟踪,分析了Mybaits的PGProvider默认数据类型转换部分的代码逻辑,解决了相关的通用查询json解析问题。
遇到的问题是,服务查询的表名、字段名都不确定,所以不能提前生成一堆实体模型。只能通过表名+字段名动态生成SQL进行动态查询,为了使查询结果通用,我们使用了List
当使用默认的查询转换Json类型数据时,会输出以下格式内容:
{
"type":"json",
"value":"{\"\aa":\"11\",\"bb\":\"22\"}"
}
这种格式必然有个Handler与之对应,找到这个Handler并替换掉应该就能解决我们的问题;
一路单步跟下去,发现几个底层转换方法需要认真分析下:
//解析妹一行的结果值
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getRowValue(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap, java.lang.String)
//创建字段和java类型的映射关系
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#createAutomaticMappings()
//获取jdbcType类型使用的handler对象
org.apache.ibatis.executor.resultset.ResultSetWrapper#getTypeHandler(Class propertyType, String columnName)
//其中的一条命令是使用java类型+jdbc类型在typeHandlerRegistry中查找handler
handler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType);
//getTypeHandler的实现不多贴出来瞧瞧
//org.apache.ibatis.type.TypeHandlerRegistry#getTypeHandler(java.lang.reflect.Type, org.apache.ibatis.type.JdbcType)
private TypeHandler getTypeHandler(Type type, JdbcType jdbcType) {
if (ParamMap.class.equals(type)) {
return null;
}
Map> jdbcHandlerMap = getJdbcHandlerMap(type); //通过JDBC类型获取到了三个实现
TypeHandler handler = null;
if (jdbcHandlerMap != null) {
handler = jdbcHandlerMap.get(jdbcType);
if (handler == null) {
handler = jdbcHandlerMap.get(null);
}
if (handler == null) {
// #591
handler = pickSoleHandler(jdbcHandlerMap);
}
}
// type drives generics here
return (TypeHandler) handler;
}
//上面方法中的jdbcHandlerMap内容为:
{null=class java.lang.Object, OTHER=class java.lang.Object, ARRAY=class java.lang.Object}
//由于没有对应的handler,所以最总new了一个默认的handler:handler = new ObjectTypeHandler();
//org.apache.ibatis.type.ObjectTypeHandler
//以上为handler的查找定位,接下来的代码是比较有意思的,有关数据库中的数据是如何转为java对象的。(题外话了)
//org.postgresql.jdbc.PgResultSet#internalGetObject
//org.postgresql.jdbc.PgResultSet#getString(int) --获取一个字符
public String getString(int columnIndex) throws SQLException {
...
Encoding encoding = connection.getEncoding();
try {
/*
1.this_row表示一行的数据,使用byte[][]类型的二维数据表示,第一维是字段索引,第二维是字段值,知道jdbcType就可以将不同类型转为Java类型了
2.byte的取值范围是[-128, 127],是不是就限制了每个pg的每个表最多只能有0~127个索引共128个字段呢???
*/
return trimString(columnIndex, encoding.decode(this_row[columnIndex - 1]));
} catch (IOException ioe) {
...
}
}
通过以上定位发现所有的handler都是间接从configuration的TypehandlerRegisty对象的typeHandlerMap私有属性中获得。
看看typehandlerMap的赋值方法,发现调用复制到这个是register(…)方法,数了下居然有12个重载-_-!!!
不过针对这个私有变脸赋值的方法只有一个,就是:
private void register(Type javaType, JdbcType jdbcType, TypeHandler handler) {
if (javaType != null) {
Map> map = typeHandlerMap.get(javaType);
if (map == null || map == NULL_TYPE_HANDLER_MAP) {
map = new HashMap<>();
typeHandlerMap.put(javaType, map);
}
map.put(jdbcType, handler);
}
allTypeHandlersMap.put(handler.getClass(), handler);
}
对于注册我用的方法是,通过@autowired获得到了已注册的所有SqlSessionFactory,遍历SqlSessionFactory为每个实例注册handler
@Configuration
public class MybatisConfig {
@Autowired
private List sqlSessionFactoryList;
@PostConstruct
public void addSqlInterceptor() {
SchemaParamsterInterceptor interceptor = new SchemaParamsterInterceptor();
for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
...
//注册json对象处理器
//registryJsonObjectTypeHandler(sqlSessionFactory);
}
}
private void registryJsonObjectTypeHandler(SqlSessionFactory sqlSessionFactory ){
org.apache.ibatis.session.Configuration config = sqlSessionFactory.getConfiguration();
//以下两个注册方法针对的私有对象不同,一个是jdbcTypeHandlerMap另一个是typeHandlerMap
config.getTypeHandlerRegistry().register(JdbcType.OTHER, new JsonObjectTypeHandler());
config.getTypeHandlerRegistry().register(Object.class,JdbcType.OTHER, new JsonObjectTypeHandler());
}
}
既然要实现自定的Typehandler,自然要继承BaseTypeHandler基类,其中的T为泛型类型,下面讨论下T的确定过程。
要返回json字符串,用到又是com.alibaba.fastjson,第一个想法就是使用JSONObject类型了;
数据库中存的为{“a”:“1”,“b”:“2”},查询下非常完美正常解析了。
但是如果db中存的是数组内?如:[{“a”:“1”,“b”:“2”},…] 或 [“a”,“b”,…],这时自定义的转换失败。
fastjson中有个类型专门的处理数组,对象是com.alibaba.fastjson.JSONArray。
这时数组类型处理的没问题了,但是把[] 去掉。逻辑又异常了!!!!
{“a”:“1”,“b”:“2”},[{“a”:“1”,“b”:“2”},…] 这两类json如何共存呢?
JSONObject和JSONArray,是个包含关系,只需要将JSONObjet加个[]就可以变为JSONArray类型。
虽然能解决问题,但是和db中存的数据发生了天壤之别,这个方案也不可取。
为什么要拘泥于实现类呢?JSONObject和JSONArray也是有共同点的,顶层必然继承自Object对象。
且json字符串数组和对象区别也比较好区分,如果是中扩招([)开头的字符串就是数组了。所以通过判断是否为数据,
使用fastjson的两个不同转换方法去做转换,通过Object去接收转换后的对象实例,完美解决了数组和对象的问题。
配置并使用自定义的TypeHandler比较容易,继承个基类实现几个方法就OK了。不过总有一种猜的成分在里面,不知道他是如何工作如何实现的,
甚至不清楚我自定义的handler是否成功,是否注册到了需要生效的位置。如果不通过分析代码分析流程自己永远只能在“黑盒”之外用一用。
遇到问题解决问题,是很好的学些过程,在时间和进度允许的的情况下,可以钻下“牛角尖”。