java - 如何使用 spring-boot 和 JPA 持久化包含另一个未持久化实体的多个相同实例的新实体?

标签 java jpa entitymanager cascade persist

概述:

我正在构建一个 spring-boot 应用程序,它部分地从外部 REST 服务中检索一些实体,并将其与数据库中本地保存的实体的先前版本进行比较。

我正在注入(inject) EntityManager@PersistenceContext ,并使用它来处理数据库,因为有许多实体类型,并且该类型最初对模块是未知的。我可以得到 JpaRepository来自工厂,但不同实体类型的数量可能会增长,如果可能的话,我宁愿不依赖它。

问题:

当模块检索到它不在数据库中的实体时,它会执行一些业务逻辑,然后尝试持久化新实体。
Person类是所讨论的实体之一,它包含三个 Site 类型的字段,它们通常持有相同的对象。

当我试图坚持一个新的Person具有相同的 Site具有 CascadeType.PERSIST 的多个字段中的对象,我得到一个 EntityExistsException (参见堆栈跟踪(1))。

当我从 Site 中删除 CascadeType.PERSIST 时字段,并尝试持久化一个新的 Person具有相同的 Site多个字段中的对象,我得到一个 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 (参见stacktrace (4)),如果我同时使用PERSISTMERGE我得到一个 InvalidDataAccessApiUsageException`(请参阅 stacktrace (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 中添加以下 block 文件:

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

    关于java - 如何使用 spring-boot 和 JPA 持久化包含另一个未持久化实体的多个相同实例的新实体?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50721043/

    相关文章:

    hibernate - 如何在 JUnit 测试中初始化 EJB 的 EntityManager?

    java - 从不断变化的字符串中获取子字符串

    java - Android:ScrollView 不使用键盘滚动

    java - 是否可以在 JAVA 中运行 HADOOP 并将文件从本地 fs 复制到 HDFS 但无需在文件系统上安装 Hadoop?

    java - 添加一个方面来捕获异常并返回 null

    java - unique(true) - 无法准备语句

    java - 更改按钮单击 ANDROID 上的默认启动器

    java - 用户、用户角色的数据库设计——多对多关系

    JPA 1.0 @OrderBy 使用字段名称与 JPA 2.0 @OrderColumn 使用列名称

    jpa - 始终打开 EntityManager