JPA - OneToMany 坚持 - EntityExistsException

Posted

技术标签:

【中文标题】JPA - OneToMany 坚持 - EntityExistsException【英文标题】:JPA - OneToMany persist - EntityExistsException 【发布时间】:2017-01-02 01:09:19 【问题描述】:

通过 SO 搜索,但我找不到我的案例。

有一个实体卡,像这样:

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.NotEmpty;

@Entity
@ToString(exclude="playRequirements")
@Table(name = "CARD",
       indexes = @Index(name = "CARD_IDX_1", columnList="ID", unique = true))
public class Card implements Serializable 

    @Getter
    @Setter
    @NotEmpty
    @NotBlank
    @Id
    @Column(name = "ID", unique = true, nullable = false)
    private String id;

    @Getter
    @Setter
    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy="card")
    private List<CardPlayRequirementsRel> playRequirements;

作为与实体 CardPlayRequirementsRel 的反比关系:

import java.io.Serializable;

import hscli.entities.domain.PlayRequirement;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import org.hibernate.validator.constraints.NotEmpty;

@Entity
@ToString
@EqualsAndHashCode(of="card", "playRequirement")
@Table(name = "CARD_PLAY_REQUIREMENT_REL", 
       indexes=@Index(name="CARD_PLAY_REQ_REL_IDX_1", unique=true, columnList="CARD,PLAY_REQUIREMENT"))
public class CardPlayRequirementsRel implements Serializable 

    @Id
    @Getter
    @Setter
    @NotEmpty
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "CARD", nullable = false, foreignKey = @ForeignKey(name = "FK_CARD_PLAY_REQ_REL_CARD"))
    private Card card;

    @Id
    @Getter
    @Setter
    @NotEmpty
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "PLAY_REQUIREMENT", nullable = false, foreignKey = @ForeignKey(name = "FK_CARD_PLAY_REQ_REL_PLAY_REQ"))
    private PlayRequirement playRequirement;

    @Getter
    @Setter
    @NotEmpty
    @Column(name = "VALUE", nullable = false)
    private Long value;

我想保留一张卡片列表:

[...]

List<CardDTO> dtoList = connectionService.getCardDTO();
List<Card> toSave = new ArrayList<Card>();
for(CardDTO dto : dtoList) 
    log.debug("----------------> id: [" + dto.getId() + "]");
    Card entity = new Card();
    dtoToCardEntity(dto, entity);
    toSave.add(entity);

cardRepository.save(toSave);

[...]

private void dtoToCardEntity(CardDTO dto, Card entity) 
    entity.setId(dto.getId());

    if(dto.getPlayRequirements() != null && CollectionUtils.isNotEmpty(dto.getPlayRequirements().keySet())) 
        entity.setPlayRequirements(new ArrayList<CardPlayRequirementsRel>());
        for(String playReq : dto.getPlayRequirements().keySet()) 
            CardPlayRequirementsRel rel = new CardPlayRequirementsRel();
            rel.setCard(entity);
            rel.setPlayRequirement(domainBaseService.findOneByCodice(playReq, PlayRequirement.class));
            rel.setValue(dto.getPlayRequirements().get(playReq));
            entity.getPlayRequirements().add(rel);
        
    

但是在保存时我得到了这个异常:

13398 02:04:41,679 [main] DEBUG [org.hibernate.SQL:92] - 
    select
        cardplayre0_.PLAY_REQUIREMENT as PLAY_REQUIREMENT2_7_0_,
        cardplayre0_.CARD as CARD3_7_0_,
        cardplayre0_.VALUE as VALUE1_7_0_ 
    from
        CARD_PLAY_REQUIREMENT_REL cardplayre0_ 
    where
        cardplayre0_.PLAY_REQUIREMENT=? 
        and cardplayre0_.CARD=?
13398 02:04:41,679 [main] TRACE [org.hibernate.type.descriptor.sql.BasicBinder:65] - binding parameter [1] as [VARCHAR] - [REQ_MINION_TARGET]
13399 02:04:41,680 [main] TRACE [org.hibernate.type.descriptor.sql.BasicBinder:65] - binding parameter [2] as [VARCHAR] - [AT_024]
13404 02:04:41,685 [main] ERROR [hscli.HearthStoneCardListImporter:56] - A different object with the same identifier value was already associated with the session : [hscli.entities.cards.CardPlayRequirementsRel#CardPlayRequirementsRel(card=null, playRequirement=null, value=0)]; nested exception is javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [hscli.entities.cards.CardPlayRequirementsRel#CardPlayRequirementsRel(card=null, playRequirement=null, value=0)]
org.springframework.dao.DataIntegrityViolationException: A different object with the same identifier value was already associated with the session : [hscli.entities.cards.CardPlayRequirementsRel#CardPlayRequirementsRel(card=null, playRequirement=null, value=0)]; nested exception is javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [hscli.entities.cards.CardPlayRequirementsRel#CardPlayRequirementsRel(card=null, playRequirement=null, value=0)]
    at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:410)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:246)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:491)
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59)
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:147)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
    at com.sun.proxy.$Proxy41.save(Unknown Source)
    at hscli.services.impl.CardServiceImpl.updateCards(CardServiceImpl.java:52)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
    at com.sun.proxy.$Proxy44.updateCards(Unknown Source)
    at hscli.HearthStoneCardListImporter.updateCards(HearthStoneCardListImporter.java:54)
    at hscli.HearthStoneCardListImporter.main(HearthStoneCardListImporter.java:29)
Caused by: javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [hscli.entities.cards.CardPlayRequirementsRel#CardPlayRequirementsRel(card=null, playRequirement=null, value=0)]
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1664)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1602)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1608)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:1171)
    at sun.reflect.GeneratedMethodAccessor36.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:298)
    at com.sun.proxy.$Proxy39.merge(Unknown Source)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:509)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:540)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:72)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:503)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:488)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:460)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
    ... 23 more

【问题讨论】:

您确认CardPlayRequirementsRel实例的两个PK字段不为空吗? 【参考方案1】:

如果您的实体中有多个使用@Id 注释的列,则必须使用@IdClass 注释或将其替换为嵌入的@EmbeddedId。请参阅How to map a composite key with Hibernate? 以获得很好的解释,这似乎与休眠无关。

我不完全确定这实际上是您现在看到的问题,因为异常似乎抱怨 id 字段为 null 的多个实例,所以由于您没有指定任何生成器,您应该确保 id 列设置为实际值并且不为空。

【讨论】:

非常感谢。使用@EmbeddedId 注解解决。

以上是关于JPA - OneToMany 坚持 - EntityExistsException的主要内容,如果未能解决你的问题,请参考以下文章

JPA、OneToMany 和 ManyToOne

JPA(休眠)映射OneToMany不正确?

JPA,打开 JPA OneToMany - FailedObject

JPA @OneToMany 不保存父 ID

在 Hibernate/JPA 中保存一个带有子对象的对象 - @OneToMany

为啥在看到 PersistentBag 的实体中使用 JPA 列出(@OneToMany)