Saltar al contenido

¿Conversión más eficiente de ResultSet a JSON?

Solución:

Creo que hay una forma de usar menos memoria (una cantidad fija y no lineal dependiendo de la cardinalidad de los datos) pero esto implica cambiar la firma del método. De hecho, podemos imprimir los datos Json directamente en un flujo de salida tan pronto como los recuperemos del ResultSet: los datos ya escritos serán recolectados como basura ya que no necesitamos un array que los guarda en la memoria.

Yo uso GSON que acepta adaptadores de tipo. Escribí un adaptador de tipo para convertir ResultSet a JsonArray y se parece mucho a su código. Estoy esperando la versión “Gson 2.1: Dirigida al 31 de diciembre de 2011”, que tendrá el “Soporte para adaptadores de tipo de transmisión definidos por el usuario”. Luego modificaré mi adaptador para que sea un adaptador de transmisión.


Actualizar

Como prometí, estoy de regreso pero no con Gson, sino con Jackson 2. Siento llegar tarde (de 2 años).

Prefacio: los key para usar menos memoria del resultado, sí se encuentra en el cursor del “lado del servidor”. Con este tipo de cursores (también conocido como conjunto de resultados para los desarrolladores de Java), el DBMS envía datos de forma incremental al cliente (también conocido como controlador) a medida que el cliente avanza con la lectura. Creo que el cursor de Oracle está en el lado del servidor de forma predeterminada. Para MySQL> 5.0.2, busque useCursorFetch en el parámetro de URL de conexión. Consulte su DBMS favorito.

1: Entonces, para usar menos memoria debemos:

  • usar el cursor del lado del servidor detrás de la escena
  • use resultset open como solo lectura y, por supuesto, solo reenvío;
  • evitar cargar todo el cursor en una lista (o un JSONArray) pero escriba cada fila directamente en un línea de salida, donde por línea de salida me refiero a un flujo de salida o un escritor o también un generador json que envuelve un flujo de salida o un escritor.

2: Como dice Jackson Documentation:

La API de transmisión tiene el mejor rendimiento (menor sobrecarga, lectura / escritura más rápida; otros 2 métodos se basan en ella)

3: Veo que en su código usa getInt, getBoolean. getFloat … de ResultSet sin wasNull. Espero que esto pueda generar problemas.

4: Usé matrices para almacenar en caché los pensamientos y evitar llamar a los captadores en cada iteración. Aunque no soy un fanático de la construcción de interruptor / caja, lo usé para eso int SQL Types.

La respuesta: Aún no completamente probado, se basa en Jackson 2.2:


    com.fasterxml.jackson.core
    jackson-databind
    2.2.2

los ResultSetSerializer object le indica a Jackson cómo serializar (transformar el objeto a JSON) un ResultSet. Utiliza la API de transmisión de Jackson en su interior. Aquí el código de una prueba:

SimpleModule module = new SimpleModule();
module.addSerializer(new ResultSetSerializer());

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);

[ . . . do the query . . . ]
ResultSet resultset = statement.executeQuery(query);

// Use the DataBind Api here
ObjectNode objectNode = objectMapper.createObjectNode();

// put the resultset in a containing structure
objectNode.putPOJO("results", resultset);

// generate all
objectMapper.writeValue(stringWriter, objectNode);

Y, por supuesto, el código de la clase ResultSetSerializer:

public class ResultSetSerializer extends JsonSerializer 

    public static class ResultSetSerializerException extends JsonProcessingException
        private static final long serialVersionUID = -914957626413580734L;

        public ResultSetSerializerException(Throwable cause)
            super(cause);
        
    

    @Override
    public Class handledType() 
        return ResultSet.class;
    

    @Override
    public void serialize(ResultSet rs, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException 

        try 
            ResultSetMetaData rsmd = rs.getMetaData();
            int numColumns = rsmd.getColumnCount();
            String[] columnNames = new String[numColumns];
            int[] columnTypes = new int[numColumns];

            for (int i = 0; i < columnNames.length; i++) 
                columnNames[i] = rsmd.getColumnLabel(i + 1);
                columnTypes[i] = rsmd.getColumnType(i + 1);
            

            jgen.writeStartArray();

            while (rs.next()) 

                boolean b;
                long l;
                double d;

                jgen.writeStartObject();

                for (int i = 0; i < columnNames.length; i++) 

                    jgen.writeFieldName(columnNames[i]);
                    switch (columnTypes[i]) 

                    case Types.INTEGER:
                        l = rs.getInt(i + 1);
                        if (rs.wasNull()) 
                            jgen.writeNull();
                         else 
                            jgen.writeNumber(l);
                        
                        break;

                    case Types.BIGINT:
                        l = rs.getLong(i + 1);
                        if (rs.wasNull()) 
                            jgen.writeNull();
                         else 
                            jgen.writeNumber(l);
                        
                        break;

                    case Types.DECIMAL:
                    case Types.NUMERIC:
                        jgen.writeNumber(rs.getBigDecimal(i + 1));
                        break;

                    case Types.FLOAT:
                    case Types.REAL:
                    case Types.DOUBLE:
                        d = rs.getDouble(i + 1);
                        if (rs.wasNull()) 
                            jgen.writeNull();
                         else 
                            jgen.writeNumber(d);
                        
                        break;

                    case Types.NVARCHAR:
                    case Types.VARCHAR:
                    case Types.LONGNVARCHAR:
                    case Types.LONGVARCHAR:
                        jgen.writeString(rs.getString(i + 1));
                        break;

                    case Types.BOOLEAN:
                    case Types.BIT:
                        b = rs.getBoolean(i + 1);
                        if (rs.wasNull()) 
                            jgen.writeNull();
                         else 
                            jgen.writeBoolean(b);
                        
                        break;

                    case Types.BINARY:
                    case Types.VARBINARY:
                    case Types.LONGVARBINARY:
                        jgen.writeBinary(rs.getBytes(i + 1));
                        break;

                    case Types.TINYINT:
                    case Types.SMALLINT:
                        l = rs.getShort(i + 1);
                        if (rs.wasNull()) 
                            jgen.writeNull();
                         else 
                            jgen.writeNumber(l);
                        
                        break;

                    case Types.DATE:
                        provider.defaultSerializeDateValue(rs.getDate(i + 1), jgen);
                        break;

                    case Types.TIMESTAMP:
                        provider.defaultSerializeDateValue(rs.getTime(i + 1), jgen);
                        break;

                    case Types.BLOB:
                        Blob blob = rs.getBlob(i);
                        provider.defaultSerializeValue(blob.getBinaryStream(), jgen);
                        blob.free();
                        break;

                    case Types.CLOB:
                        Clob clob = rs.getClob(i);
                        provider.defaultSerializeValue(clob.getCharacterStream(), jgen);
                        clob.free();
                        break;

                    case Types.ARRAY:
                        throw new RuntimeException("ResultSetSerializer not yet implemented for SQL type ARRAY");

                    case Types.STRUCT:
                        throw new RuntimeException("ResultSetSerializer not yet implemented for SQL type STRUCT");

                    case Types.DISTINCT:
                        throw new RuntimeException("ResultSetSerializer not yet implemented for SQL type DISTINCT");

                    case Types.REF:
                        throw new RuntimeException("ResultSetSerializer not yet implemented for SQL type REF");

                    case Types.JAVA_OBJECT:
                    default:
                        provider.defaultSerializeValue(rs.getObject(i + 1), jgen);
                        break;
                    
                

                jgen.writeEndObject();
            

            jgen.writeEndArray();

         catch (SQLException e) 
            throw new ResultSetSerializerException(e);
        
    

Dos cosas que harán esto más rápido son:

Mueva su llamada a rsmd.getColumnCount() fuera del bucle while. El recuento de columnas no debe variar entre filas.

Para cada tipo de columna, terminas llamando a algo como esto:

obj.put(column_name, rs.getInt(column_name));

Será un poco más rápido usar el índice de la columna para recuperar el valor de la columna:

obj.put(column_name, rs.getInt(i));

Una solución más simple (basada en el código en cuestión):

JSONArray json = new JSONArray();
ResultSetMetaData rsmd = rs.getMetaData();
while(rs.next()) 
  int numColumns = rsmd.getColumnCount();
  JSONObject obj = new JSONObject();
  for (int i=1; i<=numColumns; i++) 
    String column_name = rsmd.getColumnName(i);
    obj.put(column_name, rs.getObject(column_name));
  
  json.put(obj);

return json;

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *