[java] 최대 절전 모드에서 분리 된 객체를 다시 부착하는 올바른 방법은 무엇입니까?

동일한 ID의 객체가 이미 세션에 존재할 수 있지만 분리 된 객체를 최대 절전 모드 세션에 다시 연결 해야하는 상황이 있습니다. 이로 인해 오류가 발생할 수 있습니다.

지금은 두 가지 중 하나를 수행 할 수 있습니다.

  1. getHibernateTemplate().update( obj )
    최대 절전 모드 세션에 개체가없는 경우에만 작동합니다. 주어진 식별자를 가진 객체가 나중에 필요할 때 세션에 이미 존재한다는 예외가 발생합니다.

  2. getHibernateTemplate().merge( obj )
    최대 절전 모드 세션에 개체가있는 경우에만 작동합니다. 나중에 이것을 사용하면 객체가 세션에 있어야 할 때 예외가 발생합니다.

이 두 가지 시나리오에서 세션을 객체에 일반적으로 어떻게 첨부 할 수 있습니까? 더 우아한 솔루션이 있어야하기 때문에이 문제의 솔루션 흐름을 제어하기 위해 예외를 사용하고 싶지 않습니다.



답변

따라서 JPA에서 오래된 분리 엔티티를 다시 첨부 할 방법이없는 것 같습니다.

merge() 오래된 상태를 DB로 푸시하고 중재 업데이트를 덮어 씁니다.

refresh() 분리 된 엔티티에서 호출 할 수 없습니다.

lock() 분리 된 엔터티에서 호출 할 수없고, 가능한 경우에도 엔터티를 다시 연결하여 ‘LockMode.NONE’인수와 함께 ‘lock’을 호출하면 잠금이 아니라 잠금이 아니라는 것을 암시하는 것이 가장 반 직관적 인 API 디자인입니다. 나는 본 적이있다.

그래서 당신은 붙어 있습니다. 없다 detach()방법은 있지만, attach()reattach(). 개체 수명주기의 분명한 단계는 사용할 수 없습니다.

JPA에 대한 비슷한 질문의 수로 판단 할 때 JPA가 일관된 모델을 가지고 있다고 주장하더라도 많은 시간을 낭비하는 저주를받은 대부분의 프로그래머의 정신 모델과 일치하지 않는 것 같습니다. JPA는 가장 간단한 작업을 수행하고 응용 프로그램 전체에서 캐시 관리 코드로 끝납니다.

그것을 수행하는 유일한 방법은 오래된 분리 된 엔티티를 버리고 동일한 ID로 찾기 쿼리를 수행하여 L2 또는 DB에 충돌하는 것입니다.

미크


답변

이 모든 대답은 중요한 차이점을 놓치고 있습니다. update ()는 객체 그래프를 세션에 (다시) 첨부하는 데 사용됩니다. 전달한 객체는 관리되는 객체입니다.

merge ()는 실제로 (재) 첨부 API가 아닙니다. merge ()에 반환 값이 있습니까? 전달 된 그래프가 아닌 관리되는 그래프를 반환하기 때문입니다. merge ()는 JPA API이며 해당 동작은 JPA 사양에 의해 관리됩니다. merge ()에 전달한 객체가 이미 관리되어있는 경우 (이미 세션과 연결되어 있음) Hibernate가 작동하는 그래프입니다. 전달 된 객체는 merge ()에서 반환 된 것과 동일한 객체입니다. 그러나 merge ()에 전달한 객체가 분리되면 Hibernate는 관리되는 새 객체 그래프를 생성하고 분리 된 그래프의 상태를 새로운 관리 형 그래프로 복사합니다. 다시 말하지만, 이는 모두 JPA 사양에 따라 결정되고 적용됩니다.

“이 엔티티가 관리되는지 또는 관리되도록”에 대한 일반적인 전략의 관점에서 아직 삽입되지 않은 데이터를 고려할 것인지에 따라 다릅니다. 당신이 그렇게 가정하면, 같은 것을 사용하십시오

if ( session.contains( myEntity ) ) {
    // nothing to do... myEntity is already associated with the session
}
else {
    session.saveOrUpdate( myEntity );
}

update () 대신 saveOrUpdate ()를 사용했습니다. 아직 삽입되지 않은 데이터를 처리하지 않으려면 update ()를 대신 사용하십시오.


답변

외교적 대답 : 아마도 확장 된 지속성 컨텍스트를 찾고있을 것입니다. 이것이 Seam Framework 의 주요 이유 중 하나입니다. 특히 Spring에서 Hibernate를 사용하는 데 어려움을 겪고 있다면 Seam의 문서를 확인하십시오 .

외교적 답변 : 이것은 Hibernate docs에 설명되어있다 . 더 자세한 설명이 필요하면 “분리 된 객체로 작업하기”라는 Hibernate를 사용한 Java Persistence 섹션 9.3.2를 살펴보십시오 . 나는 거라고 강하게 당신이 Hibernate에서 CRUD보다 더 많은 것을하고 있다면이 책을받을 것을 권장합니다.


답변

엔터티가 수정되지 않았다고 확신하는 경우 (또는 수정 내용이 손실 될 것이라는 데 동의 한 경우) 잠금을 사용하여 세션에 다시 연결할 수 있습니다.

session.lock(entity, LockMode.NONE);

아무것도 잠그지 않지만 세션 캐시에서 엔티티를 가져 오거나 (없는 경우) DB에서 엔티티를 읽습니다.

“예를 들어 HttpSession의”엔터티에서 관계를 탐색 할 때 LazyInitException을 방지하는 것이 매우 유용합니다. 먼저 엔티티를 “다시 첨부”하십시오.

상속이 매핑 된 경우 (getId ()에서 이미 예외가 발생 함)를 제외하고 get을 사용하는 것도 가능합니다.

entity = session.get(entity.getClass(), entity.getId());


답변

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

엔터티 상태

JPA는 다음 엔티티 상태를 정의합니다.

새로운 (일시적)

Hibernate Session(aka Persistence Context)와 연결되지 않았으며 데이터베이스 테이블 행에 매핑 되지 않은 새로 생성 된 객체 는 New (Transient) 상태 인 것으로 간주됩니다.

지속 되려면 EntityManager#persist메소드 를 명시 적으로 호출 하거나 전이 지속 메커니즘을 사용해야합니다.

지속적 (관리)

지속성 엔티티가 데이터베이스 테이블 행과 연관되었으며 현재 실행중인 지속성 컨텍스트에 의해 관리되고 있습니다. 이러한 엔티티에 대한 모든 변경 사항이 감지되어 세션 세션 시간 동안 데이터베이스에 전파됩니다.

최대 절전 모드를 사용하면 더 이상 INSERT / UPDATE / DELETE 문을 실행할 필요가 없습니다. Hibernate는 트랜잭션 쓰기-비하인드 작업 스타일을 사용하며 현재 Session플러시 시간 동안 가장 마지막 책임있는 순간에 변경 사항이 동기화됩니다 .

분리

현재 실행중인 지속성 컨텍스트가 닫히면 이전에 관리 된 모든 엔티티가 분리됩니다. 연속적인 변경 사항은 더 이상 추적되지 않으며 자동 데이터베이스 동기화가 수행되지 않습니다.

엔터티 상태 전환

EntityManager인터페이스에서 정의한 다양한 방법을 사용하여 엔티티 상태를 변경할 수 있습니다 .

JPA 엔티티 상태 전이를 더 잘 이해하려면 다음 다이어그램을 고려하십시오.

JPA 엔티티 상태 전환

JPA를 사용하는 경우 분리 된 엔티티를 active에 다시 연관시키기 EntityManager위해 병합 조작을 사용할 수 있습니다 .

을 제외하고 기본 Hibernate API를 사용하는 merge경우 다음 다이어그램에 표시된 것처럼 업데이트 방법을 사용하여 분리 된 엔티티를 활성 Hibernate 세션에 다시 연결할 수 있습니다.

최대 절전 모드 상태 전환

분리 된 엔터티 병합

병합이 분리 된 엔티티 상태 (소스)를 관리 엔티티 인스턴스 (대상)에 복사하려고합니다.

다음 Book엔티티 EntityManager를 유지했으며 이제 엔티티를 닫는 데 사용 된 엔티티가 닫히면 엔티티가 분리됩니다 .

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);

    return book;
});

엔티티가 분리 상태 인 동안 다음과 같이 수정합니다.

_book.setTitle(
    "High-Performance Java Persistence, 2nd edition"
);

이제 변경 사항을 데이터베이스에 전파하여 merge메소드를 호출 할 수 있습니다 .

doInJPA(entityManager -> {
    Book book = entityManager.merge(_book);

    LOGGER.info("Merging the Book entity");

    assertFalse(book == _book);
});

그리고 최대 절전 모드는 다음 SQL 문을 실행합니다.

SELECT
    b.id,
    b.author AS author2_0_,
    b.isbn AS isbn3_0_,
    b.title AS title4_0_
FROM
    book b
WHERE
    b.id = 1

-- Merging the Book entity

UPDATE
    book
SET
    author = 'Vlad Mihalcea',
    isbn = '978-9730228236',
    title = 'High-Performance Java Persistence, 2nd edition'
WHERE
    id = 1

병합 엔티티가 current에 해당하는 것이 없으면 EntityManager데이터베이스에서 새로운 엔티티 스냅 샷을 가져옵니다.

관리 대상 엔티티가 있으면 JPA는 분리 된 엔티티의 상태를 현재 관리되는 엔티티에 복사하고 지속성 컨텍스트flush 동안 더티 검사 메커니즘 이 관리 엔티티가 변경된 것을 발견 하면 UPDATE가 생성됩니다 .

따라서를 사용할 때 merge분리 된 객체 인스턴스는 병합 작업 후에도 분리 된 상태를 유지합니다.

분리 된 엔터티 다시 연결

최대 절전 모드이지만 JPA는이 update방법을 통한 재 연결을 지원합니다 .

최대 절전 모드 Session는 지정된 데이터베이스 행에 대해 하나의 엔터티 개체 만 연결할 수 있습니다. 지속성 컨텍스트는 메모리 내 캐시 (첫 번째 레벨 캐시)로 작동하고 하나의 값 (엔티티) 만 주어진 키 (엔티티 유형 및 데이터베이스 식별자)와 연관되기 때문입니다.

현재 Hibernate와 이미 연결된 다른 JVM 오브젝트 (같은 데이터베이스 행과 일치)가없는 경우에만 엔티티를 다시 첨부 할 수 있습니다 Session.

Book엔터티 가 지속되고 엔터티 Book가 분리 된 상태 일 때 수정 한 것을 고려하면 다음 과 같습니다.

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);

    return book;
});

_book.setTitle(
    "High-Performance Java Persistence, 2nd edition"
);

분리 된 엔티티를 다음과 같이 다시 첨부 할 수 있습니다.

doInJPA(entityManager -> {
    Session session = entityManager.unwrap(Session.class);

    session.update(_book);

    LOGGER.info("Updating the Book entity");
});

그리고 최대 절전 모드는 다음 SQL 문을 실행합니다.

-- Updating the Book entity

UPDATE
    book
SET
    author = 'Vlad Mihalcea',
    isbn = '978-9730228236',
    title = 'High-Performance Java Persistence, 2nd edition'
WHERE
    id = 1

update방법은 당신을 필요로 하이버 네이트에 .unwrapEntityManagerSession

와 달리 merge제공된 분리 된 엔티티는 현재 지속성 컨텍스트와 다시 연관되며 엔티티가 수정되었는지 여부에 관계없이 플러시 중에 UPDATE가 스케줄됩니다.

이를 방지하기 위해 @SelectBeforeUpdateHibernate 어노테이션을 사용하여 로드 된 상태를 페치 한 SELECT 문을 트리거 한 다음 더티 점검 메커니즘에서 사용합니다.

@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {

    //Code omitted for brevity
}

NonUniqueObjectException에주의하십시오

update지속성 컨텍스트에 이미 다음 예제에서와 동일한 ID 및 동일한 유형의 엔티티 참조가 포함되어있는 경우 발생할 수있는 한 가지 문제점입니다 .

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    Session session = entityManager.unwrap(Session.class);
    session.saveOrUpdate(book);

    return book;
});

_book.setTitle(
    "High-Performance Java Persistence, 2nd edition"
);

try {
    doInJPA(entityManager -> {
        Book book = entityManager.find(
            Book.class,
            _book.getId()
        );

        Session session = entityManager.unwrap(Session.class);
        session.saveOrUpdate(_book);
    });
} catch (NonUniqueObjectException e) {
    LOGGER.error(
        "The Persistence Context cannot hold " +
        "two representations of the same entity",
        e
    );
}

이제 위의 테스트 케이스를 실행할 때 Hibernate는 NonUniqueObjectException두 번째에 EntityManager이미 Book전달한 것과 동일한 식별자를 가진 엔티티를 포함 update하고 Persistence Context가 동일한 엔티티의 두 가지 표현을 보유 할 수 없기 때문에 a를 던질 것 입니다.

org.hibernate.NonUniqueObjectException:
    A different object with the same identifier value was already associated with the session : [com.vladmihalcea.book.hpjp.hibernate.pc.Book#1]
    at org.hibernate.engine.internal.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:651)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:284)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:227)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:92)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
    at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:682)
    at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:674)

결론

merge방법은 당신이 업데이트 손실을 방지 할 수 있습니다 당신이 낙관적 잠금을 사용하는 경우 선호된다. 이 주제에 대한 자세한 내용은 이 기사를 확인 하십시오 .

이는 작업에 update의해 생성 된 추가 SELECT 문을 방지 merge하여 일괄 업데이트 실행 시간을 줄일 수 있으므로 일괄 업데이트에 유용합니다 .


답변

JavaDoc으로 돌아가서 org.hibernate.Session다음을 발견했습니다.

과도 인스턴스 호출에 의해 영속화 할 수있다 save(), persist()또는
saveOrUpdate(). 을 호출하여 영구 인스턴스를 일시적으로 만들 수 있습니다 delete(). get()또는 load()메소드가 반환 한 모든 인스턴스 는 영구적입니다. 분리 된 인스턴스를 호출하여 영속화 할 수있다 update(), saveOrUpdate(), lock()또는 replicate(). 임시 인스턴스 또는 분리 된 인스턴스의 상태는를 호출하여 새 영구 인스턴스로 유지 될 수도 있습니다 merge().

따라서 update(), saveOrUpdate(), lock(), replicate()merge()후보 옵션이 있습니다.

update(): 식별자가 동일한 영구 인스턴스가 있으면 예외가 발생합니다.

saveOrUpdate(): 저장 또는 업데이트

lock(): 더 이상 사용되지 않음

replicate(): 현재 식별자 값을 재사용하여 주어진 분리 된 인스턴스의 상태를 유지합니다.

merge(): 식별자가 동일한 영속 객체를 반환합니다. 주어진 인스턴스가 세션과 연관되지 않습니다.

따라서, lock()곧바로 사용되어서는 안되며 기능 요구 사항에 따라 하나 이상을 선택할 수 있습니다.


답변

NHibernate를 사용하여 C #에서 그렇게했지만 Java에서 동일한 방식으로 작동해야합니다.

public virtual void Attach()
{
    if (!HibernateSessionManager.Instance.GetSession().Contains(this))
    {
        ISession session = HibernateSessionManager.Instance.GetSession();
        using (ITransaction t = session.BeginTransaction())
        {
            session.Lock(this, NHibernate.LockMode.None);
            t.Commit();
        }
    }
}

Contains가 항상 false이므로 모든 객체에서 첫 번째 잠금이 호출되었습니다. 문제는 NHibernate가 데이터베이스 ID와 유형별로 객체를 비교한다는 것입니다. 포함 equals하지 않은 경우 참조로 비교 하는 메소드를 사용합니다 . 이 equals방법을 사용하면 예외없이 작동합니다.

public override bool Equals(object obj)
{
    if (this == obj) {
        return true;
    }
    if (GetType() != obj.GetType()) {
        return false;
    }
    if (Id != ((BaseObject)obj).Id)
    {
        return false;
    }
    return true;
}