簡體   English   中英

如何使用 spring-boot 和 JPA 持久化包含另一個非持久化實體的多個相同實例的新實體?

[英]How to persist a new entity containing multiple identical instances of another unpersisted entity with spring-boot and JPA?

概述:

我正在構建一個 spring-boot 應用程序,它部分地從外部 REST 服務中檢索一些實體,並將其與數據庫中本地保存的實體的先前版本進行比較。

我正在使用@PersistenceContext注入EntityManager ,並使用它來處理數據庫,因為有許多實體類型,並且模塊最初不知道該類型。 我可以從工廠獲得JpaRepository ,但不同實體類型的數量可能會增加,如果可能的話,我寧願不依賴它。

問題:

當模塊檢索到數據庫中沒有的實體時,它會執行一些業務邏輯,然后嘗試持久保存新實體。

Person類是所討論的實體之一,它包含三個Site類型的字段,這些字段通常包含相同的對象。

當我嘗試使用CascadeType.PERSIST在多個字段中保留一個具有相同Site對象的新Person ,我得到一個EntityExistsException (請參閱堆棧跟蹤 (1))。

當我從Site字段中刪除 CascadeType.PERSIST 並嘗試保留一個在多個字段中具有相同Site對象的新Person ,我得到一個TransientPropertyValueException (請參閱堆棧跟蹤 (2))。

我想我明白為什么會出現這兩種異常的原因:

  • 第一種情況是因為第一個站點字段級聯持久化后,無法為第二個字段重新持久化。

  • 我認為的第二種情況是因為@Transactional注釋試圖在沒有持久化站點實例的情況下刷新事務。

我已經嘗試刪除@Transactional注釋並自己開始和提交 EntityTransaction,但我得到了一個IllegalStateException (參見堆棧跟蹤 (3)),盡管我認為這是預期的,因為 spring 應該處理事務本身。

我看過類似問題的答案(例如thisthis ),但都建議更改 CascadeType 的一些變體。

在另一個問題中,有人建議確保有問題的實體被equals()方法正確評估,所以我檢查了調試器和((Person)newEntity).currentSite.equals(((Person)newEntity).homeSite)評估為真。

我怎樣才能在多個字段中一致地持久化/合並具有相同對象的實體?


編輯:我還嘗試了fetch = FetchType.EAGER的級聯類型的各種組合,但這不會對它們各自的異常產生任何變化。


編輯 2:我嘗試使用JpaRepository而不是使用EntityManager ,並根據我使用的級聯類型有效地獲得相同的異常集。

如果我使用PERSIST ,但沒有MERGE ,我會得到一個EntityNotFoundException (參見PERSIST (4)),如果我同時使用PERSISTMERGE我得到一個 InvalidDataAccessApiUsageException`(參見PERSIST (5))。


人:

@EqualsAndHashCode(callSuper = true)
@javax.persistence.Entity
@XmlDiscriminatorValue("person")
@XmlRootElement(name = "person")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlSeeAlso({Subscriber.class})
public class Person extends MobileResource implements Serializable {

    private static final Logger LOG = LogManager.getLogger(Person.class);

    private String firstName;
    private String surname;

    public Person() {
        super();
    }

    public Person(Long id) {
        super(id);
    }

    public Person(Person that) {
        super(that);
        this.firstName = that.firstName;
        this.surname = that.surname;
    }

    // getters && setters
}

移動資源:

@EqualsAndHashCode(callSuper = true)
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@XmlRootElement(name = "resource")
@XmlDiscriminatorNode("@type")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlSeeAlso({Vehicle.class, Person.class})
public abstract class MobileResource extends Resource implements Serializable {

    private static final Logger LOG = LogManager.getLogger(MobileResource.class);

    @ManyToOne(cascade = CascadeType.ALL)
    private MobileResourceStatus status;
    private Long                 incidentId;
    @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.DETACH})
    private Site                 homeSite;
    @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.DETACH})
    private Site                 currentSite;
    @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.DETACH})
    private Site                 relocationSite;

    public MobileResource() {
        super();
    }

    public MobileResource(Long id) {
        super(id);
    }

    public MobileResource(MobileResource that) {
        super(that);
        this.status = that.status;
        this.incidentId = that.incidentId;
        this.homeSite = that.homeSite;
        this.currentSite = that.currentSite;
        this.relocationSite = that.relocationSite;
    }

    // getters && setters
}

地點:

@EqualsAndHashCode(callSuper = true)
@javax.persistence.Entity
public class Site extends Resource implements Serializable {

    private static final Logger LOG = LogManager.getLogger(Site.class);

    private String location;

    public Site() {
        super();
    }

    public Site(Long id) {
        super(id);
    }

    public Site(Site that) {
        super(that);
        this.location = that.location;
    }
}

資源:

@EqualsAndHashCode
@MappedSuperclass
@XmlRootElement
@XmlSeeAlso({MobileResource.class})
public abstract class Resource implements Entity, Serializable {

    private static final Logger LOG = LogManager.getLogger(Resource.class);

    @Id
    private Long            id;
    private String          callSign;
    @XmlPath(".")
    private LatLon          latLon;
    private Long            brigadeId;
    private Long            batchId;
    @ManyToMany(cascade = CascadeType.ALL)
    private List<Attribute> attributes;
    @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.DETACH})
    private ResourceType    type;

    public Resource() {
    }

    public Resource(Long id) {
        this.id = id;
    }

    public Resource(Resource that) {
        this.id = that.id;
        this.callSign = that.callSign;
        this.latLon = that.latLon;
        this.attributes = that.attributes;
        this.batchId = that.batchId;
        this.brigadeId = that.brigadeId;
        this.type = that.type;
    }

    // getters && setters
}

默認實體消息處理程序:

@Component
public class DefaultEntityMessageHandler implements EntityMessageHandler {


    @PersistenceContext
    private EntityManager entityManager;

    @Override
    @Transactional
    public void handleEntityMessage(EntityMessageData data, Message message) {

        // business logic
        if (newEntity != null) {
            if (oldEntity != null)
                entityManager.merge(newEntity);
            else
                entityManager.persist(newEntity);
        }
    }
}

堆棧跟蹤(1):

2018-06-06 12:05:15,975 ERROR ActiveMQMessageConsumer - ID:cpt-9225-1528283097161-1:1:1:1 Exception while processing message: ID:cpt-8919-1528281875592-1:1:1:1:4 
javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [my.class.path.entity.resource.site.Site#738]
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:118)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:157)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:164)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:813)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:773)
at org.hibernate.jpa.event.internal.core.JpaPersistEventListener$1.cascade(JpaPersistEventListener.java:80)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:467)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:392)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:126)
at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:414)
at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:252)
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:182)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:125)
at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:67)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:189)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:132)
...

更改 MobileResource 中的級聯類型:

@ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.REMOVE, CascadeType.DETACH})
private Site                 homeSite;
@ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.REMOVE, CascadeType.DETACH})
private Site                 currentSite;
@ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.REMOVE, CascadeType.DETACH})

堆棧跟蹤(2):

2018-06-06 12:19:24,084 ERROR ExceptionMapperStandardImpl - HHH000346: Error during managed flush [org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : my.class.path.entity.resource.mobile_resource.person.Person.currentSite -> my.class.path.entity.resource.site.Site]
2018-06-06 12:19:24,093 ERROR ActiveMQMessageConsumer - ID:cpt-9436-1528283955454-1:1:1:1 Exception while processing message: ID:cpt-8919-1528281875592-1:1:1:1:8
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : my.class.path.entity.resource.mobile_resource.person.Person.currentSite -> my.class.path.entity.resource.site.Site; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : my.class.path.entity.resource.mobile_resource.person.Person.currentSite -> my.class.path.entity.resource.site.Site
    at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:365)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:227)
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:540)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:746)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:714)
...

堆棧跟蹤 (3):

2018-06-06 13:29:35,594 ERROR ActiveMQMessageConsumer - ID:cpt-9864-1528288166188-1:1:1:1 Exception while processing message: ID:cpt-8919-1528281875592-1:1:1:1:9
java.lang.IllegalStateException: Not allowed to create transaction on shared EntityManager - use Spring transactions or EJB CMT instead
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:254)
    at com.sun.proxy.$Proxy114.getTransaction(Unknown Source)
    at my.class.path.entity_controller.DefaultEntityMessageHandler.handleEntityMessage(DefaultEntityMessageHandler.java:60)
    at my.class.path.entity_listener.listeners.IdExtractorMessageListener.onMessage(IdExtractorMessageListener.java:41)
...

堆棧跟蹤 (4)

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2018-06-06 15:26:36,143 ERROR SpringApplication - Application run failed
java.lang.IllegalStateException: Failed to execute CommandLineRunner
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:793)
    at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:774)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:335)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1246)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1234)
    at my.class.path.OfficerSubscription.main(OfficerSubscription.java:44)
Caused by: org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find my.class.path.entity.resource.site.Site with id 738; nested exception is javax.persistence.EntityNotFoundException: Unable to find my.class.path.entity.resource.site.Site with id 738
    at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:373)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:227)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:507)
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
...

堆棧跟蹤 (5)

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2018-06-06 15:31:54,840 ERROR SpringApplication - Application run failed
java.lang.IllegalStateException: Failed to execute CommandLineRunner
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:793)
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:774)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:335)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1246)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1234)
at my.class.path.OfficerSubscription.main(OfficerSubscription.java:44)
Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: Multiple representations of the same entity [my.class.path.entity.resource.site.Site#738] are being merged. Detached: [FJE84 - Uckfield]; Detached: [FJE84 - Uckfield]; nested exception is java.lang.IllegalStateException: Multiple representations of the same entity [my.class.path.entity.resource.site.Site#738] are being merged. Detached: [FJE84 - Uckfield]; Detached: [FJE84 - Uckfield]
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:365)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:227)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:507)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:135)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy122.save(Unknown Source)
at my.class.path.OfficerSubscription.run(OfficerSubscription.java:81)
at my.class.path.OfficerSubscription$$FastClassBySpringCGLIB$$705870eb.invoke(<generated>)
    ...

經過幾天的搜索,我終於在我的spring boot項目中解決了這個問題。

application.yaml文件中添加以下塊:

    spring:
      jpa:
        properties:
          hibernate:
            enable_lazy_load_no_trans: true
            event:
              merge:
                entity_copy_observer: allow

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM