EntityManager.find()와 EntityManager.getReference()를 JPA와 함께 사용하는 경우
데이터베이스 엔티티를 취득하고 반환된 오브젝트를 다른 테이블로 전달하기 위해 EntityManager.getReference(LOBJ.getClass() 및 LOBj.getId()를 사용하는 상황(이상하다고 생각하지만 매우 정상적인 상황일 가능성이 있음)이 발생했습니다.
기본적으로 흐름은 다음과 같습니다.
클래스 TFacade{ createT(FOBJ, AObj) {T TObj = 신규 T();TOBj.setF(FOBJ); TOBj.setA(AObj); ...엔티티 매니저지속(TOBJ); ...L LObj = A.getL();FOBj.setL(LOBJ); FFacade.editF(FOBJ); }} @TransactionAttributeType.필요_신규클래스 FFacade{ 편집F(FOBJ){L LObj = FObj.getL();LOBj = EntityManager.getReference(LOBJ.getClass(), LOBj.getId()); ...Entity Manager.merge(FOBJ); ...FLHFacade.create(FOBj, LOBj); }} @TransactionAttributeType.필수의클래스 FLHFacade{ 만들다FLH(FOBJ, LOBJ){FLH FLHObj = 새로운 FLH();FLHObj.setF(FOBJ); FLHObj.setL(LOBJ); .... 엔티티 매니저지속(FLHObj);...}}
'java.lang'이라는 예외도 있어요부정 인수예외:알 수 없는 엔티티: com.my.interance.L$$EnhancerByCGLIB$3e7987d0"
잠시 조사해보니 EntityManager.getReference() 메서드를 사용하고 있었기 때문에 프록시를 반환하고 있었기 때문에 위의 예외가 발생했음을 알 수 있었습니다.
이것에 의해, EntityManager.find() 메서드가 아닌 EntityManager.getReference() 메서드를 사용하는 것이 언제 권장되는지 궁금하게 됩니다.
EntityManager.getReference()는 검색 대상 엔티티를 찾을 수 없는 경우 EntityNotFoundException을 슬로우합니다.EntityManager.find() 메서드는 엔티티를 찾을 수 없는 경우 null을 반환합니다.
트랜잭션 경계에 대해서입니다만, 새로운 엔티티를 새로운 트랜잭션으로 넘기기 전에 find() 메서드를 사용할 필요가 있다고 생각합니다.getReference() 메서드를 사용하면 위의 예외를 제외하고 저와 비슷한 상황이 될 수 있습니다.
데이터베이스 상태에 액세스할 필요가 없을 때 보통 getReference 메서드를 사용합니다(즉 getter 메서드).단지 상태를 바꾸기 위해서(즉, 세터 방식).아시다시피 getReference는 자동 더티 체크라고 하는 강력한 기능을 사용하는 프록시 개체를 반환합니다.다음과 같이 가정합니다.
public class Person {
private String name;
private Integer age;
}
public class PersonServiceImpl implements PersonService {
public void changeAge(Integer personId, Integer newAge) {
Person person = em.getReference(Person.class, personId);
// person is a proxy
person.setAge(newAge);
}
}
Find 메서드를 호출하면 JPA 프로바이더가 뒤에서 호출합니다.
SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?
UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?
getReference 메서드를 호출하면 JPA 프로바이더가 뒤에서 호출합니다.
UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?
왜 그런지 알아?
getReference를 호출하면 프록시 개체가 나타납니다.이것과 같은 것(JPA 프로바이더가 이 프록시의 실장을 담당)
public class PersonProxy {
// JPA provider sets up this field when you call getReference
private Integer personId;
private String query = "UPDATE PERSON SET ";
private boolean stateChanged = false;
public void setAge(Integer newAge) {
stateChanged = true;
query += query + "AGE = " + newAge;
}
}
따라서 트랜잭션을 커밋하기 전에 JPA 공급자는 사용자 엔티티를 업데이트하거나 업데이트하지 않기 위해 stateChanged 플래그를 확인합니다.update 스테이트먼트 후에 행이 갱신되지 않으면 JPA 프로바이더는 JPA 사양에 따라 EntityNotFoundException을 슬로우합니다.
안부 전해요,
가 Post
PostComment
음음음음음음
「 」에 find
를 @ManyToOne
post
★★★★★★★★★★★★★★★★★★:
PostComment comment = new PostComment();
comment.setReview("Just awesome!");
Post post = entityManager.find(Post.class, 1L);
comment.setPost(post);
entityManager.persist(comment);
휴지 상태에서는, 다음의 문장이 실행됩니다.
SELECT p.id AS id1_0_0_,
p.title AS title2_0_0_
FROM post p
WHERE p.id = 1
INSERT INTO post_comment (post_id, review, id)
VALUES (1, 'Just awesome!', 1)
이번에는 Post 엔티티를 가져올 필요가 없기 때문에 SELECT 쿼리는 사용할 수 없습니다.기본 post_id Foreign Key 열만 설정합니다.
그럼 이제 ,을 쓰면 now을 쓰면 겠습니다''getReference
★★★★
PostComment comment = new PostComment();
comment.setReview("Just awesome!");
Post post = entityManager.getReference(Post.class, 1L);
comment.setPost(post);
entityManager.persist(comment);
이번에는 Hibernate가 INSERT 스테이트먼트만을 발행합니다.
INSERT INTO post_comment (post_id, review, id)
VALUES (1, 'Just awesome!', 1)
★★★★★★★★★★★★★★★와 달리find
, . . . . . . . .getReference
아이디Proxy(프록시) EntityManager(EntityManager)의 SQL(SQL)의 EntityManager(EntityManager).
단, 이 경우 엔티티 프록시에 액세스할 필요가 없습니다.이 사용 예에서는 프록시를 로드하면 충분하도록 외부 키를 기본 테이블 레코드에만 전파하려고 합니다.
프록시를 로드할 때는 EntityManager가 닫힌 후 Proxy 참조에 액세스하려고 하면가 느려질 수 있다는 점에 유의해야 합니다.
이것에 의해, EntityManager.find() 메서드가 아닌 EntityManager.getReference() 메서드를 사용하는 것이 언제 권장되는지 궁금하게 됩니다.
EntityManager.getReference()
에러가 발생하기 쉬운 방법이며 클라이언트코드로 사용할 필요가 있는 경우는 거의 없습니다.
개인적으로는 쓸 필요가 없었어요.
EntityManager.getReference() 및 EntityManager.find() : 오버헤드에 관한 차이는 없습니다.
저는 받아들여진 답변에 동의하지 않으며, 특히 다음과 같습니다.
Find 메서드를 호출하면 JPA 프로바이더가 뒤에서 호출합니다.
SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ? UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?
getReference 메서드를 호출하면 JPA 프로바이더가 뒤에서 호출합니다.
UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?
와 Hibernate 5의 수 .getReference()
안 해요.
상태를 가져올 수 있는 인스턴스를 가져옵니다.요청된 인스턴스가 데이터베이스에 존재하지 않는 경우 인스턴스 상태에 처음 액세스할 때 EntityNotFoundException이 느려집니다.(getReference가 호출되었을 때 지속성 공급자 런타임은 EntityNotFoundException을 슬로우할 수 있습니다).응용 프로그램은 엔티티 매니저가 열려 있는 동안 응용 프로그램에 의해 인스턴스 상태가 액세스되지 않는 한 분리 시 인스턴스 상태를 사용할 수 있을 것으로 예상해서는 안 됩니다.
EntityManager.getReference()
하기 위한 .이 경우 엔티티를 취득할 수 있습니다.
컨텍스트에 되어 있는 , 첫의 캐시입니다.1 )는, 「Cache 「Cache」는 「Cache」입니다
이 .EntityManager.getReference()
,EntityManager.find()
또한 엔티티가 지속성 컨텍스트에 저장되어 있는 경우 엔티티를 취득하기 위한 쿼리를 남깁니다.
첫 번째 점은 어떤 예에서도 확인할 수 있습니다.
또, 실제의 휴지 상태의 실장에도 의존할 수 있습니다.
실로 indeed indeed indeed.EntityManager.getReference()
말은 '의미입니다.createProxyIfNecessary()
의 of의 org.hibernate.event.internal.DefaultLoadEventListener
이치노
은 다음과 .
private Object createProxyIfNecessary(
final LoadEvent event,
final EntityPersister persister,
final EntityKey keyToLoad,
final LoadEventListener.LoadType options,
final PersistenceContext persistenceContext) {
Object existing = persistenceContext.getEntity( keyToLoad );
if ( existing != null ) {
// return existing object or initialized proxy (unless deleted)
if ( traceEnabled ) {
LOG.trace( "Entity found in session cache" );
}
if ( options.isCheckDeleted() ) {
EntityEntry entry = persistenceContext.getEntry( existing );
Status status = entry.getStatus();
if ( status == Status.DELETED || status == Status.GONE ) {
return null;
}
}
return existing;
}
if ( traceEnabled ) {
LOG.trace( "Creating new proxy for entity" );
}
// return new uninitialized proxy
Object proxy = persister.createProxy( event.getEntityId(), event.getSession() );
persistenceContext.getBatchFetchQueue().addBatchLoadableEntityKey( keyToLoad );
persistenceContext.addProxy( keyToLoad, proxy );
return proxy;
}
흥미로운 부분은 다음과 같습니다.
Object existing = persistenceContext.getEntity( keyToLoad );
2) 엔티티를 효과적으로 조작하지 않으면 javadoc의 느릿느릿 페치된 엔티티에 메아리친다.
실제로 기업의 효과적인 로딩을 보장하기 위해, 그 로드에 대한 방법을 호출할 필요가 있다.
따라서 이 이득은 엔티티를 사용할 필요 없이 로딩하려는 시나리오와 관련이 있습니다. 드물며, 이 요구는 매우 드물게 합니다.getReference()
행동 또한 다음 부분을 읽으면 매우 오해의 소지가 있습니다.
EntityManager.getReference()보다 EntityManager.find()를 선호하는 이유
오버헤드에 는, 「 」라고 하는 것이 .getReference()
않다find()
앞서 설명한 바와 같이
왜 둘 중 해야 할까요?
를 호출하다getReference()
취득한 엔티티를 반환할 가능성이 있습니다.
여기서 게으른 페칭은 엔티티의 관계가 아니라 엔티티 자체를 말합니다.
, 이 getReference()
Persistence 컨텍스트가 닫히면 엔티티가 로드되지 않을 수 있기 때문에 결과를 예측할 수 없습니다.를 들어 되어 있는 , 「」를 할 수 .null
에서 메서드가 호출된 경우 .LazyInitializationException
집니니다
즉, 의 투척은EntityNotFoundException
그것이 바로 사용의 주된 이유입니다.getReference()
데이터베이스에 존재하지 않는 인스턴스를 오류 상황으로 처리하려면 엔티티가 존재하지 않는 동안에는 절대 수행되지 않을 수 있습니다.
EntityManager.find()
EntityNotFoundException
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★이치노 또는 로딩된 엔티티가 .null
(엔티티를 찾을 수 없는 경우) 단, 효과적으로 로드되지 않을 수 있는 프록시 형상의 엔티티는 존재하지 않습니다.
★★★★★★★★★★★★★★★★★.EntityManager.find()
대부분의 경우 선호해야 합니다.
참조는 '관리'되지만 수화되지 않기 때문에 먼저 메모리에 로드할 필요 없이 ID로 엔티티를 삭제할 수도 있습니다.
관리대상 외 엔티티를 삭제할 수 없기 때문에 find(...) 또는 create를 사용하여 모든 필드를 로드하는 것은 매우 어리석은 일입니다.Query(...)는 즉시 삭제됩니다.
MyLargeObject myObject = em.getReference(MyLargeObject.class, objectId);
em.remove(myObject);
선택한 답변에 동의하지 않습니다.또한 davidxxxx가 올바르게 지적한 바와 같이 getReference는 선택 없이 동적 업데이트 동작을 제공하지 않습니다.이 답변의 유효성에 관한 질문을 했습니다.여기를 참조해 주세요.- 휴지 상태 JPA의 getReference() 후에 select on setter를 발행하지 않으면 갱신할 수 없습니다.
솔직히 그 기능을 실제로 사용한 사람은 본 적이 없습니다.아무 곳이나.그리고 왜 그렇게 투표율이 높은지 이해가 안 돼요.
우선 휴지 상태의 프록시 오브젝트, 세터, 게터 중 어느 것을 호출하든 SQL이 기동되어 오브젝트가 로드됩니다.
그런데 JPA getReference() 프록시가 이 기능을 제공하지 않으면 어떻게 될까요?난 그냥 내 대리인을 쓸 수 있어.
프라이머리 키의 선택은 쿼리보다 빠르기 때문에 회피할 필요가 없습니다.다만, 여러가지 이유로 대응할 수 없는 분들을 위해서, 이하에 이러한 프록시의 실장을 나타냅니다.그러나 구현에 대해 알아보기 전에 먼저 사용 방법과 사용 방법을 확인하십시오.
사용.
Order example = ProxyHandler.getReference(Order.class, 3);
example.setType("ABCD");
example.setCost(10);
PersistenceService.save(example);
그러면 다음과 같은 질문이 발생합니다.
UPDATE Order SET type = 'ABCD' and cost = 10 WHERE id = 3;
또, 삽입하고 싶은 경우에도, Persistence Service.save(new Order("a", 2))를 실행할 수 있습니다.그러면 삽입이 정상적으로 실행됩니다.
실행
이것을 pom.xml에 추가합니다.
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>
이 클래스에서 동적 프록시를 만듭니다.
@SuppressWarnings("unchecked")
public class ProxyHandler {
public static <T> T getReference(Class<T> classType, Object id) {
if (!classType.isAnnotationPresent(Entity.class)) {
throw new ProxyInstantiationException("This is not an entity!");
}
try {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(classType);
enhancer.setCallback(new ProxyMethodInterceptor(classType, id));
enhancer.setInterfaces((new Class<?>[]{EnhancedProxy.class}));
return (T) enhancer.create();
} catch (Exception e) {
throw new ProxyInstantiationException("Error creating proxy, cause :" + e.getCause());
}
}
모든 메서드를 사용하여 인터페이스를 만듭니다.
public interface EnhancedProxy {
public String getJPQLUpdate();
public HashMap<String, Object> getModifiedFields();
}
이제 프록시에 이러한 메서드를 구현할 수 있는 인터셉터를 만듭니다.
import com.anil.app.exception.ProxyInstantiationException;
import javafx.util.Pair;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import javax.persistence.Id;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
/**
* @author Anil Kumar
*/
public class ProxyMethodInterceptor implements MethodInterceptor, EnhancedProxy {
private Object target;
private Object proxy;
private Class classType;
private Pair<String, Object> primaryKey;
private static HashSet<String> enhancedMethods;
ProxyMethodInterceptor(Class classType, Object id) throws IllegalAccessException, InstantiationException {
this.classType = classType;
this.target = classType.newInstance();
this.primaryKey = new Pair<>(getPrimaryKeyField().getName(), id);
}
static {
enhancedMethods = new HashSet<>();
for (Method method : EnhancedProxy.class.getDeclaredMethods()) {
enhancedMethods.add(method.getName());
}
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
//intercept enhanced methods
if (enhancedMethods.contains(method.getName())) {
this.proxy = obj;
return method.invoke(this, args);
}
//else invoke super class method
else
return proxy.invokeSuper(obj, args);
}
@Override
public HashMap<String, Object> getModifiedFields() {
HashMap<String, Object> modifiedFields = new HashMap<>();
try {
for (Field field : classType.getDeclaredFields()) {
field.setAccessible(true);
Object initialValue = field.get(target);
Object finalValue = field.get(proxy);
//put if modified
if (!Objects.equals(initialValue, finalValue)) {
modifiedFields.put(field.getName(), finalValue);
}
}
} catch (Exception e) {
return null;
}
return modifiedFields;
}
@Override
public String getJPQLUpdate() {
HashMap<String, Object> modifiedFields = getModifiedFields();
if (modifiedFields == null || modifiedFields.isEmpty()) {
return null;
}
StringBuilder fieldsToSet = new StringBuilder();
for (String field : modifiedFields.keySet()) {
fieldsToSet.append(field).append(" = :").append(field).append(" and ");
}
fieldsToSet.setLength(fieldsToSet.length() - 4);
return "UPDATE "
+ classType.getSimpleName()
+ " SET "
+ fieldsToSet
+ "WHERE "
+ primaryKey.getKey() + " = " + primaryKey.getValue();
}
private Field getPrimaryKeyField() throws ProxyInstantiationException {
for (Field field : classType.getDeclaredFields()) {
field.setAccessible(true);
if (field.isAnnotationPresent(Id.class))
return field;
}
throw new ProxyInstantiationException("Entity class doesn't have a primary key!");
}
}
그리고 예외 클래스는-
public class ProxyInstantiationException extends RuntimeException {
public ProxyInstantiationException(String message) {
super(message);
}
이 프록시를 사용하여 저장할 서비스 -
@Service
public class PersistenceService {
@PersistenceContext
private EntityManager em;
@Transactional
private void save(Object entity) {
// update entity for proxies
if (entity instanceof EnhancedProxy) {
EnhancedProxy proxy = (EnhancedProxy) entity;
Query updateQuery = em.createQuery(proxy.getJPQLUpdate());
for (Entry<String, Object> entry : proxy.getModifiedFields().entrySet()) {
updateQuery.setParameter(entry.getKey(), entry.getValue());
}
updateQuery.executeUpdate();
// insert otherwise
} else {
em.persist(entity);
}
}
}
언급URL : https://stackoverflow.com/questions/1607532/when-to-use-entitymanager-find-vs-entitymanager-getreference-with-jpa
'source' 카테고리의 다른 글
Python에서 비동기 메서드 호출? (0) | 2022.11.16 |
---|---|
날짜와 함께 GROUP_CONCAT에서 NOT IN 연산자(<>)를 사용하는 방법 (0) | 2022.11.15 |
Panda 데이터 프레임의 두 열에 함수를 적용하는 방법 (0) | 2022.11.15 |
한 줄 if 또는 루프에 중괄호(예: {})를 사용하는 목적은 무엇입니까? (0) | 2022.11.15 |
Vue 3 앱에서 계산된 속성이 반응하지 않는 이유는 무엇입니까? (0) | 2022.11.15 |