source

EntityManager.find()와 EntityManager.getReference()를 JPA와 함께 사용하는 경우

manycodes 2022. 11. 15. 21:35
반응형

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을 슬로우합니다.

안부 전해요,

PostPostComment음음음음음음

여기에 이미지 설명 입력

「 」에 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

반응형