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;