java-Mybatis custom JsonObjectTypeHandler dynamically parses database JSON type data

java-Mybatis custom JsonObjectTypeHandler dynamically parses database JSON type data

environment

  • jdk 1.8
  • springboot 1.5.6
  • PostgreSQL 14.5
  • houseboat 3.53
  • postgresql 42.2.1

introduction

Mainstream databases support the Json data type, but Mybatis does not support it well, so you must write your own TypeHandler for processing. Recently, when using the pg library, I encountered the problem of query and analysis of json type data. I also checked a lot of information, which was very inspiring, but I did not find relevant information that met my needs. So I tracked the code, analyzed the code logic of Mybaits' PGProvider default data type conversion part, and solved the related common query json parsing problem.
The problem encountered is that the table name and field name of the service query are uncertain, so a bunch of entity models cannot be generated in advance. SQL can only be dynamically generated through the table name + field name for dynamic query. In order to make the query result universal, we use the List<Map<String,Object>> type to accept the Mybatis query result. Each element in the List represents a row of data, and the Map It is the corresponding relationship between fields and values, and finally the service message is returned in Json type.

The role of the handler?

When using the default query to convert Json type data, the following format will be output:

{
    "type":"json",
    "value":"{\"\aa":\"11\",\"bb\":\"22\"}"
}

This format must have a Handler corresponding to it. Finding this Handler and replacing it should solve our problem;

Locate the acquisition logic of the Handler

Following step by step all the way, I found that several underlying conversion methods need to be carefully analyzed:

//解析妹一行的结果值
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)

getTypeHandler() method

//其中的一条命令是使用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 <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
if (ParamMap.class.equals(type)) {
    return null;
}
Map<JdbcType, TypeHandler<?>> 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<T>) 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) {
      ...
    }
}

Through the above positioning, it is found that all handlers are obtained indirectly from the typeHandlerMap private property of the TypehandlerRegisty object of configuration.
Looking at the assignment method of typehandlerMap, I found that the call copied to this is the register(...) method. After counting, there are actually 12 overloads -_-!!!
However, there is only one method for this private face-changing assignment, which is:

  private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
    if (javaType != null) {
      Map<JdbcType, TypeHandler<?>> 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);
  }

By tracking the acquisition process of the handler object, we know three key parameters:

  1. The json in db is represented by jdbcType.OTHER
  2. The json in db is represented by java.Object
  3. It is the custom handler we need to implement, inherited from the BaseTypeHandler<> abstract class

Registration of custom handler

The method I use for registration is to get all registered SqlSessionFactory through @autowired, and traverse SqlSessionFactory to register handler for each instance


@Configuration
public class MybatisConfig {
    @Autowired
    private List<SqlSessionFactory> 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());
    }
}

Implementation of JsonObjectTypeHandler

Since a custom Typehandler is to be implemented, it is natural to inherit the BaseTypeHandler base class, where T is a generic type, and the process of determining T is discussed below.

T is com.alibaba.fastjson.JSONObject

To return the json string, com.alibaba.fastjson is used again. The first idea is to use the JSONObject type; it is
stored in the database as {"a": "1", "b": "2"}, query The next is very perfect and normal analysis.
But what if the db is stored in an array? Such as: [{"a": "1", "b": "2"},…] or [“a”, “b”,…], then the custom conversion fails.
There is a special processing array in fastjson, and the object is com.alibaba.fastjson.JSONArray.

T为com.alibaba.fastjson.JSONArray

At this time, the processing of the array type is no problem, but [] is removed. The logic is abnormal again! ! ! !
{"a": "1", "b": "2"}, [{"a": "1", "b": "2"},...] How do these two types of json coexist?
JSONObject and JSONArray are a containment relationship, you only need to add [] to JSONObjet to change it to JSONArray type.
Although it can solve the problem, it is very different from the data stored in the db, and this solution is not advisable.

T为java.lang.Object

Why stick to the implementation class? JSONObject and JSONArray also have something in common, and the top layer must inherit from the Object object.
Moreover, the difference between a json string array and an object is also relatively easy to distinguish. If it is a string beginning with a Chinese expansion ([), it is an array. So by judging whether it is data,
use two different conversion methods of fastjson to convert, and receive the converted object instance through Object, which perfectly solves the problem of arrays and objects.

Summarize

It is easier to configure and use a custom TypeHandler. It is OK to inherit a base class and implement several methods. But there is always an element of guessing in it. I don’t know how it works and how it is implemented. I
don’t even know whether my custom handler is successful or not, and whether it is registered to the position that needs to take effect. If you don't analyze the code analysis process, you can only use it outside the "black box".
Encountering problems and solving problems is a good way to learn some processes. When time and progress allow, you can get down to the "horn".

Guess you like

Origin blog.csdn.net/xxj_jing/article/details/131046568