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

나는 대일 관계를 포함하는 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객체 와 관련된를 올바르게 저장하려면 어떻게해야 합니까?



답변

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

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

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

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


답변

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

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

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


답변

자식 엔티티에서 계단식을 제거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에서 삭제됩니다. 그러나 그것은 당신의 의도가 아니 었습니까?


답변

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

여기에 완전한 코드가 표시되지 않으므로 거래 패턴을 다시 확인할 수 없습니다. 이와 같은 상황에 도달하는 한 가지 방법은 병합 및 지속을 실행할 때 트랜잭션이 활성화되어 있지 않은 경우입니다. 이 경우 지속성 공급자는 수행하는 모든 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);
}


답변

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


답변

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


답변

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

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

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);
}

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