JPA EntityManager : merge ()보다 persist ()를 사용하는 이유는 무엇입니까?
EntityManager.merge()
새 개체를 삽입하고 기존 개체를 업데이트 할 수 있습니다.
persist()
(새 개체 만 만들 수있는) 사용하려는 이유는 무엇 입니까?
어느 쪽이든 PersistenceContext에 엔터티를 추가하지만, 그 차이는 나중에 엔터티로 수행하는 작업에 있습니다.
Persist는 엔티티 인스턴스를 가져와 컨텍스트에 추가하고 해당 인스턴스를 관리합니다 (즉, 엔티티에 대한 향후 업데이트가 추적 됨).
Merge는 엔터티의 새 인스턴스를 만들고 제공된 엔터티에서 상태를 복사하고 새 복사본을 관리합니다. 전달한 인스턴스는 관리되지 않습니다 (Merge를 다시 호출하지 않는 한 모든 변경 사항은 트랜잭션의 일부가 아닙니다).
아마도 코드 예제가 도움이 될 것입니다.
MyEntity e = new MyEntity();
// scenario 1
// tran starts
em.persist(e);
e.setSomeField(someValue);
// tran ends, and the row for someField is updated in the database
// scenario 2
// tran starts
e = new MyEntity();
em.merge(e);
e.setSomeField(anotherValue);
// tran ends but the row for someField is not updated in the database
// (you made the changes *after* merging)
// scenario 3
// tran starts
e = new MyEntity();
MyEntity e2 = em.merge(e);
e2.setSomeField(anotherValue);
// tran ends and the row for someField is updated
// (the changes were made to e2, not e)
시나리오 1과 3은 거의 동일하지만 시나리오 2를 사용하려는 몇 가지 상황이 있습니다.
지속과 병합은 두 가지 목적을위한 것입니다 (대안이 아님).
(차이점 정보 확장을 위해 편집 됨)
지속 :
- 데이터베이스에 새 레지스터 삽입
- 개체를 개체 관리자에 연결합니다.
병합 :
- 동일한 ID를 가진 연결된 개체를 찾아 업데이트합니다.
- 존재하는 경우 업데이트하고 이미 연결된 개체를 반환합니다.
- 존재하지 않는 경우 데이터베이스에 새 레지스터를 삽입하십시오.
persist () 효율성 :
- merge ()보다 데이터베이스에 새 레지스터를 삽입하는 것이 더 효율적일 수 있습니다.
- 원래 개체를 복제하지 않습니다.
persist () 의미 :
- 실수로 삽입하고 업데이트하지 않았는지 확인합니다.
예:
{
AnyEntity newEntity;
AnyEntity nonAttachedEntity;
AnyEntity attachedEntity;
// Create a new entity and persist it
newEntity = new AnyEntity();
em.persist(newEntity);
// Save 1 to the database at next flush
newEntity.setValue(1);
// Create a new entity with the same Id than the persisted one.
AnyEntity nonAttachedEntity = new AnyEntity();
nonAttachedEntity.setId(newEntity.getId());
// Save 2 to the database at next flush instead of 1!!!
nonAttachedEntity.setValue(2);
attachedEntity = em.merge(nonAttachedEntity);
// This condition returns true
// merge has found the already attached object (newEntity) and returns it.
if(attachedEntity==newEntity) {
System.out.print("They are the same object!");
}
// Set 3 to value
attachedEntity.setValue(3);
// Really, now both are the same object. Prints 3
System.out.println(newEntity.getValue());
// Modify the un attached object has no effect to the entity manager
// nor to the other objects
nonAttachedEntity.setValue(42);
}
이 방법은 엔티티 관리자의 모든 레지스터에 대해 하나의 연결된 개체 만 존재합니다.
ID가있는 엔티티에 대한 merge ()는 다음과 같습니다.
AnyEntity myMerge(AnyEntity entityToSave) {
AnyEntity attached = em.find(AnyEntity.class, entityToSave.getId());
if(attached==null) {
attached = new AnyEntity();
em.persist(attached);
}
BeanUtils.copyProperties(attached, entityToSave);
return attached;
}
MySQL merge ()에 연결하면 ON DUPLICATE KEY UPDATE 옵션을 사용하여 INSERT 호출을 사용하여 persist ()만큼 효율적일 수 있지만 JPA는 매우 높은 수준의 프로그래밍이며 이것이 모든 곳에서 해당 될 것이라고 가정 할 수는 없습니다.
할당 된 생성기를 사용하는 경우 persist 대신 merge를 사용하면 중복 SQL 문이 발생할 수 있으므로 성능에 영향을 미칠 수 있습니다.
또한 관리되는 엔티티에 대해 병합을 호출하는 것도 실수입니다. 관리되는 엔티티는 Hibernate에 의해 자동으로 관리되고 Persistence Context 를 플러시 할 때 더티 검사 메커니즘 에 의해 해당 상태가 데이터베이스 레코드와 동기화되기 때문 입니다.
이 모든 것이 어떻게 작동하는지 이해하려면 먼저 Hibernate가 개발자의 사고 방식을 SQL 문에서 엔티티 상태 전환으로 전환 한다는 것을 알아야합니다 .
엔티티가 Hibernate에 의해 능동적으로 관리되면 모든 변경 사항이 자동으로 데이터베이스에 전파됩니다.
Hibernate는 현재 연결된 엔티티를 모니터링합니다. 그러나 엔티티가 관리 되려면 올바른 엔티티 상태에 있어야합니다.
먼저 모든 엔티티 상태를 정의해야합니다.
신규 (임시)
Hibernate
Session
(일명Persistence Context
)와 연관 되지 않았고 데이터베이스 테이블 행에 매핑 되지 않은 새로 생성 된 객체 는 New (Transient) 상태로 간주됩니다.지속되기 위해서는
EntityManager#persist
메서드 를 명시 적으로 호출 하거나 전이 지속성 메커니즘을 사용해야합니다.영구 (관리)
영구 엔터티는 데이터베이스 테이블 행과 연결되었으며 현재 실행중인 Persistence Context에 의해 관리되고 있습니다. 이러한 엔터티에 대한 모든 변경 사항은 감지되어 데이터베이스에 전파됩니다 (세션 플러시 시간 동안). Hibernate를 사용하면 더 이상 INSERT / UPDATE / DELETE 문을 실행할 필요가 없습니다. Hibernate는 트랜잭션의 write-behind 작업 스타일을 사용하며 변경 사항은 현재
Session
플러시 시간 동안 가장 마지막 책임있는 순간에 동기화됩니다 .분리됨
현재 실행중인 지속성 컨텍스트가 닫히면 이전에 관리 된 모든 엔티티가 분리됩니다. 연속적인 변경 사항은 더 이상 추적되지 않으며 자동 데이터베이스 동기화가 발생하지 않습니다.
분리 된 엔티티를 활성 Hibernate 세션에 연결하려면 다음 옵션 중 하나를 선택할 수 있습니다.
다시 연결
Hibernate (JPA 2.1 아님)는 Session # update 메소드를 통한 재 연결을 지원합니다. Hibernate Session은 주어진 데이터베이스 행에 대해 하나의 Entity 객체 만 연관시킬 수 있습니다. 이는 지속성 컨텍스트가 메모리 내 캐시 (첫 번째 수준 캐시)로 작동하고 하나의 값 (엔티티) 만 주어진 키 (엔티티 유형 및 데이터베이스 식별자)에 연결되기 때문입니다. 엔티티는 현재 Hibernate Session에 이미 연관된 다른 JVM 객체 (동일한 데이터베이스 행과 일치)가없는 경우에만 재 부착 될 수 있습니다.
병합
병합은 분리 된 엔티티 상태 (소스)를 관리 엔티티 인스턴스 (대상)에 복사합니다. 병합 엔터티가 현재 세션에 해당하는 항목이 없으면 데이터베이스에서 하나를 가져옵니다. 분리 된 개체 인스턴스는 병합 작업 후에도 계속 분리 된 상태로 유지됩니다.
제거됨
JPA는 관리되는 엔터티 만 제거하도록 요구하지만 Hibernate는 분리 된 엔터티를 삭제할 수도 있습니다 (그러나 Session # delete 메서드 호출을 통해서만 가능). 제거 된 엔터티는 삭제 만 예약되며 실제 데이터베이스 DELETE 문은 세션 플러시 시간 동안 실행됩니다.
JPA 상태 전환을 더 잘 이해하기 위해 다음 다이어그램을 시각화 할 수 있습니다.
또는 Hibernate 특정 API를 사용하는 경우 :
을 사용 했을 때 JPA가 생성하는 필드가없는 경우에도 모든에 em.merge
대한 SELECT
문을 받았습니다 INSERT
. 기본 키 필드는 내가 직접 설정 한 UUID였습니다. 나는로 전환하여 진술을 em.persist(myEntityObject)
받았습니다 INSERT
.
JPA 사양은 persist()
.
경우 X는 분리 된 객체가되면,이
EntityExistsException
(가) 작업이 호출 지속, 또는 경우에 throw 될 수 있습니다EntityExistsException
또는 다른이PersistenceException
같은 높이에서 던져 질 수 또는 시간을 커밋합니다.
따라서 persist()
개체 가 분리 된 개체 가 아니어야 할 때 사용하는 것이 적합 합니다. 코드를 던져서 PersistenceException
빨리 실패하도록하는 것이 좋습니다.
하지만 사양이 명확하지 않다 , persist()
을 설정할 수 있습니다 @GeneratedValue
@Id
개체에 대한. merge()
그러나 @Id
이미 생성 된 개체가 있어야합니다 .
병합을 사용하는 데 도움이 될 병합에 대한 자세한 내용은 지속됩니다.
원래 엔터티가 아닌 관리되는 인스턴스를 반환하는 것은 병합 프로세스의 중요한 부분입니다. 동일한 식별자를 가진 엔터티 인스턴스가 지속성 컨텍스트에 이미있는 경우 공급자는 병합중인 엔터티의 상태로 해당 상태를 덮어 쓰지만 이미 존재하는 관리되는 버전을 클라이언트에 반환해야합니다. 익숙한. 공급자가 지속성 컨텍스트에서 Employee 인스턴스를 업데이트하지 않은 경우 해당 인스턴스에 대한 참조는 병합되는 새 상태와 일치하지 않게됩니다.
새 엔티티에서 merge ()를 호출하면 persist () 작업과 유사하게 작동합니다. 지속성 컨텍스트에 엔티티를 추가하지만 원래 엔티티 인스턴스를 추가하는 대신 새 사본을 작성하고 대신 해당 인스턴스를 관리합니다. merge () 작업에 의해 생성 된 복사본은 persist () 메서드가 호출 된 것처럼 유지됩니다.
In the presence of relationships, the merge() operation will attempt to update the managed entity to point to managed versions of the entities referenced by the detached entity. If the entity has a relationship to an object that has no persistent identity, the outcome of the merge operation is undefined. Some providers might allow the managed copy to point to the non-persistent object, whereas others might throw an exception immediately. The merge() operation can be optionally cascaded in these cases to prevent an exception from occurring. We will cover cascading of the merge() operation later in this section. If an entity being merged points to a removed entity, an IllegalArgumentException exception will be thrown.
지연로드 관계는 병합 작업에서 특별한 경우입니다. 지연로드 관계가 분리되기 전에 엔터티에서 트리거되지 않은 경우 엔터티가 병합 될 때 해당 관계가 무시됩니다. 관리되는 동안 관계가 트리거 된 다음 엔터티가 분리 된 동안 null로 설정된 경우 엔터티의 관리되는 버전도 병합 중에 관계가 지워집니다. "
위의 모든 정보는 Mike Keith와 Merrick Schnicariol이 작성한 "Pro JPA 2 Mastering the Java ™ Persistence API"에서 가져온 것입니다. 6 장. 섹션 분리 및 병합. 이 책은 실제로 저자가 JPA에 전념 한 두 번째 책입니다. 이 새 책에는 이전의 것보다 많은 새로운 정보가 있습니다. 나는 JPA에 진지하게 관여 할 사람들을 위해이 책을 읽는 것이 정말 좋습니다. 첫 번째 답변을 익명으로 게시 해 죄송합니다.
merge
와 사이에는 더 많은 차이점이 있습니다 persist
(여기에 이미 게시 된 내용을 다시 열거하겠습니다).
D1. merge
전달 된 엔터티를 관리하지 않고 관리되는 다른 인스턴스를 반환합니다. persist
다른 쪽에서는 전달 된 엔터티를 관리합니다.
//MERGE: passedEntity remains unmanaged, but newEntity will be managed
Entity newEntity = em.merge(passedEntity);
//PERSIST: passedEntity will be managed after this
em.persist(passedEntity);
D2. 당신이 개체를 제거하고 개체 등을 지속하기로 결정하는 경우 때문에, 당신은 전용) (지속으로 그렇게 할 수 merge
가 발생합니다 IllegalArgumentException
.
D3. ID를 수동으로 관리하기로 결정한 경우 (예 : UUID 사용), 해당 ID를 가진 기존 엔티티를 찾기 위해 merge
작업이 후속 SELECT
쿼리를 트리거 하지만 persist
해당 쿼리는 필요하지 않을 수 있습니다.
D4. 코드를 호출하는 코드를 단순히 신뢰하지 않는 경우가 있으며, 데이터가 업데이트되지 않고 삽입되었는지 확인하려면 persist
.
세션에있는 지연로드 된 컬렉션에 액세스하려고했기 때문에 엔터티에서 lazyLoading 예외가 발생했습니다.
내가 할 일은 별도의 요청으로 세션에서 엔티티를 검색 한 다음 문제가있는 jsp 페이지의 컬렉션에 액세스하려고하는 것입니다.
이 문제를 완화하기 위해, 나는 내 컨트롤러에서 동일한 개체를 업데이트하고 난 그것도하지만 액세스 할 수 있음 세션에서 저장 한 다시 때 상상하지만, 내 JSP에 전달 SessionScope
던져 아니라 LazyLoadingException
, 예를 들어 2의 수정 :
다음은 나를 위해 일했습니다.
// scenario 2 MY WAY
// tran starts
e = new MyEntity();
e = em.merge(e); // re-assign to the same entity "e"
//access e from jsp and it will work dandy!!
답변을 살펴보면 'Cascade'및 ID 생성과 관련하여 누락 된 세부 정보가 있습니다. 질문보기
또한, 별도 가질 수 있음을 언급 할 가치가있다 Cascade
: 병합 및 지속에 대한 주석을 Cascade.MERGE
하고 Cascade.PERSIST
있는이 사용 된 방법에 따라 처리됩니다.
사양은 당신의 친구입니다;)
Hibernate 문서에서이 설명이 유스 케이스를 포함하고 있기 때문에 깨달음을 얻었습니다.
merge ()의 사용법과 의미는 새로운 사용자에게 혼란스러워 보입니다. 첫째, 하나의 엔티티 관리자에로드 된 객체 상태를 다른 새 엔티티 관리자에서 사용하지 않는 한 merge ()를 전혀 사용할 필요 가 없습니다 . 일부 전체 응용 프로그램은이 방법을 사용하지 않습니다.
일반적으로 merge ()는 다음 시나리오에서 사용됩니다.
- 응용 프로그램은 첫 번째 엔티티 관리자에서 객체를로드합니다.
- 개체는 프레젠테이션 레이어로 전달됩니다.
- 개체가 일부 수정되었습니다.
- 객체는 비즈니스 로직 계층으로 다시 전달됩니다.
- 응용 프로그램은 두 번째 엔티티 관리자에서 merge ()를 호출하여 이러한 수정 사항을 유지합니다.
merge ()의 정확한 의미는 다음과 같습니다.
- 현재 지속성 컨텍스트와 연결된 동일한 식별자를 가진 관리되는 인스턴스가있는 경우 지정된 개체의 상태를 관리되는 인스턴스에 복사합니다.
- 현재 지속성 컨텍스트와 연결된 관리 형 인스턴스가없는 경우 데이터베이스에서로드하거나 새 관리 형 인스턴스를 만듭니다.
- 관리되는 인스턴스가 반환됩니다.
- 주어진 인스턴스는 지속성 컨텍스트와 연관되지 않고 분리 된 상태로 유지되며 일반적으로 폐기됩니다.
출처 : http://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/objectstate.html
JPA는 의심 할 여지없이 Java 플랫폼에 구축 된 엔터프라이즈 애플리케이션 도메인에서 대단한 단순화입니다. J2EE에있는 오래된 엔티티 빈의 복잡성에 대처해야했던 개발자로서 저는 JPA가 Java EE 사양에 포함 된 것이 큰 도약이라고 생각합니다. 그러나 JPA 세부 사항을 자세히 살펴보면서 쉽지 않은 것을 발견했습니다. 이 기사에서는 중첩 동작이 초보자뿐만 아니라 혼동을 일으킬 수있는 EntityManager의 병합 및 지속 메서드를 비교합니다. 또한 두 가지 방법을보다 일반적인 방법이 결합 된 특수한 경우로 보는 일반화를 제안합니다.
지속되는 엔티티
병합 방법과 달리 지속 방법은 매우 간단하고 직관적입니다. 지속 방법의 사용에 대한 가장 일반적인 시나리오는 다음과 같이 요약 할 수 있습니다.
"엔티티 클래스의 새로 생성 된 인스턴스가 persist 메서드에 전달됩니다.이 메서드가 반환 된 후 엔터티가 관리되고 데이터베이스에 삽입되도록 계획됩니다. 트랜잭션 커밋 또는 플러시 메서드가 호출 될 때 또는 이전에 발생할 수 있습니다. 엔티티가 PERSIST 캐스케이드 전략으로 표시된 관계를 통해 다른 엔티티를 참조하는 경우에도이 절차가 적용됩니다. "
사양은 세부 사항에 더 많이 들어가지만 이러한 세부 사항은 다소 이국적인 상황만을 다루기 때문에 그것들을 기억하는 것은 중요하지 않습니다.
엔티티 병합
지속성에 비해 병합 동작에 대한 설명은 그렇게 간단하지 않습니다. 영속적 인 경우처럼 주요 시나리오가 없으며 프로그래머는 올바른 코드를 작성하기 위해 모든 시나리오를 기억해야합니다. JPA 디자이너는 분리 된 엔티티를 처리하는 것이 주요 관심사 인 일부 메소드를 원했던 것 같습니다 (새로 생성 된 엔티티를 주로 처리하는 지속 메소드와 반대입니다.) 병합 메소드의 주요 작업은 다음에서 상태를 전송하는 것입니다. 관리되지 않는 엔터티 (인수로 전달됨)가 지속성 컨텍스트 내에서 관리 대상에 전달됩니다. 그러나이 작업은 전체 메서드 동작의 명료성을 악화시키는 여러 시나리오로 더 나뉩니다.
JPA 사양의 단락을 반복하는 대신 병합 방법의 동작을 개략적으로 설명하는 흐름도를 준비했습니다.
그렇다면 언제 persist를 사용해야하고 언제 병합해야합니까?
지속
- 메서드가 항상 새 항목을 만들고 항목을 업데이트하지 않기를 원합니다. 그렇지 않으면 메서드는 기본 키 고유성 위반의 결과로 예외를 throw합니다.
- 상태 저장 방식으로 엔티티를 처리하는 일괄 처리 (게이트웨이 패턴 참조).
- 성능 최적화
병합
- 메소드가 데이터베이스에 엔티티를 삽입하거나 업데이트하기를 원합니다.
- 상태 비 저장 방식으로 엔티티를 처리하려고합니다 (서비스의 데이터 전송 개체).
- You want to insert a new entity that may have a reference to another entity that may but may not be created yet (relationship must be marked MERGE). For example, inserting a new photo with a reference to either a new or a preexisting album.
Scenario X:
Table:Spitter (One) ,Table: Spittles (Many) (Spittles is Owner of the relationship with a FK:spitter_id)
This scenario results in saving : The Spitter and both Spittles as if owned by Same Spitter.
Spitter spitter=new Spitter();
Spittle spittle3=new Spittle();
spitter.setUsername("George");
spitter.setPassword("test1234");
spittle3.setSpittle("I love java 2");
spittle3.setSpitter(spitter);
dao.addSpittle(spittle3); // <--persist
Spittle spittle=new Spittle();
spittle.setSpittle("I love java");
spittle.setSpitter(spitter);
dao.saveSpittle(spittle); //<-- merge!!
Scenario Y:
This will save the Spitter, will save the 2 Spittles But they will not reference the same Spitter!
Spitter spitter=new Spitter();
Spittle spittle3=new Spittle();
spitter.setUsername("George");
spitter.setPassword("test1234");
spittle3.setSpittle("I love java 2");
spittle3.setSpitter(spitter);
dao.save(spittle3); // <--merge!!
Spittle spittle=new Spittle();
spittle.setSpittle("I love java");
spittle.setSpitter(spitter);
dao.saveSpittle(spittle); //<-- merge!!
Another observation:
merge()
will only care about an auto-generated id(tested on IDENTITY
and SEQUENCE
) when a record with such an id already exists in your table. In that case merge()
will try to update the record. If, however, an id is absent or is not matching any existing records, merge()
will completely ignore it and ask a db to allocate a new one. This is sometimes a source of a lot of bugs. Do not use merge()
to force an id for a new record.
persist()
on the other hand will never let you even pass an id to it. It will fail immediately. In my case, it's:
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist
hibernate-jpa javadoc has a hint:
Throws: javax.persistence.EntityExistsException - if the entity already exists. (If the entity already exists, the EntityExistsException may be thrown when the persist operation is invoked, or the EntityExistsException or another PersistenceException may be thrown at flush or commit time.)
You may have come here for advice on when to use persist and when to use merge. I think that it depends the situation: how likely is it that you need to create a new record and how hard is it to retrieve persisted data.
Let's presume you can use a natural key/identifier.
Data needs to be persisted, but once in a while a record exists and an update is called for. In this case you could try a persist and if it throws an EntityExistsException, you look it up and combine the data:
try { entityManager.persist(entity) }
catch(EntityExistsException exception) { /* retrieve and merge */ }
Persisted data needs to be updated, but once in a while there is no record for the data yet. In this case you look it up, and do a persist if the entity is missing:
entity = entityManager.find(key);
if (entity == null) { entityManager.persist(entity); }
else { /* merge */ }
If you don't have natural key/identifier, you'll have a harder time to figure out whether the entity exist or not, or how to look it up.
The merges can be dealt with in two ways, too:
- If the changes are usually small, apply them to the managed entity.
- If changes are common, copy the ID from the persisted entity, as well as unaltered data. Then call EntityManager::merge() to replace the old content.
persist (entity)는 완전히 새로운 엔티티와 함께 사용하여 DB에 추가해야합니다 (엔티티가 DB에 이미 존재하는 경우 EntityExistsException 발생).
엔티티가 분리되어 변경된 경우 엔티티를 지속성 컨텍스트로 되돌리려면 merge (entity)를 사용해야합니다.
아마도 지속은 INSERT SQL 문을 생성하고 UPDATE SQL 문을 병합하는 것입니다 (하지만 확실하지 않습니다).
참고 URL : https://stackoverflow.com/questions/1069992/jpa-entitymanager-why-use-persist-over-merge
'program story' 카테고리의 다른 글
JSON 파일을 prettyprint하는 방법은 무엇입니까? (0) | 2020.09.28 |
---|---|
Android에서 '컨텍스트'를 얻는 정적 방법? (0) | 2020.09.28 |
MySQL datetime 필드 및 일광 절약 시간 — "추가"시간을 어떻게 참조합니까? (0) | 2020.09.25 |
파일이 아닌 저장소의 루트에서만 속성 변경 사항을 커밋합니다. (0) | 2020.09.25 |
virtualenv 대신 글로벌 사이트 패키지에 pip 설치 (0) | 2020.09.25 |