Najbardziej wydajna konwersja ResultSet do JSON?

109

Poniższy kod konwertuje ResultSetciąg na ciąg JSON przy użyciu JSONArrayi 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;
  }
}
  • Czy jest szybszy sposób?
  • Czy istnieje sposób, który zużywa mniej pamięci?
Devin Dixon
źródło
1
Inaczej implementacja code.google.com/p/bonex-homerunning/source/browse/trunk/…
Leif Gruenwoldt
1
implementacja dla java.sql.Types.ARRAY nie działała dla mnie przy użyciu postgresql (tablica została umieszczona jako ciąg znaków przy użyciu „{...}”. Skończyło się na zmianie linii "obj.put (nazwa_kolumny, rs.getArray (nazwa_kolumny) ); "do" Array array = rs.getArray (nazwa_kolumny); if (tablica! = null) obj.put (nazwa_kolumny, nowy JSONArray (array.getArray ())); "
molekularna
Jeśli wydajność jest poważnym problemem, nie powinieneś używać tego interfejsu API JSON, ale raczej korzystaj z biblioteki strumieniowej, która po prostu zapisuje JSON bez konieczności tworzenia obiektów pamięci wszystkich danych (gdzie możesz przeglądać / znajdować rzeczy w drzewie). To powiedziawszy, upewnię się, że rzeczywiście masz problem z wydajnością, zanim to zrobisz.
Sebastiaan van den Broek,
3
W Twoim fragmencie jest błąd. java.sql.Types.BIGINTma rozmiar 8 bajtów, więc należy go odczytać za pomocą rs.getLong()notrs.getInt()
polarfish
3
Dzięki za załadowanie tego. Właśnie zaoszczędziłeś mi godzin pracy.
DRich

Odpowiedzi:

23

Kompilator JIT prawdopodobnie zrobi to całkiem szybko, ponieważ to tylko gałęzie i podstawowe testy. Prawdopodobnie można by było uczynić go bardziej eleganckim za pomocą odnośnika HashMap do wywołania zwrotnego, ale wątpię, czy byłby szybszy. Jeśli chodzi o pamięć, to i tak jest dość wąska.

Z jakiegoś powodu wątpię, czy ten kod jest w rzeczywistości kluczowym elementem pamięci lub wydajności. Czy masz jakiś prawdziwy powód, aby spróbować go zoptymalizować?

Andrew White
źródło
Umieszczam kod źródłowy w open-source'owym frameworku, więc nie wiem, do czego będzie używany. Dlatego staram się, aby był jak najbardziej efektywny.
Devin Dixon
1
@DevinDixon: czy framework jest dostępny? Czy jest coś takiego jak kod z twojego pytania już dostępny gdzieś w repozytorium open source?
34

Myślę, że istnieje sposób na użycie mniejszej ilości pamięci (stała i nieliniowa ilość w zależności od liczności danych), ale to oznacza zmianę sygnatury metody. W rzeczywistości możemy wydrukować dane Json bezpośrednio w strumieniu wyjściowym, gdy tylko pobierzemy je z zestawu wyników: już zapisane dane zostaną usunięte, ponieważ nie potrzebujemy tablicy, która przechowuje je w pamięci.

Używam GSON, który akceptuje adaptery typu. Napisałem adapter typu do konwersji ResultSet na JsonArray i wygląda bardzo podobnie do twojego kodu. Czekam na wydanie „Gson 2.1: Docelowe 31 grudnia 2011”, które będzie miało „Obsługę adapterów typu strumieniowego zdefiniowanego przez użytkownika”. Następnie zmodyfikuję mój adapter, aby był adapterem strumieniowym.


Aktualizacja

Zgodnie z obietnicą wróciłem, ale nie z Gsonem, ale z Jacksonem 2. Przepraszam, że się spóźniłem (o 2 lata).

Przedmowa: Klawisz pozwalający na użycie mniejszej ilości pamięci dla wyniku jest w kursorze „po stronie serwera”. Dzięki tego rodzaju kursorom (czyli zestawowi wyników dla programistów Java) DBMS wysyła dane stopniowo do klienta (czyli sterownika), gdy klient kontynuuje odczyt. Myślę, że kursor Oracle jest domyślnie po stronie serwera. Dla MySQL> 5.0.2 poszukaj useCursorFetch w parametrze adresu połączenia . Sprawdź swój ulubiony DBMS.

1: Aby więc używać mniej pamięci, musimy:

  • użyj kursora po stronie serwera za sceną
  • użyj otwartego zestawu wyników tylko do odczytu i oczywiście tylko do przodu ;
  • unikaj ładowania wszystkich kursorów na liście (lub a JSONArray), ale zapisz każdy wiersz bezpośrednio w wierszu wyjściowym , gdzie dla wiersza wyjściowego mam na myśli strumień wyjściowy lub pisarza lub także generator json, który opakowuje strumień wyjściowy lub piszącego.

2: Jak mówi Jackson Documentation:

Streaming API jest najlepiej działający (najniższe obciążenie, najszybszy odczyt / zapis; inne 2 metody opierają się na nim)

3: Widzę cię w twoim kodzie używając getInt, getBoolean. getFloat ... of ResultSet bez wasNull . Spodziewam się, że może to spowodować problemy.

4: Użyłem tablic do buforowania myśli i uniknięcia wywoływania funkcji pobierających w każdej iteracji. Chociaż nie jestem fanem konstrukcji przełącznika / przypadku, użyłem go do tego intSQL Types.

Odpowiedź: jeszcze nie w pełni przetestowana, jest oparta na Jackson 2.2 :

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.2.2</version>
</dependency>

ResultSetSerializerObiekt instruuje Jacksona, w jaki sposób do serializacji (tranform obiekt do JSON) ResultSet. Używa wewnątrz Jackson Streaming API. Tutaj kod testu:

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

I oczywiście kod klasy 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);
        }
    }
}
Plap
źródło
27

Dwie rzeczy, które przyspieszą to:

Przenieś wywołanie rsmd.getColumnCount()poza pętlę while. Liczba kolumn nie powinna się różnić w poszczególnych wierszach.

Dla każdego typu kolumny wywołujesz coś takiego:

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

Nieco szybciej będzie można użyć indeksu kolumny do pobrania wartości kolumny:

obj.put(column_name, rs.getInt(i));
oravecz
źródło
Zdefiniuj również String column_name;poza pętlą while.
Charney Kaye
22

Prostsze rozwiązanie (na podstawie omawianego kodu):

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;
Elhanan Mishraky
źródło
3
fajnie, ale jest błąd z DATETIME i TIMESTAMP (nie dodaje apostrofów
OhadR
ładne i proste
Anoop LL
10

Możesz użyć jOOQ do tej pracy. Nie musisz używać wszystkich funkcji jOOQ, aby skorzystać z niektórych przydatnych rozszerzeń JDBC. W takim przypadku po prostu napisz:

String json = DSL.using(connection).fetch(resultSet).formatJSON();

Odpowiednie stosowane metody API to:

Wynikowe formatowanie będzie wyglądać następująco:

{"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]]}

Możesz również łatwo utworzyć własne formatowanie za pomocą Result.map(RecordMapper)

To zasadniczo robi to samo, co twój kod, omijając generowanie obiektów JSON i „przesyłając strumieniowo” bezpośrednio do pliku StringBuilder. Powiedziałbym jednak, że w obu przypadkach narzut wydajności powinien być znikomy.

(Zastrzeżenie: pracuję dla firmy stojącej za jOOQ)

Lukas Eder
źródło
To jest niesamowite, ale mam problemy z analizowaniem powstałego ciągu. Gdy niektóre wartości zawierają cudzysłów, parser nie może poprawnie działać: myślę, że znaki cudzysłowu w wartościach powinny zostać zmienione ( "do \") w celu utworzenia prawidłowego ciągu JSON. Czy to błąd formatJSON()funkcji? A może coś mi brakuje?
Oneiros
@Oneiros: jOOQ powinien poprawnie pominąć te cudzysłowy ... Najlepiej zadaj nowe pytanie (ze szczegółami) lub zgłoś błąd: github.com/jOOQ/jOOQ/issues/new
Lukas Eder
W twoim przykładzie, w czym jest używany zestaw resultSet fetch(resultSet)? Nigdzie nie jest zdefiniowane. A jeśli otrzymam JDBC ResultSetprzed pobraniem, jaki jest cel DSL.using(connection)? Dlaczego potrzebuje połączenia? :)
Nikola Lošić
1
@ NikolaLošić: Cóż, pytanie dotyczy korzystania z JDBC ResultSet, więc myślę, że nie ma co do tego wątpliwości ResultSet. Rzeczywiście, nie wydaje się oczywiste, dlaczego connectionjest tu potrzebne. Jeśli używasz jOOQ, i tak będziesz mieć do dyspozycji DSLContext(wynik DSL.using(connection)lub podobny) miejsce do siedzenia.
Lukas Eder
jaka jest właściwa biblioteka JOOQ do dołączenia? Próbuję wielu, ale każdy daje mi błąd Class not found z odniesieniem DSL. Podziękowania
Lorenzo Barbagli
7

Oprócz sugestii @Jima Cooka. Inną myślą jest użycie przełącznika zamiast 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);
}
vanderwyst
źródło
4
Zapętlanie wstecz (porównywanie indeksu zero) jest również szybsze (niż porównywanie indeksu z wyrażeniem).
Dave Jarvis
4

Ta odpowiedź może nie jest najbardziej wydajna, ale z pewnością jest dynamiczna. Łącząc natywny JDBC z biblioteką Google Gson, mogę łatwo przekonwertować wynik SQL na strumień JSON.

Dołączyłem konwerter, przykładowy plik właściwości DB, generowanie tabeli SQL i plik kompilacji Gradle (z zastosowanymi zależnościami).

QueryApp.java

import java.io.PrintWriter;

import com.oracle.jdbc.ResultSetConverter;

public class QueryApp {
    public static void main(String[] args) {
        PrintWriter writer = new PrintWriter(System.out);
        String dbProps = "/database.properties";
        String indent = "    ";

        writer.println("Basic SELECT:");
        ResultSetConverter.queryToJson(writer, dbProps, "SELECT * FROM Beatles", indent, false);

        writer.println("\n\nIntermediate SELECT:");
        ResultSetConverter.queryToJson(writer, dbProps, "SELECT first_name, last_name, getAge(date_of_birth) as age FROM Beatles", indent, true);
    }
}

ResultSetConverter.java

package com.oracle.jdbc;

import java.io.*;
import java.lang.reflect.Type;
import java.sql.*;
import java.util.*;

import com.google.common.reflect.TypeToken;
import com.google.gson.GsonBuilder;
import com.google.gson.stream.JsonWriter;

public class ResultSetConverter {
    public static final Type RESULT_TYPE = new TypeToken<List<Map<String, Object>>>() {
        private static final long serialVersionUID = -3467016635635320150L;
    }.getType();

    public static void queryToJson(Writer writer, String connectionProperties, String query, String indent, boolean closeWriter) {
        Connection conn = null;
        Statement stmt = null;
        GsonBuilder gson = new GsonBuilder();
        JsonWriter jsonWriter = new JsonWriter(writer);

        if (indent != null) jsonWriter.setIndent(indent);

        try {
            Properties props = readConnectionInfo(connectionProperties);
            Class.forName(props.getProperty("driver"));

            conn = openConnection(props);
            stmt = conn.createStatement();

            gson.create().toJson(QueryHelper.select(stmt, query), RESULT_TYPE, jsonWriter);

            if (closeWriter) jsonWriter.close();

            stmt.close();
            conn.close();
        } catch (SQLException se) {
            se.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
            try {
                if (stmt != null) stmt.close();
            } catch (SQLException se2) {
            }
            try {
                if (conn != null) conn.close();
            } catch (SQLException se) {
                se.printStackTrace();
            }
            try {
                if (closeWriter && jsonWriter != null) jsonWriter.close();
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
        }
    }

    private static Properties readConnectionInfo(String resource) throws IOException {
        Properties properties = new Properties();
        InputStream in = ResultSetConverter.class.getResourceAsStream(resource);
        properties.load(in);
        in.close();

        return properties;
    }

    private static Connection openConnection(Properties connectionProperties) throws IOException, SQLException {
        String database = connectionProperties.getProperty("database");
        String username = connectionProperties.getProperty("username");
        String password = connectionProperties.getProperty("password");

        return DriverManager.getConnection(database, username, password);
    }
}

QueryHelper.java

package com.oracle.jdbc;

import java.sql.*;
import java.text.*;
import java.util.*;

import com.google.common.base.CaseFormat;

public class QueryHelper {
    static DateFormat DATE_FORMAT = new SimpleDateFormat("YYYY-MM-dd");

    public static List<Map<String, Object>> select(Statement stmt, String query) throws SQLException {
        ResultSet resultSet = stmt.executeQuery(query);
        List<Map<String, Object>> records = mapRecords(resultSet);

        resultSet.close();

        return records;
    }

    public static List<Map<String, Object>> mapRecords(ResultSet resultSet) throws SQLException {
        List<Map<String, Object>> records = new ArrayList<Map<String, Object>>();
        ResultSetMetaData metaData = resultSet.getMetaData();

        while (resultSet.next()) {
            records.add(mapRecord(resultSet, metaData));
        }

        return records;
    }

    public static Map<String, Object> mapRecord(ResultSet resultSet, ResultSetMetaData metaData) throws SQLException {
        Map<String, Object> record = new HashMap<String, Object>();

        for (int c = 1; c <= metaData.getColumnCount(); c++) {
            String columnType = metaData.getColumnTypeName(c);
            String columnName = formatPropertyName(metaData.getColumnName(c));
            Object value = resultSet.getObject(c);

            if (columnType.equals("DATE")) {
                value = DATE_FORMAT.format(value);
            }

            record.put(columnName, value);
        }

        return record;
    }

    private static String formatPropertyName(String property) {
        return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, property);
    }
}

database.properties

driver=com.mysql.jdbc.Driver
database=jdbc:mysql://localhost/JDBC_Tutorial
username=root
password=

JDBC_Tutorial.sql

-- phpMyAdmin SQL Dump
-- version 4.5.1
-- http://www.phpmyadmin.net
--
-- Host: 127.0.0.1
-- Generation Time: Jan 12, 2016 at 07:40 PM
-- Server version: 10.1.8-MariaDB
-- PHP Version: 5.6.14

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;

--
-- Database: `jdbc_tutorial`
--
CREATE DATABASE IF NOT EXISTS `jdbc_tutorial` DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci;
USE `jdbc_tutorial`;

DELIMITER $$
--
-- Functions
--
DROP FUNCTION IF EXISTS `getAge`$$
CREATE DEFINER=`root`@`localhost` FUNCTION `getAge` (`in_dob` DATE) RETURNS INT(11) NO SQL
BEGIN
DECLARE l_age INT;
   IF DATE_FORMAT(NOW(),'00-%m-%d') >= DATE_FORMAT(in_dob,'00-%m-%d') THEN
      -- This person has had a birthday this year
      SET l_age=DATE_FORMAT(NOW(),'%Y')-DATE_FORMAT(in_dob,'%Y');
   ELSE
      -- Yet to have a birthday this year
      SET l_age=DATE_FORMAT(NOW(),'%Y')-DATE_FORMAT(in_dob,'%Y')-1;
   END IF;
      RETURN(l_age);
END$$

DELIMITER ;

-- --------------------------------------------------------

--
-- Table structure for table `beatles`
--

DROP TABLE IF EXISTS `beatles`;
CREATE TABLE IF NOT EXISTS `beatles` (
  `id` int(11) NOT NULL,
  `first_name` varchar(255) DEFAULT NULL,
  `last_name` varchar(255) DEFAULT NULL,
  `date_of_birth` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

--
-- Truncate table before insert `beatles`
--

TRUNCATE TABLE `beatles`;
--
-- Dumping data for table `beatles`
--

INSERT INTO `beatles` (`id`, `first_name`, `last_name`, `date_of_birth`) VALUES(100, 'John', 'Lennon', '1940-10-09');
INSERT INTO `beatles` (`id`, `first_name`, `last_name`, `date_of_birth`) VALUES(101, 'Paul', 'McCartney', '1942-06-18');
INSERT INTO `beatles` (`id`, `first_name`, `last_name`, `date_of_birth`) VALUES(102, 'George', 'Harrison', '1943-02-25');
INSERT INTO `beatles` (`id`, `first_name`, `last_name`, `date_of_birth`) VALUES(103, 'Ringo', 'Starr', '1940-07-07');

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

build.gradle

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'application'

mainClassName = 'com.oracle.jdbc.QueryApp'

repositories {
    maven  {
        url "http://repo1.maven.org/maven2"
    }
}

jar {
    baseName = 'jdbc-tutorial'
    version =  '1.0.0'
}

sourceCompatibility = 1.7
targetCompatibility = 1.7

dependencies {
    compile 'mysql:mysql-connector-java:5.1.16'
    compile 'com.google.guava:guava:18.0'
    compile 'com.google.code.gson:gson:1.7.2'
}

task wrapper(type: Wrapper) {
    gradleVersion = '2.9'
}

Wyniki

Podstawowy SELECT

[
    {
        "firstName": "John",
        "lastName": "Lennon",
        "dateOfBirth": "1940-10-09",
        "id": 100
    },
    {
        "firstName": "Paul",
        "lastName": "McCartney",
        "dateOfBirth": "1942-06-18",
        "id": 101
    },
    {
        "firstName": "George",
        "lastName": "Harrison",
        "dateOfBirth": "1943-02-25",
        "id": 102
    },
    {
        "firstName": "Ringo",
        "lastName": "Starr",
        "dateOfBirth": "1940-07-07",
        "id": 103
    }
]

Pośredni WYBÓR

[
    {
        "firstName": "John",
        "lastName": "Lennon",
        "age": 75
    },
    {
        "firstName": "Paul",
        "lastName": "McCartney",
        "age": 73
    },
    {
        "firstName": "George",
        "lastName": "Harrison",
        "age": 72
    },
    {
        "firstName": "Ringo",
        "lastName": "Starr",
        "age": 75
    }
]
Panie Polywhirl
źródło
3

Pierwsze wstępnie wygenerowane nazwy kolumn, drugie użycie rs.getString(i)zamiast rs.getString(column_name).

Oto implementacja tego:

    /*
     * Convert ResultSet to a common JSON Object array
     * Result is like: [{"ID":"1","NAME":"Tom","AGE":"24"}, {"ID":"2","NAME":"Bob","AGE":"26"}, ...]
     */
    public static List<JSONObject> getFormattedResult(ResultSet rs) {
        List<JSONObject> resList = new ArrayList<JSONObject>();
        try {
            // get column names
            ResultSetMetaData rsMeta = rs.getMetaData();
            int columnCnt = rsMeta.getColumnCount();
            List<String> columnNames = new ArrayList<String>();
            for(int i=1;i<=columnCnt;i++) {
                columnNames.add(rsMeta.getColumnName(i).toUpperCase());
            }

            while(rs.next()) { // convert each object to an human readable JSON object
                JSONObject obj = new JSONObject();
                for(int i=1;i<=columnCnt;i++) {
                    String key = columnNames.get(i - 1);
                    String value = rs.getString(i);
                    obj.put(key, value);
                }
                resList.add(obj);
            }
        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return resList;
    }
coderz
źródło
jak uzyskać dostęp do obiektu i używać go .... powiedzieć, że chcę wydrukować całą kolumnę Name ... i czy obiekt jest łatwo dostępny dla operacji CRUD?
Prathamesh dhanawade
@Prathameshdhanawade Ta metoda ma na celu ukrycie zestawu wyników JDBC w tablicy JSON. Programiści zwykle nie używają surowego zestawu wyników JDBC, zwykle konwertują go na listę obiektów Java, czyli obiekt JSON. Widzisz, że zwracana wartość to tablica obiektów JSON. Masz łatwy dostęp do jednego obiektu poprzez JSONObject json = resList.get(i);Następnie możesz swobodnie manipulować obiektem JSON json.
coderz
Właściwie będę potrzebował obiektu do przygotowania widoku graficznego. Byłem więc po prostu ciekawy, czy mógłbym manipulować obiektem. Thnx.
Prathamesh dhanawade
Czy mógłbyś opisać „widok graficzny”? A może podano przykład?
coderz
Próbuję wyświetlić wartość kolumny jako: JSONObject obj = jsonList.get (1); System.out.println (obj.getString ("nazwa") + "\ t" + obj.getString ("firma")); ale wyświetlany jest błąd jako „org.json.JSONException: JSONObject [„ nazwa ”] nie został znaleziony.”
Prathamesh dhanawade
2

Jeśli ktoś planuje skorzystać z tej implementacji, możesz sprawdzić to i to

To jest moja wersja tego kodu konwersji:

public class ResultSetConverter {
public static JSONArray convert(ResultSet rs) throws SQLException,
        JSONException {
    JSONArray json = new JSONArray();
    ResultSetMetaData rsmd = rs.getMetaData();
    int numColumns = rsmd.getColumnCount();
    while (rs.next()) {

        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.getLong(column_name));
            } else if (rsmd.getColumnType(i) == java.sql.Types.REAL) {
                obj.put(column_name, rs.getFloat(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.getDouble(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.CHAR) {
                obj.put(column_name, rs.getString(column_name));
            } else if (rsmd.getColumnType(i) == java.sql.Types.NCHAR) {
                obj.put(column_name, rs.getNString(column_name));
            } else if (rsmd.getColumnType(i) == java.sql.Types.LONGNVARCHAR) {
                obj.put(column_name, rs.getNString(column_name));
            } else if (rsmd.getColumnType(i) == java.sql.Types.LONGVARCHAR) {
                obj.put(column_name, rs.getString(column_name));
            } else if (rsmd.getColumnType(i) == java.sql.Types.TINYINT) {
                obj.put(column_name, rs.getByte(column_name));
            } else if (rsmd.getColumnType(i) == java.sql.Types.SMALLINT) {
                obj.put(column_name, rs.getShort(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.TIME) {
                obj.put(column_name, rs.getTime(column_name));
            } else if (rsmd.getColumnType(i) == java.sql.Types.TIMESTAMP) {
                obj.put(column_name, rs.getTimestamp(column_name));
            } else if (rsmd.getColumnType(i) == java.sql.Types.BINARY) {
                obj.put(column_name, rs.getBytes(column_name));
            } else if (rsmd.getColumnType(i) == java.sql.Types.VARBINARY) {
                obj.put(column_name, rs.getBytes(column_name));
            } else if (rsmd.getColumnType(i) == java.sql.Types.LONGVARBINARY) {
                obj.put(column_name, rs.getBinaryStream(column_name));
            } else if (rsmd.getColumnType(i) == java.sql.Types.BIT) {
                obj.put(column_name, rs.getBoolean(column_name));
            } else if (rsmd.getColumnType(i) == java.sql.Types.CLOB) {
                obj.put(column_name, rs.getClob(column_name));
            } else if (rsmd.getColumnType(i) == java.sql.Types.NUMERIC) {
                obj.put(column_name, rs.getBigDecimal(column_name));
            } else if (rsmd.getColumnType(i) == java.sql.Types.DECIMAL) {
                obj.put(column_name, rs.getBigDecimal(column_name));
            } else if (rsmd.getColumnType(i) == java.sql.Types.DATALINK) {
                obj.put(column_name, rs.getURL(column_name));
            } else if (rsmd.getColumnType(i) == java.sql.Types.REF) {
                obj.put(column_name, rs.getRef(column_name));
            } else if (rsmd.getColumnType(i) == java.sql.Types.STRUCT) {
                obj.put(column_name, rs.getObject(column_name)); // must be a custom mapping consists of a class that implements the interface SQLData and an entry in a java.util.Map object.
            } else if (rsmd.getColumnType(i) == java.sql.Types.DISTINCT) {
                obj.put(column_name, rs.getObject(column_name)); // must be a custom mapping consists of a class that implements the interface SQLData and an entry in a java.util.Map object.
            } else if (rsmd.getColumnType(i) == java.sql.Types.JAVA_OBJECT) {
                obj.put(column_name, rs.getObject(column_name));
            } else {
                obj.put(column_name, rs.getString(i));
            }
        }

        json.put(obj);
    }

    return json;
}
}
Li3ro
źródło
2

Podobnie jak pojedynek, pętla if / then jest wydajniejsza niż przełącznik dla wyliczeń. Jeśli masz przełącznik względem surowej liczby całkowitej wyliczenia, jest to bardziej wydajne, ale w stosunku do zmiennej, jeśli / to jest bardziej wydajne, przynajmniej dla Java 5, 6 i 7.

To znaczy z jakiegoś powodu (po kilku testach wydajności)

if (ordinalValue == 1) {
   ...
} else (ordinalValue == 2 {
   ... 
}

jest szybszy niż

switch( myEnum.ordinal() ) {
    case 1:
       ...
       break;
    case 2:
       ...
       break;
}

Widzę, że kilka osób wątpi we mnie, więc wyślę tutaj kod, który pozwoli Ci uruchomić samodzielnie, aby zobaczyć różnicę, wraz z danymi wyjściowymi z Javy 7. Wyniki poniższego kodu z 10 wartościami wyliczenia są następujące. Zauważ, że kluczem jest tutaj jeśli / to przy użyciu wartości całkowitej porównanej ze stałymi porządkowymi wyliczenia, w porównaniu z przełącznikiem z wartością porządkową wyliczenia względem surowych wartości porządkowych wewnętrznych, a przełącznikiem z wyliczeniem względem każdej nazwy wyliczenia. Jeśli / to z wartością całkowitą pokonało oba inne przełączniki, chociaż ostatni przełącznik był nieco szybszy niż pierwszy, nie był szybszy niż if / else.

Jeśli / else zajęło 23 ms, zmiana
zajęła 45 ms, zmiana
2 zajęła 30 ms.
Łączna liczba dopasowań: 3000000

package testing;

import java.util.Random;

enum TestEnum {
    FIRST,
    SECOND,
    THIRD,
    FOURTH,
    FIFTH,
    SIXTH,
    SEVENTH,
    EIGHTH,
    NINTH,
    TENTH
}

public class SwitchTest {
    private static int LOOP = 1000000;
    private static Random r = new Random();
    private static int SIZE = TestEnum.values().length;

    public static void main(String[] args) {
        long time = System.currentTimeMillis();
        int matches = 0;
        for (int i = 0; i < LOOP; i++) {
            int j = r.nextInt(SIZE);
            if (j == TestEnum.FIRST.ordinal()) {
                matches++;
            } else if (j == TestEnum.SECOND.ordinal()) {
                matches++;
            } else if (j == TestEnum.THIRD.ordinal()) {
                matches++;
            } else if (j == TestEnum.FOURTH.ordinal()) {
                matches++;
            } else if (j == TestEnum.FIFTH.ordinal()) {
                matches++;
            } else if (j == TestEnum.SIXTH.ordinal()) {
                matches++;
            } else if (j == TestEnum.SEVENTH.ordinal()) {
                matches++;
            } else if (j == TestEnum.EIGHTH.ordinal()) {
                matches++;
            } else if (j == TestEnum.NINTH.ordinal()) {
                matches++;
            } else {
                matches++;
            }
        }
        System.out.println("If / else took "+(System.currentTimeMillis() - time)+" ms");
        time = System.currentTimeMillis();
        for (int i = 0; i < LOOP; i++) {
            TestEnum te = TestEnum.values()[r.nextInt(SIZE)];
            switch (te.ordinal()) {
                case 0:
                    matches++;
                    break;
                case 1:
                    matches++;
                    break;
                case 2:
                    matches++;
                    break;
                case 3:
                    matches++;
                    break;
                case 4:
                    matches++;
                    break;
                case 5:
                    matches++;
                    break;
                case 6:
                    matches++;
                    break;
                case 7:
                    matches++;
                    break;
                case 8:
                    matches++;
                    break;
                case 9:
                    matches++;
                    break;
                default:
                    matches++;
                    break;
            }
        }
        System.out.println("Switch took "+(System.currentTimeMillis() - time)+" ms");
        time = System.currentTimeMillis();
        for (int i = 0; i < LOOP; i++) {
            TestEnum te = TestEnum.values()[r.nextInt(SIZE)];
            switch (te) {
                case FIRST:
                    matches++;
                    break;
                case SECOND:
                    matches++;
                    break;
                case THIRD:
                    matches++;
                    break;
                case FOURTH:
                    matches++;
                    break;
                case FIFTH:
                    matches++;
                    break;
                case SIXTH:
                    matches++;
                    break;
                case SEVENTH:
                    matches++;
                    break;
                case EIGHTH:
                    matches++;
                    break;
                case NINTH:
                    matches++;
                    break;
                default:
                    matches++;
                    break;
            }
        }
        System.out.println("Switch 2 took "+(System.currentTimeMillis() - time)+" ms");     
        System.out.println("Total matches: "+matches);
    }
}
Marcus
źródło
Cóż, ok, ale ... prawda jest taka, że ​​różnica między tymi alternatywami kodowania dla każdego rzeczywistego przypadku użycia jest tak mała, że ​​w rzeczywistej aplikacji różnice w wydajności między nimi byłyby prawdopodobnie niezmiernie małe. Najpierw popraw kod, a następnie (tylko wtedy) zrób go szybko (jeśli ma być szybszy).
scottb
Zgoda, ale główne pytanie dotyczyło najszybszego i najbardziej efektywnego sposobu. Istnieje wiele stylów, które sprawiają, że kod jest trudny do odczytania lub utrzymania, ale są one bardziej wydajne, chociaż zazwyczaj są one rozwiązywane przez optymalizacje kompilatora w pewnym momencie w przyszłości. Jednym z przykładów jest użycie intern()for string, które w większości nie są już potrzebne w większości nowoczesnych wersji Javy.
Marcus,
Zgoda, ale jeśli-to-inaczej i same bloki przełączników są kiepskimi rozwiązaniami problemów związanych z konserwacją, solidnością i czytelnością w każdym przypadku. W przypadku wyliczeń, których jesteś właścicielem (lub których kod możesz modyfikować), najlepszą praktyką jest bardzo często używanie metod specyficznych dla stałych w bogatym typie wyliczenia.W większości przypadków włączanie wyliczenia powinno być traktowane ostrożnie, ponieważ są lepsze rozwiązania.
scottb
1

Dla wszystkich, którzy zdecydowali się na rozwiązanie if-else mesh, użyj:

String columnName = metadata.getColumnName(
String displayName = metadata.getColumnLabel(i);
switch (metadata.getColumnType(i)) {
case Types.ARRAY:
    obj.put(displayName, resultSet.getArray(columnName));
    break;
...

Ponieważ w przypadku aliasów w zapytaniu nazwa kolumny i etykieta kolumny to dwie różne rzeczy. Na przykład, jeśli wykonasz:

select col1, col2 as my_alias from table

Dostaniesz

[
    { "col1": 1, "col2": 2 }, 
    { "col1": 1, "col2": 2 }
]

Zamiast:

[
    { "col1": 1, "my_alias": 2 }, 
    { "col1": 1, "my_alias": 2 }
]
seekme_94
źródło
0
package com.idal.cib;

import java.io.FileWriter;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;

public class DBJsonConverter {

    static ArrayList<String> data = new ArrayList<String>();
    static Connection conn = null;
    static PreparedStatement ps = null;
    static ResultSet rs = null;
    static String path = "";
    static String driver="";
    static String url="";
    static String username="";
    static String password="";
    static String query="";

    @SuppressWarnings({ "unchecked" })
    public static void dataLoad(String path) {
        JSONObject obj1 = new JSONObject();
        JSONArray jsonArray = new JSONArray();
        conn = DatabaseConnector.getDbConnection(driver, url, username,
                password);
        try {
            ps = conn.prepareStatement(query);
            rs = ps.executeQuery();
            ArrayList<String> columnNames = new ArrayList<String>();
            if (rs != null) {
                ResultSetMetaData columns = rs.getMetaData();
                int i = 0;
                while (i < columns.getColumnCount()) {
                    i++;
                    columnNames.add(columns.getColumnName(i));
                }
                while (rs.next()) {
                    JSONObject obj = new JSONObject();
                    for (i = 0; i < columnNames.size(); i++) {
                        data.add(rs.getString(columnNames.get(i)));
                        {
                            for (int j = 0; j < data.size(); j++) {
                                if (data.get(j) != null) {
                                    obj.put(columnNames.get(i), data.get(j));
                                }else {
                                    obj.put(columnNames.get(i), "");
                                }
                            }
                        }
                    }

                    jsonArray.add(obj);
                    obj1.put("header", jsonArray);
                    FileWriter file = new FileWriter(path);
                    file.write(obj1.toJSONString());
                    file.flush();
                    file.close();
                }
                ps.close();
            } else {
                JSONObject obj2 = new JSONObject();
                obj2.put(null, null);
                jsonArray.add(obj2);
                obj1.put("header", jsonArray);
            }
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                    rs.close();
                    ps.close();
                } catch (SQLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }

    @SuppressWarnings("static-access")
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        driver = "oracle.jdbc.driver.OracleDriver";
        url = "jdbc:oracle:thin:@localhost:1521:database";
        username = "user";
        password = "password";
        path = "path of file";
        query = "select * from temp_employee";

        DatabaseConnector dc = new DatabaseConnector();
        dc.getDbConnection(driver,url,username,password);
        DBJsonConverter formatter = new DBJsonConverter();
        formatter.dataLoad(path);

    }

}




package com.idal.cib;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DatabaseConnector {

    static Connection conn1 = null;

    public static Connection getDbConnection(String driver, String url,
            String username, String password) {
        // TODO Auto-generated constructor stub
        try {

            Class.forName(driver);

            conn1 = DriverManager.getConnection(url, username, password);
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return conn1;
    }

}
Prashant Chilwant
źródło
Pl dodaj wyjaśnienie do swojej odpowiedzi.
Sahil Mittal,
0
public static JSONArray GetJSONDataFromResultSet(ResultSet rs) throws SQLException {
    ResultSetMetaData metaData = rs.getMetaData();
    int count = metaData.getColumnCount();
    String[] columnName = new String[count];
    JSONArray jsonArray = new JSONArray();
    while(rs.next()) {
        JSONObject jsonObject = new JSONObject();
        for (int i = 1; i <= count; i++){
               columnName[i-1] = metaData.getColumnLabel(i);
               jsonObject.put(columnName[i-1], rs.getObject(i));
        }
        jsonArray.put(jsonObject);
    }
    return jsonArray;
}
hitesh bariya
źródło
-1

w drugą stronę, tutaj użyłem ArrayList i Map, więc nie jest to wywołanie obiektu json wiersz po wierszu, ale po zakończeniu iteracji zestawu wyników:

 List<Map<String, String>> list = new ArrayList<Map<String, String>>();

  ResultSetMetaData rsMetaData = rs.getMetaData();  


      while(rs.next()){

              Map map = new HashMap();
              for (int i = 1; i <= rsMetaData.getColumnCount(); i++) {
                 String key = rsMetaData.getColumnName(i);

                  String value = null;

               if (rsmd.getColumnType(i) == java.sql.Types.VARCHAR) {
                           value = rs.getString(key);
               } else if(rsmd.getColumnType(i)==java.sql.Types.BIGINT)                         
                             value = rs.getLong(key);
               }                  


                    map.put(key, value);
              }
              list.add(map);


    }


     json.put(list);    
reil bhudaya
źródło