2012-06-26 16 views
9

SQL SELECTクエリの結果をネットワーク経由で結果を送信する別のシステムのInputStreamパラメータとして返すJava関数が必要です。Java SQL Result to InputStream

ただし、InputStreamは、カスタム区切り文字(通常はCSVとは限りません)を使用してStringにする必要があります。

私は簡単に結果を取得するための関数を作成することができますが、区切りStringを作成し、最終的にStringInputStreamに、SQLの結果は、多くの場合、メモリ内で処理するのに非常に大きくなりすぎることを変換します。また、結果を返す前に結果セット全体を処理すると、不要な待機時間が発生します。

InputStreamを返して、SQL結果を反復処理し、処理された(区切られた)データをデータベースから返されるように送信するにはどうすればよいですか?あなたの基本的なアイデアを与える必要があり

+0

jdbcキャッシュセットを使用してみましたか?それはあなたがやろうとしていることに役立つかもしれません。 http://docs.oracle.com/javase/1.5.0/docs/api/javax/sql/rowset/CachedRowSet.html – ChadNC

+0

いいえ、どうすればそれが役に立ちますか?問題は、接続を開いたままではなく、結果をメモリに保存していることです。 –

+0

これは、キャッシュされた行セットと同じものです。ネットワーク経由で他のデバイス、アプリケーションなどにクエリの結果を送信する簡単な方法を提供します。 – ChadNC

答えて

8

ポスティング(テストされていない)のコードスニペットは、:

/** 
* Implementors of this interface should only convert current row to byte array and return it. 
* 
* @author yura 
*/ 
public interface RowToByteArrayConverter { 
    byte[] rowToByteArray(ResultSet resultSet); 
} 

public class ResultSetAsInputStream extends InputStream { 

    private final RowToByteArrayConverter converter; 
    private final PreparedStatement statement; 
    private final ResultSet resultSet; 

    private byte[] buffer; 
    private int position; 

    public ResultSetAsInputStream(final RowToByteArrayConverter converter, final Connection connection, final String sql, final Object... parameters) throws SQLException { 
     this.converter = converter; 
     statement = createStatement(connection, sql, parameters); 
     resultSet = statement.executeQuery(); 
    } 

    private static PreparedStatement createStatement(final Connection connection, final String sql, final Object[] parameters) { 
     // PreparedStatement should be created here from passed connection, sql and parameters 
     return null; 
    } 

    @Override 
    public int read() throws IOException { 
     try { 
      if(buffer == null) { 
       // first call of read method 
       if(!resultSet.next()) { 
        return -1; // no rows - empty input stream 
       } else { 
        buffer = converter.rowToByteArray(resultSet); 
        position = 0; 
        return buffer[position++] & (0xff); 
       } 
      } else { 
       // not first call of read method 
       if(position < buffer.length) { 
        // buffer already has some data in, which hasn't been read yet - returning it 
        return buffer[position++] & (0xff); 
       } else { 
        // all data from buffer was read - checking whether there is next row and re-filling buffer 
        if(!resultSet.next()) { 
         return -1; // the buffer was read to the end and there is no rows - end of input stream 
        } else { 
         // there is next row - converting it to byte array and re-filling buffer 
         buffer = converter.rowToByteArray(resultSet); 
         position = 0; 
         return buffer[position++] & (0xff); 
        } 
       } 
      } 
     } catch(final SQLException ex) { 
      throw new IOException(ex); 
     } 
    } 



    @Override 
    public void close() throws IOException { 
     try { 
      statement.close(); 
     } catch(final SQLException ex) { 
      throw new IOException(ex); 
     } 
    } 
} 

これは非常に単純で実装され、それは次の方法で改善することができます。

  • コードreadメソッドのifとelseとの間の重複を取り除くことができます - 各行のバイト配列バッファを再作成する代わりに、
  • のように投稿されました(new byte[]は高価なオペラですより洗練されたロジックを実装して、バイトアレイバッファを使用することができます。バイトアレイバッファは、一度だけ初期化されてから再充填されます。 1つはint fillByteArrayFromRow(ResultSet rs, byte[] array)RowToByteArrayConverter.rowToByteArrayメソッドの署名を変更する必要があります。この場合、埋められたバイト数が返され、渡されたバイト配列が埋められます。

バイト配列は、署名されたバイトが含まれているので(実際255符号なしバイトなどである)-1を含有し、したがって、そう& (0xff)を整数値として、符号なしバイトに符号付きバイトを変換するために使用される、ストリームの不正確な終了を示すことができます。詳細はHow does Java convert int into byte?を参照してください。

ネットワークの転送速度が遅い場合、これによりオープン結果が になる可能性があり、データベースに問題が発生する可能性があります。

希望これは、私は次のように導入することで、@Yuraによって提案された答えを改善するだろう...

2

を支援します。便利なバイト配列にデータを書き込むためにByteArrayOutputStreamを使用して初期化され
使用DataOutputStreamを、 RowToByteArrayConverterの実装内、今、あなたを

public abstract class RowToByteArrayConverter { 
    public byte[] rowToByteArray(ResultSet resultSet) { 
     parseResultSet(dataOutputStream, resultSet); 
     return byteArrayOutputSteam.toByteArray(); 
    } 

    public RowToByteArrayConverter() { 
    dataOutputStream = new DataOutputStream(byteArrayOutputStream); 
    } 

    protected DataOutputStream dataOutputStream; 
    protected ByteArrayOutputStream byteArrayOutputStream; 

    protected abstract void parseResultSet(DataOutputStream dataOutputStresm, ResultSet rs); 
} 

を -
実際に、私はそれらのすべては、(最初​​の時からコンパイルされない可能性がある、これは私のアイデアのコードスニペットである)同じ抽象クラスを拡張し、コンバータの階層構造を持つことを示唆していますparseResultSetメソッドを単にオーバーライドすることでこのクラスをオーバーライドすることができます。例えば、
- レコードの列 "name"からStringとして名前を取得するコードを記述します。 DataOutputStreamにwriteUTF8を実行します。

0

上記の答えは、制限されたサイズのストリングビルダが超過しているという問題に対する便利な解決策を提供します。彼らはまた、メモリ効率的です。しかし、私のテストは、入力ストリームを取得するために

彼らはただのStringBuilderにデータを書き込み、

新れるByteArrayInputStream(data.getBytes(「UTF-8」))を呼び出すよりも低速であることを示唆しています。

私ははるかに多くのパフォーマンスであることが判明すると、パーティション機能を使用して、それぞれに複数のスレッドを使用して、着信データをスライスすることです:

  1. クエリ
  2. データのサブセットのソース・データベースデータをターゲットに書き込む

これにより、合計データが文字列バッファの最大サイズを超えることができないという問題も回避されます。

たとえば、SQL Serverテーブルに「RecordDate」という列のある6mレコードがあります。 Recorddateの値は2013年と2016年の間で異なります。したがって、それぞれのスレッドがそれぞれ2013,14,15,16のデータを要求するように設定します。次に、各スレッドは、上記のようにgetBytes()を使用してInputStreamに変換することによって、トランスコードされたデータをStringBuilderに書き込み、それぞれのバルクロードをターゲットに書き込みます。

この結果、2倍のスピードアップとなりました。

なぜですか?ソース・データベースとターゲット・データベースは複数の同時リクエストを処理できるため、全体の作業負荷は、ソース・データベース、トランスコーダ、ターゲット・データベースの3つのプロセスすべてで複数のスレッドに分散されます。