JPA - OneToMany 坚持 - EntityExistsException

JPA - OneToMany persist - EntityExistsException

通过 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.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.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

如果您的实体中有多个用 @Id 注释的列,您必须使用 @IdClass 注释或将其替换为嵌入的 @EmbeddedId。请参阅 How to map a composite key with Hibernate? 以获得出色的解释,它似乎独立于休眠。

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