2015年1月31日土曜日

[JPA]中間テーブルを作らずに直接結合させる

例えば1対多の依存があるエンティティをJPAで使用した場合、デフォルトでは中間テーブルを使った結合となる。

Entityの関連定義

    @OneToMany(cascade = {CascadeType.ALL})
    public List<Tel> getTels() {
        return tels;
    }

テーブル定義

上のEntityの関連定義だと下のようなテーブル構造となります。
中間テーブルのテーブル名やカラム名を変更したい場合には、OneToManyとセットでJoinTableアノテーションを定義して任意の値に変更します。

どうしても過去の資産などを使わないとダメな場合なので中間テーブルを使えない場合には、OneToManyとセットでJoinColumnを定義してあげる。JoinColumnのname属性には、子供側テーブルの外部キーとなる項目名を定義します。

Entityを次のように変更すると、Userテーブルの子テーブルのTelテーブルにはUserテーブルを参照するための外部キー(user_id)が作成される。
    @OneToMany(cascade = {CascadeType.ALL})
    @JoinColumn(name = "user_id")
    public List<Tel> getTels() {
        return tels;
    }

テーブル定義は次のようになります。

2015年1月25日日曜日

[JPA]ElementCollectionとCollectionTableアノテーション

JPAのEntityクラスに基本型のCollectionやListの属性を定義する場合は、ElementCollectionアノテーションを使用する。

ElementCollectionをつけることで、基本型(Entityじゃないクラス)をもつCollectionやListの属性を別のテーブルに保存してくれるようになる。
CollectionTableアノテーションを使うと、保存先のテーブルの名前などをカスタマイズできるようになる。

Entityクラスの定義です。ソースコードが長くなると見づらくなるので、JPAのアノテーションはpublicフィールドに設定してアクセッサは定義していません。
@Entity
public class User {

    @Id
    @GeneratedValue
    public Long id;

    public String name;

    @ElementCollection
    public Set mail;
}

上のEntity定義の場合、CollectionTableアノテーションを使用していないのでデフォルトの定義でテーブルが作られます。
テーブル名は、親テーブルのテーブル名とその属性の名前(user_mail)となり、カラム名は属性名(mail)となります。


テーブル名を変更したい場合には、CollectionTableアノテーションのname属性を使用します。
カラム名を変更したい場合には、Columnアノテーションを使用します。



2015年1月20日火曜日

[JDBC]DatabaseMetaDataから識別子の格納方法を判断する

JDBCのDatabaseMetaDataから、データベースの識別子(テーブル名やカラム名)がどのように格納されているか判断する方法。
DatabaseMetaDataを使ってカラム一覧や主キー一覧などを取得するときには、格納方法を判断して識別子を大文字や小文字に変換してから使用する必要がある。

引用符付きの識別子は、基本使わない(使っとこともない)ので、引用符なしの識別子が大文字、小文字どちらで格納されているかを判断するだけのサンプルコード
// storesMixedCaseIdentifiersで引用符なしの識別子の格納方法を判断する。
// falseが返される場合は、大文字、小文字のどちらかで格納されている。
System.out.println("大文字小文字混在の引用符なし識別子を大文字小文字混在で格納する → " + metaData.storesMixedCaseIdentifiers());

// storesLowerCaseIdentifiersで小文字で格納されているかを判断する。
// trueが返される場合は、小文字で格納されている。
System.out.println("引用符なし識別子を小文字で格納する → " + metaData.storesLowerCaseIdentifiers());

// storesUpperCaseIdentifiersで大文字で格納されているかを判断する。
// trueが返される場合は、大文字で格納されている。
System.out.println("引用符なし識別子を大文字で格納する → " + metaData.storesUpperCaseIdentifiers());

実行結果

Oracleの場合

大文字で格納されていることが分かる
大文字小文字混在の引用符なし識別子を大文字小文字混在で格納する → false
引用符なし識別子を小文字で格納する → false
引用符なし識別子を大文字で格納する → true

PostgreSQLの場合

小文字で格納されていることが分かる
大文字小文字混在の引用符なし識別子を大文字小文字混在で格納する → false
引用符なし識別子を小文字で格納する → true
引用符なし識別子を大文字で格納する → false

2015年1月8日木曜日

PostgreSQLはStatement.RETURN_GENERATED_KEYSで全カラムの値が返される

PostgresSQLは、Statement生成時に、Statement.RETURN_GENERATED_KEYSを指定するとStatement#getGeneratedKeysでそのテーブルの全カラムの情報が取得される。

これは、Statement.RETURN_GENERATED_KEYSでStatementを生成すると、実行されるINSERT文に「RETURNING *」が付加されるため。

ID、NAME、BIRTHDAYの3つのカラムを持つテーブルにINSERT処理を行っています。
INSERT対象のカラムはNAMEのみで、ID列は自動採番カラム(SERIAL)となっています。
PreparedStatement statement = connection.prepareStatement("insert into USER_INFO (name) VALUES (?)",
        Statement.RETURN_GENERATED_KEYS);
statement.setString(1, "なまえ");

statement.executeUpdate();
ResultSet generatedKeys = statement.getGeneratedKeys();
if (generatedKeys.next()) {
    System.out.println(MessageFormat.format("カラム数 = [{0}]",
            statement.getMetaData().getColumnCount()));
    System.out.println("generatedKeys.getDate(3) = " + generatedKeys.getDate(3));
}
connection.commit();

実行結果

実行すると、GeneratedKeysで取得したResultSetのカラム数は3となっていることがわかります。
また、INSERT時に列挙していないカラムのデータもとれているのがわかります。
カラム数 = [3]
generatedKeys.getDate(3) = null

実際に実行されたSQL文

ログを見てみると、実行されたINSERT文にRETURNING *が付加されているのがわかります。
これが理由で、getGeneratedKeysが全カラムの情報を戻して来ています。
2015-01-08 12:48:11 JST LOG:  duration: 0.000 ms  parse : insert into USER_INFO (name) VALUES ($1) RETURNING *
2015-01-08 12:48:11 JST LOG:  duration: 0.000 ms  bind : insert into USER_INFO (name) VALUES ($1) RETURNING *
2015-01-08 12:48:11 JST DETAIL:  parameters: $1 = 'なまえ'
2015-01-08 12:48:11 JST LOG:  duration: 1.000 ms  execute : insert into USER_INFO (name) VALUES ($1) RETURNING *
2015-01-08 12:48:11 JST DETAIL:  parameters: $1 = 'なまえ'
2015-01-08 12:48:11 JST LOG:  duration: 0.000 ms  parse : insert into USER_INFO (name) VALUES ($1) RETURNING *

カラムインデックスやカラム名指定の場合は?

Statement生成時に自動生成カラムのインデックスや名前を指定した場合は、指定したカラムの情報のみ取得することが出来ます。
例えば、ID列のみを指定した場合実行されるSQL文は以下のようになります。
2015-01-08 12:55:28 JST LOG:  duration: 34.000 ms  parse : insert into USER_INFO (name) VALUES ($1) RETURNING "id"
2015-01-08 12:55:28 JST LOG:  duration: 0.000 ms  bind : insert into USER_INFO (name) VALUES ($1) RETURNING "id"
2015-01-08 12:55:28 JST DETAIL:  parameters: $1 = 'なまえ'
2015-01-08 12:55:28 JST LOG:  duration: 0.000 ms  execute : insert into USER_INFO (name) VALUES ($1) RETURNING "id"
2015-01-08 12:55:28 JST DETAIL:  parameters: $1 = 'なまえ'
2015-01-08 12:55:28 JST LOG:  duration: 0.000 ms  parse : insert into USER_INFO (name) VALUES ($1) RETURNING "id"

2015年1月7日水曜日

OracleでJDBC経由でレコード追加後にROWIDを取得する

OracleのJDBCドライバで、Statement生成時にStatement.RETURN_GENERATED_KEYSフラグを指定している場合、自動生成キーは追加されたレコードのROWIDが返されます。

何が返されるかわからなかったので、試してみたらROWIDが取得出来たのですが、OracleのドキュメントにROWIDが取得されることが明記されていました。
以下が、ドキュメントの内容となります。
java.sql.Statement

キー列を明示的に指定しないと、Oracle JDBCドライバでは取り出す列を特定できません。特定できるのは、列名または列索引の配列が使用されている場合です。
ただし、int型のStatement.RETURN_GENERATED_KEYSフラグが使用されている場合、Oracle JDBCドライバではこれらの列を特定できません。int型フラグを使用して自動生成キーが戻るように指定した場合は、ROWID擬似列がキーとして戻ります。
このROWIDは、ResultSetオブジェクトからフェッチして、他の列を取り出すために使用できます。

PreparedStatement statement = connection.prepareStatement("INSERT INTO HOGE VALUES (?, ?)",
Statement.RETURN_GENERATED_KEYS);
statement.setLong(1, 1);
statement.setString(2, "name");
statement.executeUpdate();
ResultSet generatedKeys = statement.getGeneratedKeys();
if (generatedKeys.next()) {
    ROWID rowid = (ROWID) generatedKeys.getRowId(1);
    System.out.println(rowid.stringValue());
}
上のコードを実行した場合、標準出力にはROWIDを文字列表現に変換した値が出力されます。
例えば、「AAAOKLAAEAAACQTAAA」のような値が出力されます。