149

識別子ではない/複合識別子の一部ではない列にDBシーケンスを使用することは可能ですか?

私は hibernate を jpa プロバイダーとして使用しており、識別子の一部ではありませんが、(シーケンスを使用して) 生成された値であるいくつかの列を持つテーブルがあります。

私が望むのは、シーケンスを使用してエンティティの新しい値を作成することです。ここで、シーケンスの列は主キー (の一部) ではありません。

@Entity
@Table(name = "MyTable")
public class MyEntity {

    //...
    @Id //... etc
    public Long getId() {
        return id;
    }

   //note NO @Id here! but this doesn't work...
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "myGen")
    @SequenceGenerator(name = "myGen", sequenceName = "MY_SEQUENCE")
    @Column(name = "SEQ_VAL", unique = false, nullable = false, insertable = true, updatable = true)
    public Long getMySequencedValue(){
      return myVal;
    }

}

次に、これを行うと:

em.persist(new MyEntity());

ID が生成されますが、mySequenceValプロパティも JPA プロバイダーによって生成されます。

明確にするために、 Hibernateにプロパティの値を生成させたいと考えていmySequencedValueます。Hibernate がデータベースで生成された値を処理できることはわかっていますが、Hibernate 自体以外のトリガーやその他のものを使用してプロパティの値を生成したくありません。Hibernate が主キーの値を生成できる場合、単純なプロパティを生成できないのはなぜですか?

4

20 に答える 20

85

この問題に対する答えを探して、このリンクに出くわしました

Hibernate/JPA は非 id プロパティの値を自動的に作成できないようです。@GeneratedValue注釈は、自動採番を作成するために と組み合わせてのみ使用されます@Id

注釈は@GeneratedValue、データベースがこの値自体を生成していることを Hibernate に伝えるだけです。

そのフォーラムで提案されている解決策 (または回避策) は、次のように、生成された Id を持つ別のエンティティを作成することです。

@実在物
public class GeneralSequenceNumber {
  @ID
  @GeneratedValue(...)
  プライベートロング番号;
}

@実在物
パブリック クラス MyEntity {
  @ ID ..
  プライベート ロング ID。

  @OneToOne(...)
  プライベート GeneralSequenceNumber myVal;
}
于 2009-02-11T09:35:27.700 に答える
57

私はそれ@Column(columnDefinition="serial")が完璧に機能することを発見しましたが、PostgreSQLに対してのみです。2番目のエンティティは「醜い」オプションであるため、私にとってこれは完璧なソリューションでした。

エンティティへの呼び出しsaveAndFlushも必要でありsave、DBから値を入力するのに十分ではありません。

于 2012-05-18T06:44:57.017 に答える
26

これは非常に古い質問であることは知っていますが、最初に結果に示され、jpaは質問以来大きく変化しました。

これを行う正しい方法は、@Generated注釈を使用することです。シーケンスを定義し、列のデフォルトをそのシーケンスに設定してから、列を次のようにマップできます。

@Generated(GenerationTime.INSERT)
@Column(name = "column_name", insertable = false)
于 2014-05-23T14:12:51.347 に答える
14

Hibernate は間違いなくこれをサポートしています。ドキュメントから:

「生成されたプロパティは、データベースによって生成された値を持つプロパティです。通常、Hibernate アプリケーションは、データベースが値を生成していたプロパティを含むオブジェクトを更新する必要がありました。ただし、プロパティを生成済みとしてマークすると、アプリケーションはこの責任を Hibernate に委任できます。基本的に、生成されたプロパティを定義したエンティティに対して Hibernate が SQL INSERT または UPDATE を発行するたびに、生成された値を取得するためにその後すぐに選択を発行します。」

挿入時にのみ生成されるプロパティの場合、プロパティ マッピング (.hbm.xml) は次のようになります。

<property name="foo" generated="insert"/>

挿入および更新時に生成されるプロパティの場合、プロパティ マッピング (.hbm.xml) は次のようになります。

<property name="foo" generated="always"/>

残念ながら、私は JPA を知らないので、この機能が JPA 経由で公開されているかどうかはわかりません (おそらくそうではないと思います)。

または、挿入と更新からプロパティを除外してから、session.refresh( obj ); を「手動で」呼び出すことができるはずです。データベースから生成された値をロードするために挿入/更新した後。

これは、プロパティが挿入および更新ステートメントで使用されないように除外する方法です。

<property name="foo" update="false" insert="false"/>

繰り返しますが、JPA がこれらの Hibernate 機能を公開しているかどうかはわかりませんが、Hibernate はそれらをサポートしています。

于 2008-11-12T10:57:19.037 に答える
8

フォローアップとして、私がそれを機能させる方法は次のとおりです。

@Override public Long getNextExternalId() {
    BigDecimal seq =
        (BigDecimal)((List)em.createNativeQuery("select col_msd_external_id_seq.nextval from dual").getResultList()).get(0);
    return seq.longValue();
}
于 2010-04-19T16:42:02.247 に答える
5

これは古いスレッドですが、私の解決策を共有し、うまくいけばこれについていくつかのフィードバックを得たいと思います。このソリューションは、一部のJUnitテストケースのローカルデータベースでのみテストしたことに注意してください。したがって、これは今のところ生産的な機能ではありません。

プロパティのないSequenceというカスタムアノテーションを導入することで、この問題を解決しました。これは、インクリメントされたシーケンスから値を割り当てる必要があるフィールドの単なるマーカーです。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sequence
{
}

この注釈を使用して、エンティティにマークを付けました。

public class Area extends BaseEntity implements ClientAware, IssuerAware
{
    @Column(name = "areaNumber", updatable = false)
    @Sequence
    private Integer areaNumber;
....
}

データベースに依存しないようにするために、シーケンスの現在の値と増分サイズを保持するSequenceNumberというエンティティを導入しました。classNameを一意のキーとして選択したので、各エンティティクラスは独自のシーケンスを取得します。

@Entity
@Table(name = "SequenceNumber", uniqueConstraints = { @UniqueConstraint(columnNames = { "className" }) })
public class SequenceNumber
{
    @Id
    @Column(name = "className", updatable = false)
    private String className;

    @Column(name = "nextValue")
    private Integer nextValue = 1;

    @Column(name = "incrementValue")
    private Integer incrementValue = 10;

    ... some getters and setters ....
}

最後のステップで最も難しいのは、シーケンス番号の割り当てを処理するPreInsertListenerです。豆の入れ物として春を使ったことに注意してください。

@Component
public class SequenceListener implements PreInsertEventListener
{
    private static final long serialVersionUID = 7946581162328559098L;
    private final static Logger log = Logger.getLogger(SequenceListener.class);

    @Autowired
    private SessionFactoryImplementor sessionFactoryImpl;

    private final Map<String, CacheEntry> cache = new HashMap<>();

    @PostConstruct
    public void selfRegister()
    {
        // As you might expect, an EventListenerRegistry is the place with which event listeners are registered
        // It is a service so we look it up using the service registry
        final EventListenerRegistry eventListenerRegistry = sessionFactoryImpl.getServiceRegistry().getService(EventListenerRegistry.class);

        // add the listener to the end of the listener chain
        eventListenerRegistry.appendListeners(EventType.PRE_INSERT, this);
    }

    @Override
    public boolean onPreInsert(PreInsertEvent p_event)
    {
        updateSequenceValue(p_event.getEntity(), p_event.getState(), p_event.getPersister().getPropertyNames());

        return false;
    }

    private void updateSequenceValue(Object p_entity, Object[] p_state, String[] p_propertyNames)
    {
        try
        {
            List<Field> fields = ReflectUtil.getFields(p_entity.getClass(), null, Sequence.class);

            if (!fields.isEmpty())
            {
                if (log.isDebugEnabled())
                {
                    log.debug("Intercepted custom sequence entity.");
                }

                for (Field field : fields)
                {
                    Integer value = getSequenceNumber(p_entity.getClass().getName());

                    field.setAccessible(true);
                    field.set(p_entity, value);
                    setPropertyState(p_state, p_propertyNames, field.getName(), value);

                    if (log.isDebugEnabled())
                    {
                        LogMF.debug(log, "Set {0} property to {1}.", new Object[] { field, value });
                    }
                }
            }
        }
        catch (Exception e)
        {
            log.error("Failed to set sequence property.", e);
        }
    }

    private Integer getSequenceNumber(String p_className)
    {
        synchronized (cache)
        {
            CacheEntry current = cache.get(p_className);

            // not in cache yet => load from database
            if ((current == null) || current.isEmpty())
            {
                boolean insert = false;
                StatelessSession session = sessionFactoryImpl.openStatelessSession();
                session.beginTransaction();

                SequenceNumber sequenceNumber = (SequenceNumber) session.get(SequenceNumber.class, p_className);

                // not in database yet => create new sequence
                if (sequenceNumber == null)
                {
                    sequenceNumber = new SequenceNumber();
                    sequenceNumber.setClassName(p_className);
                    insert = true;
                }

                current = new CacheEntry(sequenceNumber.getNextValue() + sequenceNumber.getIncrementValue(), sequenceNumber.getNextValue());
                cache.put(p_className, current);
                sequenceNumber.setNextValue(sequenceNumber.getNextValue() + sequenceNumber.getIncrementValue());

                if (insert)
                {
                    session.insert(sequenceNumber);
                }
                else
                {
                    session.update(sequenceNumber);
                }
                session.getTransaction().commit();
                session.close();
            }

            return current.next();
        }
    }

    private void setPropertyState(Object[] propertyStates, String[] propertyNames, String propertyName, Object propertyState)
    {
        for (int i = 0; i < propertyNames.length; i++)
        {
            if (propertyName.equals(propertyNames[i]))
            {
                propertyStates[i] = propertyState;
                return;
            }
        }
    }

    private static class CacheEntry
    {
        private int current;
        private final int limit;

        public CacheEntry(final int p_limit, final int p_current)
        {
            current = p_current;
            limit = p_limit;
        }

        public Integer next()
        {
            return current++;
        }

        public boolean isEmpty()
        {
            return current >= limit;
        }
    }
}

上記のコードからわかるように、リスナーはエンティティクラスごとに1つのSequenceNumberインスタンスを使用し、SequenceNumberエンティティのincrementValueによって定義されたいくつかのシーケンス番号を予約します。シーケンス番号が不足している場合は、ターゲットクラスのSequenceNumberエンティティをロードし、次の呼び出しのためにincrementValue値を予約します。このようにして、シーケンス値が必要になるたびにデータベースにクエリを実行する必要はありません。次のシーケンス番号のセットを予約するために開かれているStatelessSessionに注意してください。EntityPersisterでConcurrentModificationExceptionが発生するため、ターゲットエンティティが現在永続化されているのと同じセッションを使用することはできません。

これが誰かを助けることを願っています。

于 2012-08-07T09:16:18.750 に答える
4

私はあなたと同じ状況で実行していますが、基本的に JPA で非 ID プロパティを生成できるかどうかについても、深刻な答えは見つかりませんでした。

私の解決策は、ネイティブ JPA クエリを使用してシーケンスを呼び出し、プロパティを永続化する前に手動で設定することです。

これは満足のいくものではありませんが、現時点では回避策として機能します。

マリオ

于 2008-11-21T14:24:49.437 に答える
2

JPA 仕様のセッション 9.1.9 GeneratedValue Annotation で、「[43] ポータブル アプリケーションは、他の永続フィールドまたはプロパティで GeneratedValue アノテーションを使用しないでください」という特定のメモを見つけました。したがって、少なくとも単純にJPAを使用して、非主キー値の値を自動生成することはできないと思います。

于 2010-11-24T19:31:38.870 に答える
1

@Morten Bergの受け入れられたソリューションの隣に代替案を提供したいと思います。これは私にとってより効果的でした。

このアプローチにより、実際に必要なNumberタイプLong(私のユースケースでは) ではなく、フィールドを定義することができますGeneralSequenceNumber。これは、JSON の (デ) シリアル化などに役立ちます。

欠点は、データベースのオーバーヘッドが少し増えることです。


まず、type のActualEntity自動インクリメントを行いたい が必要です:generatedLong

// ...
@Entity
public class ActualEntity {

    @Id 
    // ...
    Long id;

    @Column(unique = true, updatable = false, nullable = false)
    Long generated;

    // ...

}

次に、ヘルパー エンティティが必要Generatedです。ActualEntityパッケージの実装の詳細を保持するために、 の隣に package-private を配置しました。

@Entity
class Generated {

    @Id
    @GeneratedValue(strategy = SEQUENCE, generator = "seq")
    @SequenceGenerator(name = "seq", initialValue = 1, allocationSize = 1)
    Long id;

}

最後に、 を保存する直前にフックする場所が必要ActualEntityです。そこで、Generatedインスタンスを作成して永続化します。idこれにより、生成されたタイプのデータベース シーケンスが提供されますLong。この値を に書き込むことで利用しActualEntity.generatedます。

私のユースケースでは、 get が永続@RepositoryEventHandler化される直前に get が呼び出されるSpring Data REST を使用してこれを実装しました。ActualEntity原則を示す必要があります。

@Component
@RepositoryEventHandler
public class ActualEntityHandler {

    @Autowired
    EntityManager entityManager;

    @Transactional
    @HandleBeforeCreate
    public void generate(ActualEntity entity) {
        Generated generated = new Generated();

        entityManager.persist(generated);
        entity.setGlobalId(generated.getId());
        entityManager.remove(generated);
    }

}

実際のアプリケーションではテストしていませんので、注意してお楽しみください。

于 2020-05-05T13:47:30.107 に答える
0

「自分のプロパティの値を生成するために、Hibernate 以外のトリガーやその他のものを使用したくありません」

その場合、必要な値を生成する UserType の実装を作成し、mySequenceVal プロパティの永続化のためにその UserType を使用するようにメタデータを構成するのはどうですか?

于 2008-11-17T16:29:24.443 に答える
0

これは、シーケンスを使用することと同じではありません。シーケンスを使用する場合、何も挿入または更新していません。次のシーケンス値を取得しているだけです。休止状態はそれをサポートしていないようです。

于 2009-11-11T20:36:46.697 に答える
0

今日はこれに苦労していましたが、これを使用して解決できました

@Generated(GenerationTime.INSERT)
@Column(name = "internal_id", columnDefinition = "serial", updatable = false)
private int internalId;
于 2020-09-01T13:29:02.057 に答える
-1

私はあなたのような状況にありました (非 @Id フィールドの JPA/Hibernate シーケンス)。最終的に、挿入時に一意のシーケンス番号を追加するトリガーをデータベース スキーマに作成しました。JPA/Hibernateで動作させることができませんでした

于 2008-11-12T01:54:33.953 に答える