JDBC インターフェイスの詳細

ここでは、Denodo の JDBC ドライバーに固有の情報について説明します。

ビューとそのフィールドの説明

このドライバーは、ビューとそのフィールドの説明を、テーブルまたはビュー、およびそのフィールドのメタデータの [REMARKS] 列に公開します。

blob 値のコンテンツタイプの取得

このドライバーは、blob フィールドの コンテンツタイプ を使用できるようにします。次の例に、その方法を示します。

blob 値のコンテンツタイプの取得
ResultSet rs = stmt.executeQuery(...);
...
com.denodo.vdb.jdbcdriver.VDBJDBCBlob blob =
    (com.denodo.vdb.jdbcdriver.VDBJDBCBlob) rs.getBlob(index);
String contentType = blob.getContentType();

Denodo JDBC ドライバーを使用した日時値の操作

以下のサブセクションでは、JDBC ドライバーを使用してさまざまな日時値を操作する方法について説明します。

準備済みステートメントのパラメータでの日時値の設定

次の表に、パラメータ (「?」) の値を設定するために呼び出す必要がある PreparedStatement クラスのメソッドを、値の型ごとに示します。

PreparedStatement で日時パラメータを設定するためのメソッド

PreparedStatement でその型のパラメータを設定するためのメソッド

localdate

以下のいずれか

  • PreparedStatement.setObject(java.time.LocalDate)

  • PreparedStatement.setDate(java.sql.Date)

例:

// Creating a LocalDate object
setObject(1, java.time.LocalDate.of(2018,01,15));

// Creating a Date object
setDate(1, java.sql.Date.valueOf("2018-01-15"));

java.sql.Date クラスのドキュメントによれば、「setDate」を使用する場合、「Date」オブジェクトを「正規化」する必要があります。そのためには、インスタンスが関連付けられている特定のタイムゾーンの時間、分、秒、およびミリ秒を 0 に設定します。そのため、LocalDate オブジェクトの方が簡単に使用できます。

time

以下のいずれか

  • PreparedStatement.setObject(java.time.LocalTime)

  • PreparedStatement.setTime(java.sql.Time)

例:

// Creating a LocalTime object
setObject(1, LocalTime.of(11, 58, 59, 123000000));

// Creating a java.sql.Time object
setTime(2, java.sql.Time.valueOf("11:58:59"));

java.sql.Time クラスのドキュメントによれば、「setTime」を使用する場合、Time オブジェクトの日付コンポーネントを、1970 年 1 月 1 日の「ゼロエポック」値に設定する必要があります。そのため、LocalTime オブジェクトの方が簡単に使用できます。

timestamp

PreparedStatement.setObject(java.time.LocalDateTime)

例:

setObject(1, java.time.LocalDateTime.of(
    2018, 01, 15, 23, 58, 59, 256000000))

最後のパラメータは、値がナノ秒単位であるため 256 ミリ秒を表します。Denodo では、timestamp、timestamptz、および time の最大精度はナノ秒ではなくミリ秒です。

PreparedStatement.setTimeStamp() を使用する場合は、timestamptz 値を指定する必要があります。そうしないと、クエリに timestamptz から timestamp へのキャストがある場合を除き、クエリは失敗します。また、キャストがある場合でも、それが機能するには、コネクション URI のパラメータ i18n が Denodo サーバーの i18n 設定と一致する必要があります。

date (非推奨)

timestamptz と同じ (以下を参照)

timestamptz

以下のいずれか

  • PreparedStatement.setObject(java.time.OffsetDateTime)

  • PreparedStatement.setTimestamp(java.sql.Timestamp)

例:

// Creating an OffsetDateTime object
setObject(1, OffsetDateTime.parse(
    "2018-01-01T21:15:00+01:00"))

// Creating a Timestamp object
SimpleDateFormat sdf =
    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss XXX");
sdf.setTimeZone("GMT");
setTimestamp(
      1
    , sdf.parse("1982-12-13 01:59:59 +0000"));

interval_year_month

setObject(java.time.Period)

例:

// Equivalent to INTERVAL '145-11' YEAR TO MONTH
setObject(1, Period.ofYears(145).plusMonths(11));

// Equivalent to INTERVAL '145' YEAR
setObject(Period.ofYears (145));

interval_day_second

setObject(java.time.Duration)

例:

// Equivalent to INTERVAL '4 5:12' DAY TO MINUTE
setObject(Duration.ofDays(4).plusHours(5).plusMinutes(12));

// Equivalent to INTERVAL '4 5:12:10.222' DAY TO SECOND
setObject(Duration.parse("P4DT5H12M10.222S"));

ドライバーが日時型と時間間隔型をレポートする方法

次の表に、JDBC ドライバーが各日時型をレポートする方法を示します。

Denodo の型名

ResultSetMetaData.getColumnTypeName() メソッドで返される型名

ResultSetMetaData.getColumnType(int) メソッドで返される値

localdate

DATE

91

time

TIME

92

timestamp

TIMESTAMP

93

date (非推奨)

TIMESTAMP_WITH_TIMEZONE

2014

timestamptz

TIMESTAMP_WITH_TIMEZONE

2014

interval_year_month

INTERVAL_YEAR_MONTH

2020

interval_day_second

INTERVAL_DAY_SECOND

2021

date 型および timestamptz 型は同じ型 (TIMESTAMP WITH TIMEZONE) でレポートされるため、クライアントアプリケーションはこれらを区別できません。これは、Denodo 6.0 以前のバージョンから Denodo 8.0 へのアップグレードを容易にするための意図的な動作です。クライアントアプリケーションはこれらの型を区別する必要はなく、どちらも timestamptz として扱います。

interval_year_month 型および interval_day_second 型は JDBC API に含まれていないため、それらのコードは Denodo によって定義されています。


Denodo の型名

ResultSetMetaData.getColumnClassName(int) の結果

ResultSet.getObject() クラスによって返されるオブジェクトの Java クラス

localdate

java.sql.Date

java.sql.Date

time

java.sql.Time

java.sql.Time

timestamp

java.sql.Timestamp

java.sql.Timestamp

date (非推奨)

java.sql.Timestamp

java.sql.Timestamp

timestamptz

java.sql.Timestamp

java.sql.Timestamp

interval_year_month

java.lang.Long

java.lang.Long

この値を java.time.Period オブジェクトに変換するには、 java.time.Period.ofMonths (value) を呼び出します。

ドライバーから Duration オブジェクトを取得するには、 ResultSet.getObject(col, java.time.Period.class) を呼び出します。

interval_day_second

java.lang.Long

java.lang.Long

この値を java.time.Duration オブジェクトに変換するには、 java.time.Duration. ofMillis(value) を呼び出します。

ドライバーから Duration オブジェクトを取得するには、 ResultSet.getObject(col, java.time.Duration.class) を呼び出します。

Struct (レジスター) 内部のエレメント名の取得

Denodo の JDBC ドライバーは、複合値を JDBC API のクラスに変換します。

  • register 型の値を java.sql.Struct オブジェクトに変換します。

  • array 型の値を java.sql.Array オブジェクトに変換します。

  • java.sql.Array オブジェクトは Struct オブジェクトの配列です。

標準の JDBC API では、 java.sql.Struct オブジェクトの内部 (register フィールドの内部など) にある値を取得するメソッドが提供されています。しかし、 Struct のサブフィールドの名前を取得する方法や、サブフィールドの名前でこれらの値を取得する方法は提供されていません。

ここでは、Denodo JDBC ドライバーを使用して以下のことを行う方法について説明します。

  1. Struct オブジェクトのサブフィールドの名前を取得する

  2. サブフィールドの値を register の内部の位置ではなくその名前で取得する

たとえば、アプリケーションで last_name サブフィールドと first_name サブフィールドが含まれる register フィールドを返すクエリを実行するとします。行ごとに、結果セットは Struct オブジェクトを返します。各 Struct オブジェクトの値を取得するには、アプリケーションは Struct.getAttributes() メソッドを呼び出す必要があります。このメソッドは、姓と名の 2 つの値の配列を返します。その後、このレジスターを変更してサブフィールド (たとえば telephone) を追加した場合、 Struct.getAttributes() によって返される配列には、2 つではなく 3 つのエレメントが含まれます。また、配列の最初のエレメントが名ではなく電話番号になっている場合、アプリケーションは無効なデータを取得します。

こうした保全性の問題を回避するには、Denodo JDBC API のクラスを使用して Struct の値をレジスター内の位置ではなく名前で取得します。そうすることで、変更に対するアプリケーションの堅牢性が向上します。

次の例に、これを行う方法を示します。

Struct オブジェクト内部の値の名前の取得
 import com.denodo.vdb.jdbcdriver.printer.Field;
 import com.denodo.vdb.jdbcdriver.VDBJDBCResultSetMetaData;
 import com.denodo.vdb.vdbinterface.common.clientResult.vo.descriptions.type.RegisterVO;
 import com.denodo.vdb.vdbinterface.common.clientResult.vo.descriptions.type.RegisterValueVO;

 ...

 public static void main(String[] args)
         throws Exception {

     /*
      * The method getConnection() returns a Connection to Virtual
      * DataPort
      */
     Connection connection = getConnection();
     Statement st = connection.createStatement();
     String query = "SELECT * FROM view_with_compound_fields";
     ResultSet rs = st.executeQuery(query);

     /*
      * The classes 'VDBJDBCResultSetMetaData' and 'Field' are part
      * of the Denodo JDBC API. They do not belong to the standard
      * JDBC API.
      */
     VDBJDBCResultSetMetaData metaData =
             (VDBJDBCResultSetMetaData) rs.getMetaData();
     Field[] fields = metaData.getFields();
     while (rs.next()) {

         int columnCount = metaData.getColumnCount();
         for (int i = 1; i <= columnCount; i++) {

             Object value = rs.getObject(i);
             if (value != null) {

                 if (metaData.getColumnType(i) == Types.STRUCT) {
                     /*
                      * The JDBC API represents the values of type
                      * 'register' as 'Struct' objects.
                      */

                     /*
                      * The classes 'RegisterVO' and
                      * 'RegisterValueVO' are part of the Denodo JDBC
                      * API. They do not belong to the standard Java
                      * API.
                      */
                     RegisterVO vdpType =
                        ((RegisterVO) fields[i - 1].getVdpType());
                     List<RegisterValueVO> registerSubTypes =
                         vdpType.getElements();
                     Struct struct = (Struct) value;
                     Object[] structValues = struct.getAttributes();
                     String firstName = null, lastName = null;
                     for (int j=0; j < registerSubTypes.size(); j++) {
                         /*
                          * The variable 'registerSubTypes'
                          * contains the names of the names of the
                          * subfields.
                          */

                         String subFieldName =
                             registerSubTypes.get(j).getName();
                         switch (subFieldName) {
                         case "first_name":
                             firstName = (String) structValues[j];
                             break;

                         case "last_name":
                             lastName = (String) structValues[j];
                             break;
                         }
                         /*
                          * ...
                          */
                     }
                 } else if (metaData.getColumnType(i)==Types.ARRAY) {
                     /*
                      * The JDBC API represents the values of type
                      * 'array' as 'Array' objects.
                      */
                     Object[] register =
                         (Object[]) rs.getArray(i).getArray();
                     for (Object o : register) {
                         /*
                          * In the Denodo JDBC API, the content of an
                          * 'Array' is an array of 'Struct' objects.
                          */

                         Struct s = (Struct) o;
                         /*
                          * ...
                          */
                     }
                 } // else ...
             }
         }
     }

     /*
      * Close ResultSet, Statement and Connection.
      */
 }

受信シリアライズデータをフィルタするアプリケーションからの接続

Denodo JDBC ドライバーは、Java Remote Method Invocation (RMI) を使用して Virtual DataPort に接続します。一部のクライアントアプリケーション (Oracle WebLogic Server の最新バージョンなど) には、RMI を使用して受信したデータをフィルタするメカニズムが組み込まれています。このメカニズムを使用すると、クライアントアプリケーションのセキュリティが強化されます。しかし、アプリケーションにこのフィルタがデフォルトで構成されていると、Denodo からのデータを受信できなくなる場合があります。

このフィルタが有効になっているアプリケーションから Denodo に接続する場合、アプリケーションの管理者は、このフィルタを制御するパラメータに次の文字列を追加する必要があります。

com.denodo.**

このフィルタの設定は、クライアントアプリケーションによって異なります。

この詳細については、公式の仕様である「 JEP 290: Filter Incoming Serialization Data 」を参照してください。

日時リテラルを使用したクエリ

JDBC ステートメントでエスケープ処理が有効化されている場合、Denodo JDBC ドライバーはタイムスタンプ、日付、および時刻リテラルの JDBC エスケープ構文を、タイムスタンプの正しい VQL 構文と同等と解釈します。つまり、タイムスタンプリテラルの構文は次のとおりであり、

{ts 'yyyy-mm-dd hh:mm:ss.f...'}

日付リテラルの構文は次のとおりであり、

{d 'yyyy-mm-dd'}

時刻リテラルの構文は次のとおりです。

{t 'hh:mm:ss'}

たとえば、次のクエリ

SELECT *
FROM my_view
WHERE time_field = {t '20:00:03'}
  AND date_field = {d '1999-01-09'}
      AND timestamp_field = {ts '1999-01-09 20:11:11.123455'}

は、JDBC ドライバーによって処理され、次のように Virtual DataPort に送信されます。

SELECT *
FROM my_view
WHERE time_field = TIME '20:00:03'
  AND date_field = DATE '1999-01-09'
      AND timestamp_field = TIMESTAMP '1999-01-09 20:11:11.123455'

IDU ステートメントによる自動生成キーの取得

JDBC ドライバーは、基盤となるデータベースが行を挿入する際に生成する値の取得をサポートしています。多くの場合、自動生成される値は新しい行のプライマリキーです。

例:

Denodo JDBC ドライバーを使用した自動生成キーの取得
Statement stmt = conn.createStatement();
/* With "Statement.RETURN_GENERATED_KEYS", this code requests Virtual DataPort
   to return the auto-generated values. */
int rows = stmt.executeUpdate("INSERT INTO customer " +
                              "(first_name, last_name) " +
                              "VALUES ('John', 'Smith')",
                              Statement.RETURN_GENERATED_KEYS);
ResultSet rs = stmt.getGeneratedKeys();
boolean b = rs.next();
if (b == true) {
    // Retrieve the new key value
    Long customerId = rs.getLong(1);
...
}

自動生成キーを取得するには、以下のいずれかのメソッドを使用して、IDU ステートメント (「INSERT」「UPDATE」「DELETE」) を実行する必要があります。

  • boolean execute​(String sql, int autoGeneratedKeys) (2 番目の引数は「Statement.RETURN_GENERATED_KEYS」とする)

  • boolean execute​(String sql, int[] columnIndexes)

  • boolean execute​(String sql, String[] columnNames)

  • int executeUpdate​(String sql, int autoGeneratedKeys) (2 番目の引数は「Statement.RETURN_GENERATED_KEYS」とする)

  • int executeUpdate​(String sql, int[] columnIndexes)

  • int executeUpdate​(String sql, String[] columnNames)

  • long executeLargeBatch​(String sql, int autoGeneratedKeys) (2 番目の引数は「Statement.RETURN_GENERATED_KEYS」とする)

  • long executeLargeBatch​(String sql, int[] columnIndexes)

  • long executeLargeBatch​(String sql, String[] columnNames)

java.sql.Statement クラスまたは java.sql.PreparedStatement クラスを使用できます。

Virtual DataPort 自体はデータを保存しないため、この機能は基盤となるデータベースのサポートに依存します。すべてのデータベースベンダーがこの機能を実装しているわけではありません。たとえば、アプリケーションが JDBC ドライバーを使用して Virtual DataPort に接続し、このアプリケーションが上記のようなコードを実行するとします。「customer」ビューのデータが Oracle に保存されている場合、自動生成された値が Oracle によって Virtual DataPort に返され、ドライバーによってアプリケーションに渡されます。別のデータベースを使用している場合は、この動作をテストして、そのデータベースが値を返すことを確認してください。

内部的な仕組み

アプリケーションから、生成されたキーを返すようリクエストされた場合、ドライバーは Virtual DataPort に送信する IDU クエリを次のように変更します。

  • アプリケーションからすべての生成されたキーの取得をリクエストされた場合 (Statement.RETURN_GENERATED_KEYS)、ドライバーは IDU ステートメントに次を追加します。

    RETURNING ALL GENERATED KEYS
    
  • アプリケーションから、特定のフィールドの自動生成された値を名前を指定してリクエストされた場合、ドライバーは IDU ステートメントに次のようなものを追加します。

    RETURNING field_name_2, field_name_4, field_name_5
    
  • アプリケーションから、特定のフィールドの自動生成された値をインデックスを指定してリクエストされた場合、ドライバーは IDU ステートメントに次のようなものを追加します。

    RETURNING (3, 7, 11)