java-Mybatis JsonObjectTypeHandler personalizado analiza dinámicamente los datos de tipo JSON de la base de datos

java-Mybatis JsonObjectTypeHandler personalizado analiza dinámicamente los datos de tipo JSON de la base de datos

ambiente

  • jdk 1.8
  • arranque de primavera 1.5.6
  • PostgreSQL 14.5
  • casa flotante 3.53
  • postgresql 42.2.1

introducción

Las bases de datos convencionales admiten el tipo de datos Json, pero Mybatis no lo admite bien, por lo que debe escribir su propio TypeHandler para su procesamiento. Recientemente, al usar la biblioteca pg, me encontré con el problema de consultar y analizar datos tipo json, también revisé mucha información, lo cual fue muy inspirador, pero no encontré información relevante que satisficiera mis necesidades. Así que rastreé el código, analicé la lógica del código de la parte de conversión del tipo de datos predeterminado de PGProvider de Mybaits y resolví el problema de análisis json de consulta común relacionado.
El problema encontrado es que el nombre de la tabla y el nombre del campo de la consulta de servicio son inciertos, por lo que no se pueden generar muchos modelos de entidad por adelantado. SQL solo se puede generar dinámicamente a través del nombre de la tabla + nombre del campo para consultas dinámicas. Para que el resultado de la consulta sea universal, utilizamos el tipo List<Map<String,Object>> para aceptar el resultado de la consulta Mybatis. Cada elemento en el La lista representa una fila de datos y el Mapa es la relación correspondiente entre campos y valores, y finalmente el mensaje de servicio se devuelve en tipo Json.

¿El papel del manejador?

Cuando se utiliza la consulta predeterminada para convertir datos de tipo Json, se generará el siguiente formato:

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

Este formato debe tener un Handler correspondiente. Encontrar este Handler y reemplazarlo debería resolver nuestro problema;

Localizar la lógica de adquisición del Handler

Siguiendo paso a paso hasta el final, descubrí que es necesario analizar cuidadosamente varios métodos de conversión subyacentes:

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

método getTypeHandler()

//其中的一条命令是使用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) {
      ...
    }
}

A través del posicionamiento anterior, se descubre que todos los controladores se obtienen indirectamente de la propiedad privada typeHandlerMap del objeto de configuración TypehandlerRegisty.
Al observar el método de asignación de typehandlerMap, descubrí que la llamada copiada a este es el método de registro (...). Después de contar, ¡en realidad hay 12 sobrecargas -_-! Sin embargo, solo hay un método
para este privado tarea que cambia la cara, que es:

  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);
  }

Al rastrear el proceso de adquisición del objeto controlador, conocemos tres parámetros clave:

  1. El json en db está representado por jdbcType.OTHER
  2. El json en db está representado por java.Object
  3. Es el controlador personalizado que necesitamos implementar, heredado de la clase abstracta BaseTypeHandler<>.

Registro de controlador personalizado

El método que uso para el registro es obtener todos los SqlSessionFactory registrados a través de @autowired y recorrer SqlSessionFactory para registrar el controlador para cada instancia.


@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());
    }
}

Implementación de JsonObjectTypeHandler

Dado que se va a implementar un Typehandler personalizado, es natural heredar la clase base BaseTypeHandler, donde T es un tipo genérico, y el proceso de determinación de T se analiza a continuación.

T es com.alibaba.fastjson.JSONObject

Para devolver la cadena json se usa nuevamente com.alibaba.fastjson, la primera idea es usar el tipo JSONObject, se
almacena en la base de datos como {"a": "1", "b": "2"}, consulta El siguiente es un análisis muy perfecto y normal.
Pero ¿qué pasa si la base de datos se almacena en una matriz? Como por ejemplo: [{"a": "1", "b": "2"},…] o [“a”, “b”,…], entonces la conversión personalizada falla.
Hay una matriz de procesamiento especial en fastjson y el objeto es com.alibaba.fastjson.JSONArray.

T为com.alibaba.fastjson.JSONArray

En este momento, el procesamiento del tipo de matriz no es un problema, pero se elimina []. ¡La lógica vuelve a ser anormal! ! ! !
{"a": "1", "b": "2"}, [{"a": "1", "b": "2"},...] ¿Cómo coexisten estos dos tipos de json?
JSONObject y JSONArray son una relación de contención, solo necesita agregar [] a JSONObjet para cambiarlo al tipo JSONArray.
Aunque puede solucionar el problema, es muy diferente de los datos almacenados en la base de datos y esta solución no es recomendable.

T为java.lang.Objeto

¿Por qué ceñirse a la clase de implementación? JSONObject y JSONArray también tienen algo en común: la capa superior debe heredar del objeto Object.
Además, la diferencia entre una matriz de cadenas json y un objeto también es relativamente fácil de distinguir: si es una cadena que comienza con una expansión china ([), es una matriz. Entonces, al juzgar si se trata de datos,
use dos métodos de conversión diferentes de fastjson para convertir y reciba la instancia del objeto convertido a través de Object, lo que resuelve perfectamente el problema de las matrices y los objetos.

Resumir

Es más fácil configurar y usar un TypeHandler personalizado. Está bien heredar una clase base e implementar varios métodos. Pero siempre hay un elemento de conjetura en esto. No sé cómo funciona ni cómo se implementa. Ni
siquiera sé si mi controlador personalizado tiene éxito o no, y si está registrado en la posición que necesita. para tomar efecto. Si no analiza el proceso de análisis del código, solo puede usarlo fuera del "cuadro negro".
Encontrar problemas y resolverlos es una buena manera de aprender algunos procesos. Cuando el tiempo y el progreso lo permitan, podrá ponerse manos a la obra.

Supongo que te gusta

Origin blog.csdn.net/xxj_jing/article/details/131046568
Recomendado
Clasificación