PersistentObjectException : 분리 된 엔티티가 JPA 및 Hibernate에 의해 지속되도록 전달됨


237

나는 대일 관계를 포함하는 JPA-지속 객체 모델이 있습니다이 Account많이 있습니다 Transactions. A Transaction는 하나가 Account있습니다.

다음은 코드 스 니펫입니다.

@Entity
public class Transaction {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
    private Account fromAccount;
....

@Entity
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @OneToMany(cascade = {CascadeType.ALL},fetch= FetchType.EAGER, mappedBy = "fromAccount")
    private Set<Transaction> transactions;

Account객체 를 생성하고 트랜잭션을 추가하며 Account객체를 올바르게 유지할 수 있습니다. 내가 트랜잭션을 생성 할 때, 이미 존재하는 계정을 지속 사용 하고, 지속 트랜잭션을 , 나는 예외를 얻을 :

원인 : org.hibernate.PersistentObjectException : 분리 된 엔티티가 지속되도록 전달되었습니다. com.paulsanwald.Account at org.hibernate.event.internal.DefaultPersistEventListener.onPersist (DefaultPersistEventListener.java:141)

따라서 Account트랜잭션이 포함 된 트랜잭션 을 유지할 수는 있지만 트랜잭션이있는 트랜잭션은 유지할 수 없습니다 Account. 나는 이것이 Account첨부되지 않았기 때문이라고 생각 했지만이 코드는 여전히 동일한 예외를 제공합니다.

if (account.getId()!=null) {
    account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
 // the below fails with a "detached entity" message. why?
entityManager.persist(transaction);

Transaction이미 지속 된 Account객체 와 관련된를 올바르게 저장하려면 어떻게해야 합니까?


15
제 경우에는 엔터티 관리자를 사용하여 유지하려는 엔터티의 ID를 설정하고있었습니다. id의 setter를 제거하면 정상적으로 작동하기 시작했습니다.
Rushi Shah

내 경우에는 ID를 설정하지 않았지만 동일한 계정을 사용하는 두 명의 사용자가 있었고 그중 하나는 엔티티를 올바르게 유지했으며 두 번째 사용자는 동일한 엔티티를 유지하려고 할 때 오류가 발생했습니다. 지속.
sergioFC

답변:


129

이것은 전형적인 양방향 일관성 문제입니다. 그것은 잘 설명되어 이 링크 뿐만 아니라 이 링크.

이전 2 개의 링크에있는 기사에 따라 양방향 관계의 양쪽에서 세터를 수정해야합니다. 한쪽에 대한 예제 설정자가이 링크에 있습니다.

Many 측의 세터 예제는 이 링크에 있습니다.

세터를 정정 한 후 엔티티 액세스 유형을 "속성"으로 선언하려고합니다. "속성"액세스 유형을 선언하는 가장 좋은 방법은 모든 주석을 멤버 속성에서 해당 게터로 이동하는 것입니다. 엔티티 클래스 내에서 "필드"및 "속성"액세스 유형을 혼합하지 않는 것이 중요합니다. 그렇지 않으면 JSR-317 사양에 따라 동작이 정의되지 않습니다.


2
ps : @Id 주석은 최대 절전 모드가 액세스 유형을 식별하는 데 사용하는 주석입니다.
Diego Plentz

2
예외는 detached entity passed to persist왜 일관성을 향상시키는 것이 효과가 있는가? 좋아, 일관성은 복구되었지만 개체는 여전히 분리되어 있습니다.
Gilgamesz

1
@Sam, 설명해 주셔서 감사합니다. 그러나 나는 아직도 이해하지 못한다. '특별한'세터가 없으면 양방향 관계가 만족스럽지 않습니다. 그러나 왜 객체가 분리되었는지 알 수 없습니다.
Gilgamesz

28
링크로만 답변되는 답변을 게시하지 마십시오.
El Mac

6
이 답변이 질문과 어떤 관련이 있는지 알 수 없습니까?
Eugen Labun

270

해결책은 간단 합니다. 또는 CascadeType.MERGE대신에를 사용하십시오 .CascadeType.PERSISTCascadeType.ALL

나는 같은 문제가 있었고 CascadeType.MERGE나를 위해 일했다.

당신이 정렬되기를 바랍니다.


5
놀랍게도 그 사람도 저를 위해 일했습니다. CascadeType.ALL에 다른 모든 캐스케이드 유형이 포함되어 있기 때문에 의미가 없습니다 ... WTF? 스프링 4.0.4, 스프링 데이터 jpa 1.8.0 및 최대 절전 모드 4.X가 있습니다. 왜 ALL이 작동하지 않지만 MERGE가 작동하는지 생각하는 사람이 있습니까?
Vadim Kirilchuk

22
@VadimKirilchuk 이것은 저에게도 효과가 있었고 완전히 이해됩니다. Transaction은 PERSISTED이므로 계정을 PERSIST로 시도하지만 계정이 이미 db에 있으므로 작동하지 않습니다. 그러나 CascadeType.MERGE를 사용하면 계정이 대신 자동으로 병합됩니다.
건슬링거

4
트랜잭션을 사용하지 않는 경우 발생할 수 있습니다.
lanoxx

1
또 다른 해결책 : 이미 존재하는 객체를 삽입하지 마십시오 :)
Andrii Plotnikov

3
고마워요. 참조 키가 NOT NULL로 제한되는 경우 지속 형 객체 삽입을 피할 수 없습니다. 이것이 유일한 해결책입니다. 다시 감사합니다.
makkasi

22

자식 엔티티에서 계단식을 제거Transaction 하면 다음과 같습니다.

@Entity class Transaction {
    @ManyToOne // no cascading here!
    private Account account;
}

( FetchType.EAGER의 기본값이므로 제거 가능 @ManyToOne)

그게 다야!

왜? 자식 엔터티에 "cascade ALL"이라고 말하면 Transaction모든 DB 작업이 부모 엔터티로 전파되어야합니다 Account. 당신이 다음 할 경우 persist(transaction), persist(account)뿐만 아니라 호출됩니다.

그러나 persist( Transaction이 경우) 임시 (신규) 엔티티 만 전달 될 수 있습니다 . 분리 된 (또는 다른 비 일시적 상태) 상태는 그렇지 않을 수 있습니다 ( Account이 경우 이미 DB에 있으므로).

따라서 "분리 된 엔티티가 지속되도록 전달됨" 예외가 발생 합니다. Account실체는 의미! Transaction당신이 전화 하지 않습니다 persist.


일반적으로 자녀를 부모로 전파하고 싶지 않습니다. 불행히도 책에는 (좋은 것조차도) 많은 책과 그물을 통해 많은 코드 예제가 있습니다. 왜 그런지 모르겠다 ... 아마도 때때로 많은 생각없이 단순히 반복해서 복사했을 것이다.

remove(transaction)@ManyToOne에서 "캐스케이드 ALL"을 계속 호출하면 어떻게 될까요? account(BTW, 다른 모든 트랜잭션!)뿐만 아니라 DB에서 삭제됩니다. 그러나 그것은 당신의 의도가 아니 었습니까?


의도가 실제로 부모와 함께 자녀를 저장하고 DB에서 자동 생성 한 addressId와 함께 person (parent) 및 address (child)와 같이 child와 함께 부모를 삭제 한 다음 Person on save를 호출하기 전에 추가하고 싶습니다. 거래 방법에 주소를 저장하기위한 전화. 이렇게하면 DB에서 생성 한 id와 함께 저장됩니다. Hibenate가 여전히 2 개의 쿼리를 수행하므로 성능에 영향을 미치지 않습니다. 쿼리 순서 만 변경하고 있습니다.
Vikky

우리가 아무것도 전달할 수 없다면 모든 경우에 필요한 기본값입니다.
Dhwanil Patel

15

병합을 사용하는 것은 위험하고 까다롭기 때문에 더러운 해결 방법입니다. 최소한 엔티티 객체를 전달하여 병합하면 중지 한다는 것을 기억해야 합니다. 할 때 트랜잭션 연결이 되고 대신 연결된 새로운 엔터티가 반환 . 즉, 기존 엔티티 객체를 소유하고있는 사람이 있으면 변경 사항이 자동으로 무시되고 커밋시 폐기됩니다.

여기에 완전한 코드가 표시되지 않으므로 거래 패턴을 다시 확인할 수 없습니다. 이와 같은 상황에 도달하는 한 가지 방법은 병합 및 지속을 실행할 때 트랜잭션이 활성화되어 있지 않은 경우입니다. 이 경우 지속성 공급자는 수행하는 모든 JPA 작업에 대해 새 트랜잭션을 열고 호출이 반환되기 전에 즉시 커밋하고 닫습니다. 이 경우 첫 번째 트랜잭션에서 병합이 실행 된 다음 merge 메소드가 리턴 된 후 트랜잭션이 완료되고 닫히고 리턴 된 엔티티가 분리됩니다. 아래의 지속성은 두 번째 트랜잭션을 열고 분리 된 엔티티를 참조하려고 시도하여 예외를 제공합니다. 자신이하는 일을 잘 모르면 항상 코드를 트랜잭션 안에 넣습니다.

컨테이너 관리 트랜잭션을 사용하면 다음과 같습니다. 참고 : 이는 메소드가 세션 Bean 내에 있고 로컬 또는 원격 인터페이스를 통해 호출되었다고 가정합니다.

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void storeAccount(Account account) {
    ...

    if (account.getId()!=null) {
        account = entityManager.merge(account);
    }

    Transaction transaction = new Transaction(account,"other stuff");

    entityManager.persist(account);
}

나는 같은 문제에 직면하고있다. 나는 @Transaction(readonly=false)서비스 계층에서 사용했다 ., 여전히 같은 문제를 겪고있다.
Senthil Arumugam SP

왜 이런 식으로 작동하는지 이해할 수는 없지만 Transactional 주석 내에 Entity mapping에 persist 메소드와 뷰를 배치하면 문제가 해결되었습니다.
Deadron

이것은 실제로 가장 많이 찬성 된 것보다 더 나은 솔루션입니다.
Aleksei Maide

13

이 경우 아마도 account병합 논리를 사용 하여 객체를 얻었으며 persist새 객체를 유지하는 데 사용되며 계층 구조에 이미 유지 된 객체가 있는지 불평합니다. saveOrUpdate이러한 경우 대신 대신 사용해야합니다 persist.


3
그것은 JPA이므로 비슷한 방법은 .merge ()라고 생각하지만 동일한 예외가 발생합니다. 명확하게 말하면 거래는 새로운 개체이며 계정은 아닙니다.
Paul Sanwald

사용 @PaulSanwald mergetransaction같은 오류가 목적?
dan

사실, 아뇨. .merge (transaction)이면 트랜잭션이 전혀 지속되지 않습니다.
Paul Sanwald

@PaulSanwald 흠, transaction그것이 지속되지 않았습니까? 어떻게 확인 했습니까? 주 merge지속 된 개체에 대한 참조를 반환합니다.
dan

.merge ()에 의해 리턴 된 오브젝트는 널 ID를 갖습니다. 또한 나중에 .findAll ()을 수행하고 있으며 내 객체가 없습니다.
Paul Sanwald

12

id (pk)를 전달하여 메소드를 유지하거나 persist () 대신 save () 메소드를 시도하지 마십시오.


2
좋은 조언! 그러나 id가 생성 된 경우에만. 할당 된 경우 ID를 설정하는 것이 정상입니다.
Aldian

이것은 나를 위해 일했습니다. 또한 TestEntityManager.persistAndFlush()인스턴스를 관리하고 지속적으로 설정하고 지속성 컨텍스트를 기본 데이터베이스에 동기화하기 위해 사용할 수 있습니다 . 원래 소스 엔티티를 반환
Anyul Rivas

7

이것은 매우 일반적인 질문 이므로이 기사를 썼습니다. 답변을 기반으로하는이 했습니다.

문제를 해결하려면 다음 단계를 수행해야합니다.

1. 하위 연관 계단식 제거

따라서 연결 @CascadeType.ALL에서 를 제거해야합니다 @ManyToOne. 하위 엔터티는 상위 연결로 계단식이되어서는 안됩니다. 상위 엔터티 만 하위 엔터티에 캐스케이드되어야합니다.

@ManyToOne(fetch= FetchType.LAZY)

열성적인 페치는 성능이 매우 나쁘기 때문에 fetch속성을로 설정했습니다 .FetchType.LAZY

2. 협회의 양쪽을 설정

양방향 연관이있을 때마다 상위 엔티티에서 addChildremoveChild메소드를 사용하여 양쪽을 동기화해야합니다 .

public void addTransaction(Transaction transaction) {
    transcations.add(transaction);
    transaction.setAccount(this);
}

public void removeTransaction(Transaction transaction) {
    transcations.remove(transaction);
    transaction.setAccount(null);
}

양방향 연결의 양쪽 끝을 동기화하는 것이 중요한 이유에 대한 자세한 내용은 이 문서를 확인 하십시오 .


4

엔터티 정의 에서에 조인 된 @JoinColumn 을 지정하지 않았습니다 . 다음과 같은 것을 원할 것입니다 :AccountTransaction

@Entity
public class Transaction {
    @ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
    @JoinColumn(name = "accountId", referencedColumnName = "id")
    private Account fromAccount;
}

편집 : 글쎄, @Table클래스 에서 주석을 사용하는 경우 유용 할 것 입니다. 허. :)


2
그래, 나는 이것이 모두 동일하다고 생각하지 않는다. 나는 @JoinColumn (name = "fromAccount_id", ReferenceColumnName = "id")을 추가했고 작동하지 않았다. :)
Paul Sanwald

예, 일반적으로 엔터티를 테이블에 매핑하는 데 매핑 XML 파일을 사용하지 않으므로 일반적으로 주석 기반이라고 가정합니다. 그러나 추측해야한다면, hibernate.xml을 사용하여 엔티티를 테이블에 매핑하고 있습니까?
NemesisX00

아니요, 스프링 데이터 JPA를 사용하고 있으므로 주석 기반입니다. 나는 반대편에 "mappedBy"주석이 : @OneToMany을 (캐스케이드 = {CascadeType.ALL} 가져 = FetchType.EAGER, mappedBy = "fromAccount")
폴 Sanwald

4

일대 다 관계를 올바르게 관리하기 위해 주석이 올바르게 선언되어 있어도이 정확한 예외가 발생할 수 있습니다. Transaction연결된 자식 데이터 모델에 새 자식 개체를 추가 할 때는 원하지 않는 한 기본 키 값을 관리 해야합니다 . 를 호출하기 전에 다음과 같이 선언 된 자식 엔터티에 기본 키 값을 제공하면 persist(T)이 예외가 발생합니다.

@Entity
public class Transaction {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
....

이 경우 주석은 데이터베이스가 삽입시 엔티티의 기본 키 값 생성을 관리한다고 선언합니다. 자신을 제공하면 (예 : ID 세터를 통해)이 예외가 발생합니다.

대안 적으로, 그러나 실제로는이 주석 선언은 동일한 예외를 발생시킵니다.

@Entity
public class Transaction {
    @Id
    @org.hibernate.annotations.GenericGenerator(name="system-uuid", strategy="uuid")
    @GeneratedValue(generator="system-uuid")
    private Long id;
....

따라서 id이미 관리되고있는 응용 프로그램 코드에서 값을 설정하지 마십시오 .


4

도움이되지 않고 여전히이 예외가 발생하면 equals()메소드를 검토하십시오 . 하위 콜렉션을 포함하지 마십시오. 특히 임베디드 컬렉션의 깊은 구조를 가진 경우 (예 : A는 B를 포함하고, B는 C를 포함하는 등)

예를 들면 다음과 Account -> Transactions같습니다.

  public class Account {

    private Long id;
    private String accountName;
    private Set<Transaction> transactions;

    @Override
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (!(obj instanceof Account))
        return false;
      Account other = (Account) obj;
      return Objects.equals(this.id, other.id)
          && Objects.equals(this.accountName, other.accountName)
          && Objects.equals(this.transactions, other.transactions); // <--- REMOVE THIS!
    }
  }

위의 예에서 equals()수표 에서 거래를 제거하십시오 . 최대 절전 모드는 이전 개체를 업데이트하려고 시도하지 않지만 자식 컬렉션의 요소를 변경할 때마다 지속되도록 새 개체를 전달하기 때문입니다.
물론이 솔루션은 모든 응용 분야에 적합하지는 않으며 equalshashCode방법 에 포함 할 대상을 신중하게 설계해야합니다 .


1

어쩌면 OpenJPA의 버그 일 수 있습니다. 롤백하면 @Version 필드가 재설정되지만 pcVersionInit는 그대로 유지됩니다. @Version 필드를 선언 한 AbstraceEntity가 있습니다. pcVersionInit 필드를 재설정하여 해결할 수 있습니다. 그러나 좋은 생각이 아닙니다. 계단식 엔티티를 유지하면 작동하지 않는다고 생각합니다.

    private static Field PC_VERSION_INIT = null;
    static {
        try {
            PC_VERSION_INIT = AbstractEntity.class.getDeclaredField("pcVersionInit");
            PC_VERSION_INIT.setAccessible(true);
        } catch (NoSuchFieldException | SecurityException e) {
        }
    }

    public T call(final EntityManager em) {
                if (PC_VERSION_INIT != null && isDetached(entity)) {
                    try {
                        PC_VERSION_INIT.set(entity, false);
                    } catch (IllegalArgumentException | IllegalAccessException e) {
                    }
                }
                em.persist(entity);
                return entity;
            }

            /**
             * @param entity
             * @param detached
             * @return
             */
            private boolean isDetached(final Object entity) {
                if (entity instanceof PersistenceCapable) {
                    PersistenceCapable pc = (PersistenceCapable) entity;
                    if (pc.pcIsDetached() == Boolean.TRUE) {
                        return true;
                    }
                }
                return false;
            }

1

모든 계정에 대해 거래를 설정해야합니다.

foreach(Account account : accounts){
    account.setTransaction(transactionObj);
}

또는 많은면에서 ID를 null로 설정하기에 충분하다면 (적절한 경우) colud입니다.

// list of existing accounts
List<Account> accounts = new ArrayList<>(transactionObj.getAccounts());

foreach(Account account : accounts){
    account.setId(null);
}

transactionObj.setAccounts(accounts);

// just persist transactionObj using EntityManager merge() method.

1
cascadeType.MERGE,fetch= FetchType.LAZY

1
안녕하세요, 환영합니다. 코드 전용 답변을 시도하고 피해야합니다. 이것이 문제에 언급 된 문제를 해결하는 방법을 알려주세요 (해당되는 경우 API 수준 등).
Maarten Bodewes

VLQ 리뷰어는 다음을 참조 meta.stackoverflow.com/questions/260411/...를 . 코드 전용 답변은 삭제를 수행 할 가치가 없습니다. 적절한 조치는 "확인"을 선택하는 것입니다.
Nathan Hughes


0

내 경우에는 persist 메소드를 사용할 때 트랜잭션을 커밋했습니다 . 저장 방법으로 지속 을 변경 하면 해결되었습니다.


0

위의 솔루션이 한 번만 작동하지 않으면 엔티티 클래스의 getter 및 setter 메소드를 주석 처리하고 id 값을 설정하지 마십시오 (기본 키) 그러면 작동합니다.


0

@OneToMany (mappedBy = "xxxx", cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE})가 나를 위해 일했습니다.


0

다음에 종속 오브젝트를 저장하여 해결합니다.

이것은 ID를 설정하지 않았기 때문에 나에게 발생했습니다 (자동 생성되지 않음). @ManytoOne 관계로 저장하려고합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.