最有效的ResultSet转换为JSON?

Dev*_*xon 106 java sql json resultset

以下代码ResultSet使用JSONArray和将a转换为JSON字符串JSONObject.

import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONException;

import java.sql.SQLException;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;

public class ResultSetConverter {
  public static JSONArray convert( ResultSet rs )
    throws SQLException, JSONException
  {
    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+1; i++) {
        String column_name = rsmd.getColumnName(i);

        if(rsmd.getColumnType(i)==java.sql.Types.ARRAY){
         obj.put(column_name, rs.getArray(column_name));
        }
        else if(rsmd.getColumnType(i)==java.sql.Types.BIGINT){
         obj.put(column_name, rs.getInt(column_name));
        }
        else if(rsmd.getColumnType(i)==java.sql.Types.BOOLEAN){
         obj.put(column_name, rs.getBoolean(column_name));
        }
        else if(rsmd.getColumnType(i)==java.sql.Types.BLOB){
         obj.put(column_name, rs.getBlob(column_name));
        }
        else if(rsmd.getColumnType(i)==java.sql.Types.DOUBLE){
         obj.put(column_name, rs.getDouble(column_name)); 
        }
        else if(rsmd.getColumnType(i)==java.sql.Types.FLOAT){
         obj.put(column_name, rs.getFloat(column_name));
        }
        else if(rsmd.getColumnType(i)==java.sql.Types.INTEGER){
         obj.put(column_name, rs.getInt(column_name));
        }
        else if(rsmd.getColumnType(i)==java.sql.Types.NVARCHAR){
         obj.put(column_name, rs.getNString(column_name));
        }
        else if(rsmd.getColumnType(i)==java.sql.Types.VARCHAR){
         obj.put(column_name, rs.getString(column_name));
        }
        else if(rsmd.getColumnType(i)==java.sql.Types.TINYINT){
         obj.put(column_name, rs.getInt(column_name));
        }
        else if(rsmd.getColumnType(i)==java.sql.Types.SMALLINT){
         obj.put(column_name, rs.getInt(column_name));
        }
        else if(rsmd.getColumnType(i)==java.sql.Types.DATE){
         obj.put(column_name, rs.getDate(column_name));
        }
        else if(rsmd.getColumnType(i)==java.sql.Types.TIMESTAMP){
        obj.put(column_name, rs.getTimestamp(column_name));   
        }
        else{
         obj.put(column_name, rs.getObject(column_name));
        }
      }

      json.put(obj);
    }

    return json;
  }
}
Run Code Online (Sandbox Code Playgroud)
  • 有更快的方法吗?
  • 有没有一种使用更少内存的方法?

Pla*_*lap 33

我认为有一种方法可以使用更少的内存(固定而非线性的数量取决于数据基数),但这意味着改变方法签名.实际上,我们可以在从ResultSet中获取Json数据时直接在输出流上打印Json数据:已经写入的数据将被垃圾收集,因为我们不需要将数据保存在内存中的数组.

我使用接受类型适配器的GSON.我写了一个类型适配器来将ResultSet转换为JsonArray,它看起来非常像你的代码.我正在等待"Gson 2.1:Targeted Dec 31,2011"发布,该版本将支持"支持用户定义的流式传输类型适配器".然后我将修改我的适配器作为流适配器.


更新

正如所承诺的那样,我回来了但不是和Gson一起,而是和Jackson 2一起.抱歉迟到了(2年).

前言:使用较少内存的结果itsef的关键是在"服务器端"光标中.使用这种游标(也就是Java开发人员的结果集),当客户端继续读取时,DBMS会逐步向客户端(也称为驱动程序)发送数据.我认为Oracle游标默认是服务器端.对于MySQL> 5.0.2,在连接url paramenter中查找useCursorFetch .检查您最喜欢的DBMS.

1:因此,为了减少使用内存,我们必须:

  • 在场景后面使用服务器端光标
  • 使用结果集打开为只读,当然,只转发 ;
  • 避免将所有光标加载到列表(或a JSONArray)中,而是直接在输出行上写入每一行,其中输出行I表示输出流或编写器,或者包含输出流或编写器的json生成器.

2:杰克逊文档说:

流式API性能最佳(开销最低,读/写速度最快;其他2种方法基于它)

3:我在你的代码中看到你使用getInt,getBoolean.getFloat ... ResultSet中没有的wasNull.我希望这会产生问题.

4:我使用数组来缓存think并避免每次迭代都调用getter.虽然不是开关/案例构造的粉丝,但我将它用于intSQL Types.

答案:尚未完全测试,它基于Jackson 2.2:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.2.2</version>
</dependency>
Run Code Online (Sandbox Code Playgroud)

ResultSetSerializer对象指示Jackson如何序列化(将对象转换为JSON)ResultSet.它使用内部的Jackson Streaming API.这里的测试代码:

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);
Run Code Online (Sandbox Code Playgroud)

当然,还有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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


ora*_*ecz 27

使这更快的两件事是:

将您的通话移至rsmd.getColumnCount()while循环之外.列数不应在行之间变化.

对于每个列类型,您最终都会调用以下内容:

obj.put(column_name, rs.getInt(column_name));
Run Code Online (Sandbox Code Playgroud)

使用列索引检索列值会稍微快一些:

obj.put(column_name, rs.getInt(i));
Run Code Online (Sandbox Code Playgroud)


And*_*ite 23

JIT编译器可能会非常快,因为它只是分支和基本测试.你可以通过HashMap查找回调来使它更优雅,但我怀疑它会更快.至于记忆,这是非常苗条的.

不知何故,我怀疑这段代码实际上是内存或性能的关键瓶颈.你有什么理由尝试优化它吗?


Elh*_*aky 21

一个更简单的解决方案(基于相关代码):

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;
Run Code Online (Sandbox Code Playgroud)

  • 很好,但DATETIME和TIMESTAMP有一个错误(它不添加Apostrophes (3认同)

Luk*_*der 10

你可以使用jOOQ来完成这项工作.您不必使用jOOQ的所有功能来利用一些有用的JDBC扩展.在这种情况下,只需写:

String json = DSL.using(connection).fetch(resultSet).formatJSON();
Run Code Online (Sandbox Code Playgroud)

使用的相关API方法是:

生成的格式将如下所示:

{"fields":[{"name":"field-1","type":"type-1"},
           {"name":"field-2","type":"type-2"},
           ...,
           {"name":"field-n","type":"type-n"}],
 "records":[[value-1-1,value-1-2,...,value-1-n],
            [value-2-1,value-2-2,...,value-2-n]]}
Run Code Online (Sandbox Code Playgroud)

您也可以轻松地创建自己的格式 Result.map(RecordMapper)

这基本上与您的代码相同,绕过JSON对象的生成,直接"流式传输"到StringBuilder.不过,我会说两种情况下的性能开销应该可以忽略不计.

(免责声明:我为jOOQ背后的公司工作)


Aci*_*ier 7

除了@Jim Cook提出的建议.另一个想法是使用开关而不是if-elses:

while(rs.next()) {
  int numColumns = rsmd.getColumnCount();
  JSONObject obj = new JSONObject();

  for( int i=1; i<numColumns+1; i++) {
    String column_name = rsmd.getColumnName(i);

    switch( rsmd.getColumnType( i ) ) {
      case java.sql.Types.ARRAY:
        obj.put(column_name, rs.getArray(column_name));     break;
      case java.sql.Types.BIGINT:
        obj.put(column_name, rs.getInt(column_name));       break;
      case java.sql.Types.BOOLEAN:
        obj.put(column_name, rs.getBoolean(column_name));   break;
      case java.sql.Types.BLOB:
        obj.put(column_name, rs.getBlob(column_name));      break;
      case java.sql.Types.DOUBLE:
        obj.put(column_name, rs.getDouble(column_name));    break;
      case java.sql.Types.FLOAT:
        obj.put(column_name, rs.getFloat(column_name));     break;
      case java.sql.Types.INTEGER:
        obj.put(column_name, rs.getInt(column_name));       break;
      case java.sql.Types.NVARCHAR:
        obj.put(column_name, rs.getNString(column_name));   break;
      case java.sql.Types.VARCHAR:
        obj.put(column_name, rs.getString(column_name));    break;
      case java.sql.Types.TINYINT:
        obj.put(column_name, rs.getInt(column_name));       break;
      case java.sql.Types.SMALLINT:
        obj.put(column_name, rs.getInt(column_name));       break;
      case java.sql.Types.DATE:
        obj.put(column_name, rs.getDate(column_name));      break;
      case java.sql.Types.TIMESTAMP:
        obj.put(column_name, rs.getTimestamp(column_name)); break;
      default:
        obj.put(column_name, rs.getObject(column_name));    break;
    }
  }

  json.put(obj);
}
Run Code Online (Sandbox Code Playgroud)

  • 向后循环(比较索引零)也更快(比将索引与表达式进行比较). (4认同)