web-dev-qa-db-ja.com

JPA @GeneratedValue列で主キーの値を手動で指定する

私は次のようなプライマリキー/ IDフィールドを持つエンティティを持っています:

_@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
_

これはうまく機能します。 EclipseLinkを使用してDDLスキーマを作成していますが、列は次のように正しく作成されます。

_`id` bigint(20) NOT NULL AUTO_INCREMENT
_

ただし、PKを自分で指定したいエンティティがいくつかあります(古いデータベースから作成中の新しいデータベースにデータを転送する小さなアプリケーションです)。 POJOのIDを(setId(Long id)を使用して)指定して永続化すると、EclipseLinkは保存しません、保存します(つまり、レコードは保存されますが、idはeclipseLinkによって自動生成されます)。

@ GeneratedValueがある列の値を手動で指定する方法はありますか?

ここで問題に関するいくつかの考え:

@GeneratedValueをまったく使用せず、手動で列をAUTO_INCREMENTedに定義するだけで問題を回避しようとしました。ただし、これにより、EclipseLinkが主キーを検証するため、IDを手動で提供するように強制されますalways 。 eclipselink.id_validationを指定する必要があるという例外メッセージが表示されますが、これは何の違いも生じないようです(@PrimaryKey(validation = IdValidation.NONE)と注釈を付けましたが、同じメッセージが表示されました)。

明確にするために永続プロバイダーとしてEclipseLink(2.4.0)を使用していますが、プロジェクトから切り替えることはできません(プロジェクトの大部分は、Eclipselink固有のクエリヒント、アノテーション、およびクラスに依存します) 。


編集(回答への応答):

カスタムシーケンス:独自のシーケンスを実装しようとしました。 DefaultSequenceをサブクラス化しようとしましたが、EclipseLinkは_Internal Exception: org.Eclipse.persistence.platform.database.MySQLPlatform could not be found_を教えてくれます。しかし、私はチェックしました:クラスはクラスパス上にあります。

そこで、NativeSequenceという別のクラスをサブクラス化しました。

_public class MyNativeSequence extends NativeSequence {

    public MyNativeSequence() {
        super();
    }

    public MyNativeSequence(final String name) {
        super(name);
    }


    @Override
    public boolean shouldAlwaysOverrideExistingValue() {
        return false;
    }

    @Override
    public boolean shouldAlwaysOverrideExistingValue(final String seqName) {
        return false;
    }

}
_

しかし、私が得るものは次のとおりです。

_javax.persistence.RollbackException: Exception [EclipseLink-7197] (Eclipse Persistence Services - 2.4.0.v20120608-r11652): org.Eclipse.persistence.exceptions.ValidationException
Exception Description: Null or zero primary key encountered in unit of work clone [de.dfv.datenbank.domain.Mitarbeiter[ id=null ]], primary key [null]. Set descriptors IdValidation or the "eclipselink.id-validation" property.
    at org.Eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commitInternal(EntityTransactionImpl.Java:102)
    ...
Caused by: Exception [EclipseLink-7197] (Eclipse Persistence Services - 2.4.0.v20120608-r11652): org.Eclipse.persistence.exceptions.ValidationException
Exception Description: Null or zero primary key encountered in unit of work clone [de.dfv.datenbank.domain.Mitarbeiter[ id=null ]], primary key [null]. Set descriptors IdValidation or the "eclipselink.id-validation" property.
    at org.Eclipse.persistence.exceptions.ValidationException.nullPrimaryKeyInUnitOfWorkClone(ValidationException.Java:1451)
    ...
_

(わかりやすくするためにスタックトレースを短縮しています)。これは以前に受け取ったのと同じメッセージです。 NativeSequenceをサブクラス化すべきではありませんか?そうだとすれば、SequenceまたはStandardSequenceの抽象メソッドに何を実装すべきかわかりません。

また、クラスを単にサブクラス化する(メソッドをオーバーライドすることなく)ことが期待どおりに機能することにも注意してください。ただし、shouldAlwaysOverrideExistingValue(...)でfalseを取得しても、単一の値はまったく生成されません(プログラムをステップ実行し、getGeneratedValue()が一度も呼び出されません)。

また、トランザクション内に特定の種類の8個のエンティティを挿入すると、データベースに11個のレコードが生成されました(一体何!?)。

EDIT(2012-09-01):問題の解決策がまだありません。独自のシーケンスを実装しても解決しませんでした。私が必要とするのは、IDを明示的に設定せず(したがって自動生成される)、IDを明示的に設定できるようにする方法です(したがって、データベース内のレコードの作成に使用されます)。

私は列をauto_incrementとして定義し、@ GeneratedValueを省略しようとしましたが、検証が開始され、そのようなエンティティを保存できません。値!= 0および!=ゼロを指定すると、mysqlは主キーの重複を訴えます。

試してみるアイデアやオプションが不足しています。どれか? (賞金の開始)

28
scravy

これはeclipselinkで機能します。シーケンス用に別のテーブルが作成されますが、問題は発生しません。

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name="id", insertable=true, updatable=true, unique=true, nullable=false)
private Long id;

GenerationType.AUTOは、理想的な生成戦略を選択します。フィールドは挿入可能および更新可能として指定されているため、TABLE生成戦略が使用されます。これは、Eclipselinkが現在のシーケンス値を保持する別のテーブルを生成し、データベースに委任するのではなく、シーケンス自体を生成することを意味します。列は挿入可能と宣言されているため、永続化時にidがnullの場合、eclipselinkはidを生成します。それ以外の場合は、既存のIDが使用されます。

8
poison

TABLEシーケンスを使用する場合、EclipseLinkでは値をオーバーライドできます(データベースがこれをサポートしている場合はSEQUENCE、MySQLはサポートしていません)。

IDENTITYについては、MySQLで独自のIDを提供できるかどうかさえわかりません。これを確認することをお勧めします。通常、IDENTITYは事前割り当てをサポートしていないため、IDENTITYの使用はお勧めしません。

IDENTITYがIDを提供できるかどうかにいくつかの問題があります。 1つは、IDの値に応じて2つの異なる挿入SQLを生成する必要があることです。IDENTITYの場合、IDを挿入に含めることはできません。バグを記録して、IDENTITYでユーザーがIDをサポートできるようにすることができます。

独自のSequenceサブクラス、または場合によってはMySQLPlatformサブクラスで動作させることができるはずです。 「eclipselink.target-database」永続性ユニットプロパティを使用して、MySQLPlatformサブクラスを設定します。

7
James

問題に対するデータベース中心のソリューション:

  1. テーブルに補助のNULL入力可能列を作成します。手動で割り当てられたIDを保持します:

    CREATE TABLE `test_table`
    (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `manual_id` bigint(20) NULL,
      `some_other_field` varchar(200) NOT NULL,
      PRIMARY KEY(id)
    );
    
  2. この列をエンティティの通常のフィールドにマッピングします。

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name="manual_id")
    private Integer manualId;
    
  3. テーブルIDがnullでない場合、手動で割り当てられたIDに設定するトリガーを作成します。

    DELIMITER //
    CREATE TRIGGER `test_table_bi` BEFORE INSERT ON `test_table`
      FOR EACH ROW 
      BEGIN
        IF NEW.`manual_id` IS NOT NULL THEN 
          SET NEW.`id` = NEW.`manual_id`;
        END IF;
    END;//
    DELIMITER;    
    
  4. カスタムIDを割り当てる必要がある場合は、常にmanualIdを使用してください。トリガーは魔法をします:

    testEntiy.setManualId(300);
    entityManager.persist(testEntity);
    
  5. データベースのインポートフェーズの後、トリガー、補助列、およびそのマッピングを削除するだけです。

    DROP TRIGGER `test_table_bi`;
    ALTER TABLE `test_table` DROP COLUMN `manual_id`;
    

警告

現在のAUTO_INCREMENT値より大きいidを手動で指定した場合、次に生成されるidは、手動で割り当てられたidに1を加えた値にジャンプします。例:

INSERT INTO `test_table` (manual_id, some_other_field) VALUES (50, 'Something');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (NULL, 'Something else');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (90, 'Something 2');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (NULL, 'Something else 2');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (40, 'Something 3');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (NULL, 'Something else 3');

結果を振り回します:

+----+-----------+------------------+
| id | manual_id | some_other_field |
+----+-----------+------------------+
| 50 |        50 | Something        |
| 51 |      NULL | Something else   |
| 90 |        90 | Something 2      |
| 91 |      NULL | Something else 2 |
| 40 |        40 | Something 3      |
| 92 |      NULL | Something else 3 |
+----+-----------+------------------+

問題を回避するために、AUTO_INCREMENT列を、以前のデータベースの既存のIDのすべてよりも大きい数で開始するように設定することを強くお勧めします。

ALTER TABLE `test_table` AUTO_INCREMENT = 100000;
6
Anthony Accioly

私は明らかなものを見逃しているかもしれませんが、同じフィールドで同じ@Table(name=".....")注釈を持つ別のEntityを定義するだけで、idを生成しないのはなぜですか?次に、そのエンティティを古いDBから新しいDBにデータをコピーするコードに使用できます。生成されたIDを持つものは、ID生成を必要とする通常の作成に使用できます。

EclipseLinkで動作するかどうかはわかりませんが、ここではHibernateを使用しており、気にしないようです。

4
DuncanKinnear

PostgreSQLとEclipseLinkでGenerationType.SEQUENCEを使用するとうまくいきました。

1)変更

@GeneratedValue(strategy = GenerationType.IDENTITY) 

沿って

@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="step_id_seq")
@SequenceGenerator(name="step_id_seq", sequenceName="step_id_seq")

これで、NativeQueryを使用してシーケンスを呼び出すことができます。

return ((Vector<Integer>) em.createNativeQuery("select nextval('step_id_seq')::int").getSingleResult()).get(0);

entityManager.persist()メソッドを呼び出す前に、返されたIDをエンティティに設定します。

手遅れではないことを願っています!

1
szarza

カスタムIDジェネレーターを探してください

http://blog.anorakgirl.co.uk/2009/01/custom-hibernate-sequence-generator-for-id-field/

多分これが役立つかもしれません。

0
Dangling Piyush