JPA-Sequenz im Ruhezustand (keine ID)

138

Ist es möglich, eine DB-Sequenz für eine Spalte zu verwenden, die nicht der Bezeichner ist / nicht Teil eines zusammengesetzten Bezeichners ist ?

Ich verwende den Ruhezustand als JPA-Anbieter und habe eine Tabelle mit einigen Spalten, in denen Werte generiert werden (unter Verwendung einer Sequenz), obwohl sie nicht Teil des Bezeichners sind.

Ich möchte eine Sequenz verwenden, um einen neuen Wert für eine Entität zu erstellen, wobei die Spalte für die Sequenz NICHT (Teil) des Primärschlüssels ist:

@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;
    }

}

Wenn ich das mache:

em.persist(new MyEntity());

Die ID wird generiert, aber die mySequenceValEigenschaft wird auch von meinem JPA-Anbieter generiert.

Nur um die Dinge klar zu machen: Ich möchte, dass Hibernate den Wert für die mySequencedValueEigenschaft generiert . Ich weiß, dass Hibernate datenbankgenerierte Werte verarbeiten kann, aber ich möchte keinen Trigger oder etwas anderes als Hibernate selbst verwenden, um den Wert für meine Eigenschaft zu generieren. Wenn der Ruhezustand Werte für Primärschlüssel generieren kann, warum kann er dann nicht für eine einfache Eigenschaft generiert werden?

Miguel Ping
quelle

Antworten:

76

Auf der Suche nach Antworten auf dieses Problem bin ich auf diesen Link gestoßen

Es scheint, dass Hibernate / JPA nicht automatisch einen Wert für Ihre Nicht-ID-Eigenschaften erstellen kann. Die @GeneratedValueAnmerkung wird nur in Verbindung mit verwendet @Id, um automatische Nummern zu erstellen.

Die @GeneratedValueAnmerkung teilt Hibernate lediglich mit, dass die Datenbank diesen Wert selbst generiert.

Die in diesem Forum vorgeschlagene Lösung (oder Umgehung) besteht darin, eine separate Entität mit einer generierten ID zu erstellen.

@Entität
öffentliche Klasse GeneralSequenceNumber {
  @Ich würde
  @GeneratedValue (...)
  private Lange Nummer;
}}

@Entität 
öffentliche Klasse MyEntity {
  @Ich würde ..
  private Long id;

  @Eins zu eins(...)
  private GeneralSequnceNumber myVal;
}}
Morten Berg
quelle
Aus dem Java-Dokument von @GeneratedValue: "Die Annotation GeneratedValue kann in Verbindung mit der Annotation Id auf eine Primärschlüsseleigenschaft oder ein Feld einer Entität oder zugeordneten Oberklasse angewendet werden"
Kariem,
11
Ich fand, dass @Column (columnDefinition = "serial") perfekt funktioniert, aber nur für PostgreSQL. Für mich war dies die perfekte Lösung, da die zweite Entität eine "hässliche" Option ist
Sergey Vedernikov
@ SergeyVedernikov das war sehr hilfreich. Würde es Ihnen etwas ausmachen, dies als separate Antwort zu veröffentlichen? Es hat mein Problem sehr, sehr einfach und effektiv gelöst.
Matt Ball
@ MattBall Ich habe dies als separate Antwort gepostet :) stackoverflow.com/a/10647933/620858
Sergey Vedernikov
1
Ich habe einen Vorschlag geöffnet, um @GeneratedValueFelder zuzulassen , die nicht id sind. Bitte stimmen Sie ab, um in 2.2 aufgenommen zu werden. Java.net/jira/browse/JPA_SPEC-113
Petar Tahchiev
44

Ich fand, dass @Column(columnDefinition="serial")das perfekt funktioniert, aber nur für PostgreSQL. Für mich war dies die perfekte Lösung, da die zweite Entität eine "hässliche" Option ist.

Sergey Vedernikov
quelle
Hallo, ich brauche eine Erklärung dazu. Könnten Sie mir bitte mehr erzählen?
Emaborsa
2
@Emaborsa Das columnDefinition=Bit weist Hiberate grundsätzlich an, nicht zu versuchen, die Spaltendefinition zu generieren, sondern stattdessen den von Ihnen angegebenen Text zu verwenden. Im Wesentlichen ist Ihre DDL für die Spalte buchstäblich nur name + columnDefinition. In diesem Fall (PostgreSQL) mycolumn serialist eine gültige Spalte in einer Tabelle.
Patrick
7
Das Äquivalent für MySQL ist@Column(columnDefinition = "integer auto_increment")
Richard Kennard
2
Generiert dieses Auto automatisch seinen Wert? Ich habe versucht, eine Entität mit einer Felddefinition wie dieser beizubehalten, aber sie hat keinen Wert generiert. es warf einen Nullwert in Spalte <Spalte> verletzt Nicht-Null-Einschränkung
KyelJmD
7
Ich habe @Column(insertable = false, updatable = false, columnDefinition="serial")verhindert, dass der Ruhezustand versucht, Nullwerte einzufügen oder das Feld zu aktualisieren. Sie müssen dann die Datenbank erneut abfragen, um die generierte ID nach einer Einfügung zu erhalten, wenn Sie sie sofort verwenden müssen.
Robert Di Paolo
20

Ich weiß, dass dies eine sehr alte Frage ist, aber sie zeigt sich zuerst in den Ergebnissen und jpa hat sich seit der Frage sehr verändert.

Der richtige Weg, dies jetzt zu tun, ist mit der @GeneratedAnmerkung. Sie können die Sequenz definieren, die Standardeinstellung in der Spalte auf diese Sequenz festlegen und die Spalte dann wie folgt zuordnen:

@Generated(GenerationTime.INSERT)
@Column(name = "column_name", insertable = false)
Rumal
quelle
1
Dies erfordert weiterhin, dass der Wert von der Datenbank generiert wird, was die Frage nicht wirklich beantwortet. Für Oracle-Datenbanken vor 12c müssten Sie noch einen Datenbank-Trigger schreiben, um den Wert zu generieren.
Bernie
9
Dies ist auch eine Annotation im Ruhezustand, keine JPA.
Caarlos0
14

Der Ruhezustand unterstützt dies definitiv. Aus den Dokumenten:

"Generierte Eigenschaften sind Eigenschaften, deren Werte von der Datenbank generiert werden. In der Regel müssen Anwendungen im Ruhezustand Objekte aktualisieren, die Eigenschaften enthalten, für die die Datenbank Werte generiert hat. Durch Markieren von Eigenschaften als generiert kann die Anwendung diese Verantwortung jedoch an den Ruhezustand delegieren. Wenn Hibernate ein SQL INSERT oder UPDATE für eine Entität ausgibt, die generierte Eigenschaften definiert hat, gibt es im Wesentlichen sofort eine Auswahl aus, um die generierten Werte abzurufen. "

Für Eigenschaften, die nur beim Einfügen generiert werden, sieht Ihre Eigenschaftszuordnung (.hbm.xml) folgendermaßen aus:

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

Für Eigenschaften, die beim Einfügen und Aktualisieren generiert wurden, sieht Ihre Eigenschaftszuordnung (.hbm.xml) folgendermaßen aus:

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

Leider kenne ich JPA nicht, daher weiß ich nicht, ob diese Funktion über JPA verfügbar gemacht wird (ich vermute möglicherweise nicht).

Alternativ sollten Sie in der Lage sein, die Eigenschaft von Einfügungen und Aktualisierungen auszuschließen und dann session.refresh (obj) "manuell" aufzurufen. nachdem Sie es eingefügt / aktualisiert haben, um den generierten Wert aus der Datenbank zu laden.

Auf diese Weise würden Sie die Eigenschaft von der Verwendung in Anweisungen zum Einfügen und Aktualisieren ausschließen:

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

Auch hier weiß ich nicht, ob JPA diese Hibernate-Funktionen verfügbar macht, aber Hibernate unterstützt sie.

alasdairg
quelle
1
Die Annotation @Generated entspricht der obigen XML-Konfiguration. Weitere Informationen finden Sie in diesem Abschnitt der Dokumente zum Ruhezustand .
Eric
8

Als Folge habe ich Folgendes zum Laufen gebracht:

@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();
}
Paul
quelle
Ein Varient mit Hibernate 4.2.19 und Orakel: SQLQuery sqlQuery = getSession().createSQLQuery("select NAMED_SEQ.nextval seq from dual"); sqlQuery.addScalar("seq", LongType.INSTANCE); return (Long) sqlQuery.uniqueResult();
Aaron
6

Ich habe die Generierung von UUID (oder Sequenzen) mit Hibernate mithilfe von @PrePersistAnmerkungen korrigiert:

@PrePersist
public void initializeUUID() {
    if (uuid == null) {
        uuid = UUID.randomUUID().toString();
    }
}
Matroska
quelle
5

Obwohl dies ein alter Thread ist, möchte ich meine Lösung teilen und hoffentlich ein Feedback dazu erhalten. Seien Sie gewarnt, dass ich diese Lösung nur mit meiner lokalen Datenbank in einem JUnit-Testfall getestet habe. Dies ist also bisher keine produktive Funktion.

Ich habe dieses Problem für mich gelöst, indem ich eine benutzerdefinierte Annotation namens Sequence ohne Eigenschaft eingeführt habe. Es ist nur eine Markierung für Felder, denen ein Wert aus einer inkrementierten Sequenz zugewiesen werden soll.

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

Mit dieser Anmerkung habe ich meine Entitäten markiert.

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

Um die Datenbank unabhängig zu halten, habe ich eine Entität namens SequenceNumber eingeführt, die den aktuellen Sequenzwert und die Inkrementgröße enthält. Ich habe den Klassennamen als eindeutigen Schlüssel ausgewählt, damit jede Entitätsklasse ihre eigene Sequenz erhält.

@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 ....
}

Der letzte und schwierigste Schritt ist ein PreInsertListener, der die Zuweisung der Sequenznummer übernimmt. Beachten Sie, dass ich Feder als Bohnenbehälter verwendet habe.

@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;
        }
    }
}

Wie Sie dem obigen Code entnehmen können, hat der Listener eine SequenceNumber-Instanz pro Entitätsklasse verwendet und reserviert einige Sequenznummern, die durch den incrementValue der SequenceNumber-Entität definiert sind. Wenn die Sequenznummern ausgehen, wird die SequenceNumber-Entität für die Zielklasse geladen und inkrementelle Werte für die nächsten Aufrufe reserviert. Auf diese Weise muss ich die Datenbank nicht jedes Mal abfragen, wenn ein Sequenzwert benötigt wird. Beachten Sie die StatelessSession, die geöffnet wird, um den nächsten Satz von Sequenznummern zu reservieren. Sie können nicht dieselbe Sitzung verwenden, in der die Zielentität derzeit beibehalten wird, da dies zu einer ConcurrentModificationException im EntityPersister führen würde.

Hoffe das hilft jemandem.

Sebastian Götz
quelle
5

Wenn Sie postgresql verwenden
Und ich verwende im Frühjahr Boot 1.5.6

@Column(columnDefinition = "serial")
@Generated(GenerationTime.INSERT)
private Integer orderID;
Sulaymon Hursanov
quelle
1
Es hat auch bei mir funktioniert, ich verwende Spring Boot 2.1.6.RELEASE, Hibernate 5.3.10.Final, Zusätzlich zu dem, worauf bereits hingewiesen wurde, musste ich eine Sequenz erstellen seq_orderund eine Referenz aus dem Feld erstellen , nextval('seq_order'::regclass)
OJVM
3

Ich bin in der gleichen Situation wie Sie und habe auch keine ernsthaften Antworten gefunden, ob es grundsätzlich möglich ist, mit JPA Nicht-ID-Eigenschaften zu generieren oder nicht.

Meine Lösung besteht darin, die Sequenz mit einer nativen JPA-Abfrage aufzurufen, um die Eigenschaft von Hand festzulegen, bevor sie beibehalten wird.

Dies ist nicht zufriedenstellend, funktioniert jedoch im Moment als Problemumgehung.

Mario


quelle
2

Ich habe diesen speziellen Hinweis in Sitzung 9.1.9 GeneratedValue-Annotation aus der JPA-Spezifikation gefunden: "[43] Tragbare Anwendungen sollten die GeneratedValue-Annotation nicht für andere persistente Felder oder Eigenschaften verwenden." Daher gehe ich davon aus, dass es nicht möglich ist, automatisch Werte für Nicht-Primärschlüsselwerte zu generieren, zumindest nicht einfach mit JPA.

Gustavo Orair
quelle
1

Sieht so aus, als wäre der Thread alt, ich wollte nur meine Lösung hier hinzufügen (Verwenden von AspectJ - AOP im Frühjahr).

Die Lösung besteht darin, eine benutzerdefinierte Anmerkung @InjectSequenceValuewie folgt zu erstellen .

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InjectSequenceValue {
    String sequencename();
}

Jetzt können Sie jedes Feld in der Entität mit Anmerkungen versehen, sodass der zugrunde liegende Feldwert (Long / Integer) zur Laufzeit mit dem nächsten Wert der Sequenz eingefügt wird.

Kommentieren Sie so.

//serialNumber will be injected dynamically, with the next value of the serialnum_sequence.
 @InjectSequenceValue(sequencename = "serialnum_sequence") 
  Long serialNumber;

Bisher haben wir das Feld markiert, das wir zum Einfügen des Sequenzwerts benötigen. Wir werden also untersuchen, wie der Sequenzwert in die markierten Felder eingefügt wird. Dazu wird der Punktschnitt in AspectJ erstellt.

Wir werden die Injektion kurz vor der save/persistAusführung der Methode auslösen. Dies erfolgt in der folgenden Klasse.

@Aspect
@Configuration
public class AspectDefinition {

    @Autowired
    JdbcTemplate jdbcTemplate;


    //@Before("execution(* org.hibernate.session.save(..))") Use this for Hibernate.(also include session.save())
    @Before("execution(* org.springframework.data.repository.CrudRepository.save(..))") //This is for JPA.
    public void generateSequence(JoinPoint joinPoint){

        Object [] aragumentList=joinPoint.getArgs(); //Getting all arguments of the save
        for (Object arg :aragumentList ) {
            if (arg.getClass().isAnnotationPresent(Entity.class)){ // getting the Entity class

                Field[] fields = arg.getClass().getDeclaredFields();
                for (Field field : fields) {
                    if (field.isAnnotationPresent(InjectSequenceValue.class)) { //getting annotated fields

                        field.setAccessible(true); 
                        try {
                            if (field.get(arg) == null){ // Setting the next value
                                String sequenceName=field.getAnnotation(InjectSequenceValue.class).sequencename();
                                long nextval=getNextValue(sequenceName);
                                System.out.println("Next value :"+nextval); //TODO remove sout.
                                field.set(arg, nextval);
                            }

                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }

        }
    }

    /**
     * This method fetches the next value from sequence
     * @param sequence
     * @return
     */

    public long getNextValue(String sequence){
        long sequenceNextVal=0L;

        SqlRowSet sqlRowSet= jdbcTemplate.queryForRowSet("SELECT "+sequence+".NEXTVAL as value FROM DUAL");
        while (sqlRowSet.next()){
            sequenceNextVal=sqlRowSet.getLong("value");

        }
        return  sequenceNextVal;
    }
}

Jetzt können Sie jede Entität wie folgt mit Anmerkungen versehen.

@Entity
@Table(name = "T_USER")
public class UserEntity {

    @Id
    @SequenceGenerator(sequenceName = "userid_sequence",name = "this_seq")
    @GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "this_seq")
    Long id;
    String userName;
    String password;

    @InjectSequenceValue(sequencename = "serialnum_sequence") // this will be injected at the time of saving.
    Long serialNumber;

    String name;
}
Subin Chalil
quelle
0

"Ich möchte keinen Auslöser oder etwas anderes als den Ruhezustand selbst verwenden, um den Wert für mein Eigentum zu generieren."

Wie wäre es in diesem Fall, eine Implementierung von UserType zu erstellen, die den erforderlichen Wert generiert, und die Metadaten so zu konfigurieren, dass dieser UserType für die Persistenz der Eigenschaft mySequenceVal verwendet wird?

alasdairg
quelle
0

Dies ist nicht dasselbe wie die Verwendung einer Sequenz. Wenn Sie eine Sequenz verwenden, fügen Sie nichts ein oder aktualisieren nichts. Sie rufen einfach den nächsten Sequenzwert ab. Es sieht so aus, als würde der Ruhezustand dies nicht unterstützen.

Kammy
quelle
0

Wenn Sie eine Spalte mit dem Typ UNIQUEIDENTIFIER haben und beim Einfügen eine Standardgenerierung benötigen, die Spalte jedoch nicht PK ist

@Generated(GenerationTime.INSERT)
@Column(nullable = false , columnDefinition="UNIQUEIDENTIFIER")
private String uuidValue;

In db wirst du haben

CREATE TABLE operation.Table1
(
    Id         INT IDENTITY (1,1)               NOT NULL,
    UuidValue  UNIQUEIDENTIFIER DEFAULT NEWID() NOT NULL)

In diesem Fall definieren Sie keinen Generator für einen Wert, den Sie benötigen (dies erfolgt automatisch dank columnDefinition="UNIQUEIDENTIFIER"). Das gleiche können Sie für andere Spaltentypen versuchen

Artyom Novitskii
quelle
0

Ich habe eine Problemumgehung für MySQL-Datenbanken mit @PostConstruct und JdbcTemplate in einer Spring-Anwendung gefunden. Es kann mit anderen Datenbanken durchgeführt werden, aber der Anwendungsfall, den ich vorstellen werde, basiert auf meinen Erfahrungen mit MySql, da es auto_increment verwendet.

Zuerst hatte ich versucht, eine Spalte mit der ColumnDefinition-Eigenschaft der @ Column-Annotation als auto_increment zu definieren, aber es funktionierte nicht, da die Spalte ein Schlüssel sein musste, um automatisch inkrementell zu sein, aber anscheinend wurde die Spalte nicht als definiert ein Index bis nach seiner Definition, was zu einem Deadlock führt.

Hier ist , wo ich mit der Idee der Schaffung der Säule ohne die auto_increment Definition kam, und das Hinzufügen es nach dem der Datenbank . Dies ist mithilfe der Annotation @PostConstruct möglich, die bewirkt, dass eine Methode direkt nach der Initialisierung der Beans durch die Anwendung in Verbindung mit der Aktualisierungsmethode von JdbcTemplate aufgerufen wird.

Der Code lautet wie folgt:

In meiner Entität:

@Entity
@Table(name = "MyTable", indexes = { @Index(name = "my_index", columnList = "mySequencedValue") })
public class MyEntity {
    //...
    @Column(columnDefinition = "integer unsigned", nullable = false, updatable = false, insertable = false)
    private Long mySequencedValue;
    //...
}

In einer PostConstructComponent-Klasse:

@Component
public class PostConstructComponent {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @PostConstruct
    public void makeMyEntityMySequencedValueAutoIncremental() {
        jdbcTemplate.update("alter table MyTable modify mySequencedValue int unsigned auto_increment");
    }
}
Ignacio Velásquez Lagos
quelle
0

Ich möchte neben der akzeptierten Lösung von @Morten Berg eine Alternative anbieten, die für mich besser funktioniert hat.

Dieser Ansatz ermöglicht es, das Feld mit dem tatsächlich gewünschten NumberTyp zu definieren - Longin meinem Anwendungsfall - anstelle vonGeneralSequenceNumber . Dies kann nützlich sein, z. B. für die JSON (De-) Serialisierung.

Der Nachteil ist, dass etwas mehr Datenbank-Overhead erforderlich ist.


Zuerst benötigen wir eine, ActualEntityin der wir den generatedTyp automatisch inkrementieren möchten Long:

// ...
@Entity
public class ActualEntity {

    @Id 
    // ...
    Long id;

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

    // ...

}

Als nächstes brauchen wir eine Hilfseinheit Generated. Ich habe es paketprivat neben platziert ActualEntity, um ein Implementierungsdetail des Pakets zu erhalten:

@Entity
class Generated {

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

}

Schließlich brauchen wir einen Platz zum Einhängen, bevor wir das speichern ActualEntity. Dort erstellen wir eine GeneratedInstanz und behalten sie bei . Dies liefert dann eine idvom Typ generierte Datenbanksequenz Long. Wir nutzen diesen Wert, indem wir ihn schreiben ActualEntity.generated.

In meinem Anwendungsfall habe ich dies mithilfe eines Spring Data REST implementiert @RepositoryEventHandler, der unmittelbar vor dem ActualEntityFortbestehen des Get aufgerufen wird . Es sollte das Prinzip demonstrieren:

@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);
    }

}

Ich habe es nicht in einer realen Anwendung getestet, also genießen Sie es bitte mit Sorgfalt.

Aboger
quelle
-1

Ich war in einer Situation wie Sie (JPA / Hibernate-Sequenz für Nicht-@Id-Feld) und habe schließlich einen Trigger in meinem Datenbankschema erstellt, der beim Einfügen eine eindeutige Sequenznummer hinzufügt. Ich habe es einfach nie geschafft, mit JPA / Hibernate zu arbeiten

Frederic Morin
quelle
-1

Nachdem ich Stunden verbracht hatte, half mir dies ordentlich, mein Problem zu lösen:

Für Oracle 12c:

ID NUMBER GENERATED as IDENTITY

Für H2:

ID BIGINT GENERATED as auto_increment

Machen Sie auch:

@Column(insertable = false)
Frühling
quelle