Je pense qu'il y a un moyen d'utiliser moins de mémoire (une quantité fixe et non linéaire dépendant de la cardinalité des données) mais cela implique de changer la signature de la méthode. En fait, nous pouvons imprimer les données Json directement sur un flux de sortie dès que nous les récupérons dans le ResultSet : les données déjà écrites seront collectées puisque nous n'avons pas besoin d'un tableau qui les garde en mémoire.
J'utilise GSON qui accepte les adaptateurs de type. J'ai écrit un adaptateur de type pour convertir ResultSet en JsonArray et cela ressemble beaucoup à votre code. J'attends la version "Gson 2.1 : Targeted Dec 31, 2011" qui aura le "Support for user-defined streaming type adapters". Je modifierai alors mon adaptateur pour en faire un adaptateur de type streaming.
Mise à jour
Comme promis, je suis de retour mais pas avec Gson, plutôt avec Jackson 2. Désolé d'être en retard (de 2 ans).
Préface : La clé pour utiliser moins de mémoire du résultat lui-même se trouve dans le curseur "côté serveur". Avec ce type de curseurs (alias resultset pour les développeurs Java), le SGBD envoie les données de manière incrémentielle au client (alias driver) au fur et à mesure que le client avance dans la lecture. Je pense que le curseur Oracle est côté serveur par défaut. Pour MySQL > 5.0.2, recherchez useCursorFetch à l'adresse suivante paramètre de l'url de connexion . Vérifiez votre SGBD préféré.
1 : Donc, pour utiliser moins de mémoire, nous devons :
- utiliser le curseur côté serveur derrière la scène
- utiliser le jeu de résultats ouvert en tant que lecture seulement et, bien sûr, en avant seulement ;
- éviter de charger tout le curseur dans une liste (ou une
JSONArray
) mais écrire chaque ligne directement sur un ligne de sortie où par ligne de sortie, j'entends un flux de sortie ou un rédacteur ou encore un générateur json qui englobe un flux de sortie ou un rédacteur.
2 : Comme le dit Jackson Documentation :
L'API de streaming est la plus performante (frais généraux les plus bas, lecture/écriture les plus rapides ; les deux autres méthodes s'appuient sur elle)
3 : Je vois que dans votre code vous utilisez getInt, getBoolean. getFloat... de ResultSet sans wasNull . Je pense que cela peut poser des problèmes.
4 : J'ai utilisé des tableaux pour mettre en cache les pensées et éviter d'appeler les getters à chaque itération. Bien que n'étant pas un fan de la construction switch/case, je l'ai utilisé pour cela. int
SQL Types
.
La réponse : Pas encore entièrement testée, elle est basée sur Jackson 2.2 :
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.2.2</version>
</dependency>
Le site ResultSetSerializer
indique à Jackson comment sérialiser (transformer l'objet en JSON) un ResultSet. Il utilise l'API Streaming de Jackson à l'intérieur. Voici le code d'un test :
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);
Et, bien sûr, le code de la classe ResultSetSerializer :
public class ResultSetSerializer extends JsonSerializer<ResultSet> {
public static class ResultSetSerializerException extends JsonProcessingException{
private static final long serialVersionUID = -914957626413580734L;
public ResultSetSerializerException(Throwable cause){
super(cause);
}
}
@Override
public Class<ResultSet> 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);
}
}
}
1 votes
Une implémentation différente code.google.com/p/bonex-homerunning/source/browse/trunk/…
1 votes
L'implémentation pour java.sql.Types.ARRAY n'a pas fonctionné pour moi en utilisant postgresql (le tableau a été mis en tant que chaîne en utilisant "{...}". J'ai finalement modifié la ligne "obj.put(column_name, rs.getArray(column_name));" en "Array array = rs.getArray(column_name); if ( array != null ) obj.put(column_name, new JSONArray(array.getArray()));"
0 votes
Si les performances sont un problème majeur, vous ne devriez pas utiliser cette API JSON mais plutôt utiliser une bibliothèque de streaming qui écrit simplement du JSON sans avoir besoin de créer des objets en mémoire de toutes les données (où vous pouvez rechercher/trouver des éléments dans l'arborescence). Cela dit, je m'assurerais que vous avez effectivement un problème de performances avant de faire cela.
3 votes
Il y a une erreur dans votre extrait.
java.sql.Types.BIGINT
est de taille 8 octets, il doit donc être lu avecrs.getLong()
et non pasrs.getInt()
3 votes
Merci d'avoir chargé ceci. Tu viens de m'épargner des heures de travail.
0 votes
Je pense que tous les
if
else if
sont inutiles.