Hibernate / JPA использовать предоставленные идентификаторы для сохранения - PullRequest
0 голосов
/ 29 ноября 2018

Существует ли простой способ использовать предоставленные идентификаторы для сущностей при сохранении их в пустую базу данных?

При использовании EntityManager.persist(...) вызов завершается неудачно, так как он думает, что объект отключен (так как у него есть идентификатор),Я не хочу использовать EntityManager.merge(...) по соображениям производительности (он запускает дополнительный выбор для каждой сущности).

Я пробовал разные способы, например, предоставление ForeignGenerator, но идентификаторы обнуляются перед сохранением и NPE

Я также пытался предоставить Hibernate Interceptor и переопределить его isTransient(), чтобы всегда возвращать true, но тогда я не могу правильно сохранить каскадное сохранение (или, по крайней мере, я должен поддерживать кэшя уже сохранил сущности).

Я думаю, что должен быть простой способ сделать это, но не могу найти какой-либо.

Обновлено: добавлены сопоставления сущностей

@Entity
@Data
@NoArgsConstructor
@EqualsAndHashCode(of = "admNr")
public class Adm implements Serializable {

    @Id
    @Column(name = "ADM_NR", nullable = false)
    private String admNr;

    @OneToMany(mappedBy = "adm", cascade = CascadeType.ALL)
    private Set<Kunde> kundeList = new HashSet<>();

}

@Entity
@Data
@NoArgsConstructor
@EqualsAndHashCode(of = "kundeNr")
public class Kunde implements Serializable {

    @Id
    @Column(name = "Kunde_Nr", nullable = false)
    private String kundeNr;

    @ManyToOne(optional = false)
    @JoinColumn(name = "ADM_Nr", referencedColumnName = "ADM_NR")
    private Adm adm;

}

Обновлено: предоставлено дополнительная информация

Я не создаю объекты вручную, они создаются как часть процедуры переноса данных, и правильно устанавливаются двунаправленные отношенияв процессе, так что это не возможно, что kunde.getAdm() когда-либо будет нулевым.В любом случае, я попытался удалить nullable = false из Kunde.adm, но результат был тот же.

Я настроил каскады, чтобы иметь возможность сохранять весь график, используя em.persist(adm), как я не хотелпосещайте каждого ребенка и сохраняйте его в явном виде (модель, которую я предоставил, является упрощенной, на самом деле в ней больше уровней вложенности).

String PK - это устаревшие вещи, от которых, к сожалению, я не могу избавиться.

Ошибка, возникающая при сохранении графика (вызов em.persist(adm)):

java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : de.apollon.dmx.workflow.processing.sqlite.adm.domain.Kunde.adm -> de.apollon.dmx.workflow.processing.sqlite.adm.domain.Adm
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:146) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:157) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:164) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:797) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:768) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na]
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:305) ~[spring-orm-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at com.sun.proxy.$Proxy129.persist(Unknown Source) ~[na:na]
    at de.apollon.dmx.workflow.processing.sqlite.adm.service.SqliteAdmPackageService.storeAdmPackage(SqliteAdmPackageService.java:60) ~[classes/:na]
    at de.apollon.dmx.workflow.processing.sqlite.adm.service.SqliteAdmPackageService.createAdmPackageResource(SqliteAdmPackageService.java:48) ~[classes/:na]
    at de.apollon.dmx.workflow.processing.sqlite.adm.service.SqliteAdmPackageService$$FastClassBySpringCGLIB$$6a54d95.invoke(<generated>) ~[classes/:na]
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:746) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294) ~[spring-tx-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98) ~[spring-tx-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at de.apollon.dmx.workflow.processing.sqlite.adm.service.SqliteAdmPackageService$$EnhancerBySpringCGLIB$$5ba50508.createAdmPackageResource(<generated>) ~[classes/:na]
    at de.apollon.dmx.workflow.processing.service.AdmPackageService.createAdmPackage(AdmPackageService.java:59) ~[classes/:na]
    at de.apollon.dmx.workflow.processing.service.ProcessingService.processAdmPackage(ProcessingService.java:111) [classes/:na]
    at de.apollon.dmx.workflow.processing.service.ProcessingService.runTest(ProcessingService.java:58) [classes/:na]
    at de.apollon.dmx.workflow.Application.lambda$applicationRunner$0(Application.java:31) [classes/:na]
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:514) ~[na:na]
    at java.base/java.util.concurrent.FutureTask.runAndReset$$$capture(FutureTask.java:305) ~[na:na]
    at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java) ~[na:na]
    at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1135) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) ~[na:na]
    at java.base/java.lang.Thread.run(Thread.java:844) ~[na:na]
Caused by: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : de.apollon.dmx.workflow.processing.sqlite.adm.domain.Kunde.adm -> de.apollon.dmx.workflow.processing.sqlite.adm.domain.Adm
    at org.hibernate.action.internal.UnresolvedEntityInsertActions.checkNoUnresolvedActionsAfterOperation(UnresolvedEntityInsertActions.java:122) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
    at org.hibernate.engine.spi.ActionQueue.checkNoUnresolvedActionsAfterOperation(ActionQueue.java:432) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
    at org.hibernate.internal.SessionImpl.checkNoUnresolvedActionsAfterOperation(SessionImpl.java:631) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:794) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
    ... 29 common frames omitted

Ответы [ 2 ]

0 голосов
/ 02 декабря 2018

Проблема заключалась в том, что модели были созданы с помощью ModelMapper, и он предоставлял разные экземпляры одного и того же объекта на двух сторонах отношения.

Adm (AdmNr="1234", Java instance @1234)
  .kundeList
  -> Kunde (KundeNr="2345", Java instance @2345)
    .adm
    -> Adm (AdmNr="1234", Java instance @4444) <- should be @1234

Это приводит к тому, что мышление в спящем состоянии сущность Adm является переходной иисключение, поскольку каскада от Кунде до Адм. нет.

Решено с настройкой ModelMapper, поэтому он предоставил один и тот же экземпляр для одного ПК.

0 голосов
/ 30 ноября 2018

У вас проблемы с курицей и яйцом.С одной стороны у вас есть Adm с набором Kunde и cascade = CascadeType.ALL.С помощью каскада вы просите сущность Adm сохранить для вас отношение Kunde, не устанавливая поле Adm в Kunde.Итак, я предполагаю, что вы пытаетесь сохранить их обоих без этого.

    Adm adm = new Adm();
    adm.setAdmNr("NO1");
    Kunde kunde = new Kunde();
    kunde.setKundeNr("NO1");
    adm.getKundeList().add(kunde);
    em.persist(adm);

С другой стороны, вы аннотировали поле Kunde с помощью optional = false, говоря, что недопустимо не устанавливать Adm поле Kunde, что вышеупомянутый код не делает.Поэтому это дает вам ненулевую ошибку.Вы должны были предоставить (часть) свою трассировку стека в своем вопросе.

Причина: org.hibernate.PropertyValueException: свойство not-null ссылается на нулевое или временное значение: model.Kunde.adm

Итак, чтобы исправить, удалите ненулевое условие или установите Adm свойство Kunde

    Adm adm = new Adm();
    adm.setAdmNr("NO1");
    Kunde kunde = new Kunde();
    kunde.setKundeNr("NO1");
    kunde.setAdm(adm);
    adm.getKundeList().add(kunde);
    em.persist(adm);

Кроме того, как я уже говорил выше, вам не нужно nullable = false для первичных ключей, поскольку каждое поле первичного ключа создается с уникальным индексом, который запрещает нулевые значения.

В качестве примечания не рекомендуется создавать HashSet для kundeList с каждым новым экземпляром Adm.У вас есть двунаправленное отображение, принадлежащее сущности Kunde, но это владение переопределяется настройкой cascade для подмножества операций.Предполагая, что вы будете выполнять больше запросов, чем обновлений, новый HashSet всегда будет выброшен для управляемого списка JPA при запросе.Параметр cascade не применяется к запросам, поэтому вы не получите заполненный kundeList, если ваш запрос специально не запрашивает его, и Hashset будет заменен на пустой управляемый список JPA в любом случае.Приведенный выше рабочий код работает нормально, если вы просто добавите к нему em.persist(Kunde) и удалите настройку cascade.По всем этим причинам и, вероятно, к большему, я думаю, что предпочтительнее управлять отношениями конкретно самостоятельно и использовать kundeList в качестве поля только для запроса, для которого оно лучше всего работает.

Наконец, это действительно плохая идея использоватьСтроки в качестве первичных ключей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...