0

シナリオ:トランザクション内で JPA と JDBC を混合しているコードを見つけました。JDBC は、基本的に空白行を含むテーブルに INSERT を実行し、主キー(SELECT MAX(PK) + 1)middleName一時タイムスタンプを設定します。メソッドは、同じテーブルからmax(PK)+その一時タイムスタンプを選択して、衝突があったかどうかを確認します。成功した場合はmiddleName、更新を無効にします。このメソッドは、新しく作成された主キーを返します。

質問: データベースにエンティティを挿入し、PK を設定max(pk) + 1して新しく作成された PK にアクセスする (できれば JPA を使用する) より良い方法はありますか?

環境: EclipseLink を使用しており、Oracle データベースと MS SqlServer データベースの両方のいくつかのバージョンをサポートする必要があります。

おまけの背景: 私がこの質問をしている理由はjava.sql.BatchUpdateException、統合テストを実行しているときにチェーンの一部としてこのメ​​ソッドを呼び出すときに、に遭遇したためです。チェーンの上部では、JPA を使用EntityManagerして一部のオブジェクトを永続化します。

問題のメソッド

@Override
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public int generateStudentIdKey() {
    final long now = System.currentTimeMillis();
    int id = 0;

    try {

        try (final Connection connection = dataSource.getConnection()) {

            if (connection.getAutoCommit()) {
                connection.setAutoCommit(false);
            }

            try (final Statement statement = connection.createStatement()) {
                // insert a row into the generator table
                statement.executeUpdate(
                    "insert into student_demo (student_id, middle_name) " + 
                    "select (max(student_id) + 1) as student_id, '" + now + 
                        "' as middle_name from student_demo");
                try (final ResultSet rs = statement.executeQuery(
                    "select max(student_id) as student_id " + 
                    "from student_demo where middle_name = '" + now + "'")) {

                        if (rs.next()) {
                            id = rs.getInt(1);
                        }
                }

                if (id == 0) {
                    connection.rollback();
                    throw new RuntimeException("Key was not generated");
                }

                statement.execute("update student_demo set middle_name = null " + 
                                  "where student_id = " + id);

            } catch (SQLException statementException) {
                connection.rollback();
                throw statementException;
            }
        }
    } catch (SQLException exception) {
        throw new RuntimeException(
           "Exception thrown while trying to generate new student_ID", exception);
    }

    return id;
}
4

2 に答える 2

3

まず、これに答えるのは難しいです。しかし、私は知っています、時々あなたは悪魔に対処しなければなりません:(

技術的にはJPAではありませんが、HibernateをJPAプロバイダーとして使用している場合は、

@org.hibernate.annotations.GenericGenerator(
    name = “incrementGenerator”,
    strategy = “org.hibernate.id.IncrementGenerator”)
@GeneratedValue(generator="incrementGenerator")
private Long primaryKey;

Hibernate ソリューションは「スレッドセーフ」ですが、「クラスターセーフ」ではありません。つまり、複数のホストでアプリケーションを実行すると、失敗する可能性があります。適切な例外をキャッチして、再試行してください。

解決策に固執する場合: ResultSetStatementおよび を閉じますConnection 申し訳ありませんが、最初に try-with-resources をキャッチできませんでした。

于 2013-04-06T20:02:12.710 に答える
0

JDBC コードは病的で意味がなく、マルチ ユーザー環境では機能しません。

シーケンス オブジェクトまたはシーケンス テーブルを使用するようにコードを修正することを強くお勧めします。

JPAでは、シーケンスを使用できます。

http://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing#Sequencingを参照してください 。

本当に独自のシーケンス処理を行いたい場合は、ID を自分で割り当てるか、PrePersist を使用して独自の ID を割り当てるか、または EclipseLink で、必要なことを行う独自の Sequence サブクラスを実装することができます。SessionCustomizer を使用して、この Sequence オブジェクトを登録する必要があります。

http://wiki.eclipse.org/EclipseLink/Examples/JPA/CustomSequencingを参照して ください。

于 2013-05-14T13:06:50.383 に答える