IT

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

lottoking 2020. 8. 25. 08:23
반응형

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


나는 EntityManager.getReference (LObj.getClass (), LObj.getId ())를 사용하여 데이터베이스를 얻은 다음 반환 된 전달을 전달하는 상황 (이상에서 생각했지만 아마도 매우 정상적 임)을 발견했습니다. 다른 테이블에 유지됩니다.

따라서 기본적으로 흐름은 다음과 가변됩니다.

class TFacade {

  createT (FObj, AObj) {
    T TObj = 새로운 T ();
    TObj.setF (FObj);
    TObj.setA (AObj);
    ...
    EntityManager.persist (TObj);
    ...
    L LObj = A.getL ();
    FObj.setL (LObj);
    FFacade.editF (FObj);
  }
}

@ TransactionAttributeType.REQUIRES_NEW
class FFacade {

  editF (FObj) {
    L LObj = FObj.getL ();
    LObj = EntityManager.getReference (LObj.getClass (), LObj.getId ());
    ...
    EntityManager.merge (FObj);
    ...
    FLHFacade.create (FObj, LObj);
  }
}

@ TransactionAttributeType.REQUIRED
class FLHFacade {

  createFLH (FObj, LObj) {
    FLH FLHObj = 새로운 FLH ();
    FLHObj.setF (FObj);
    FLHObj.setL (LObj);
    ....
    EntityManager.persist (FLHObj);
    ...
  }
}

"java.lang.IllegalArgumentException : 알 수없는 가수 : com.my.persistence.L $$ EnhancerByCGLIB $$ 3e7987d0"예외가 발생했습니다.

잠시 후에 본 후 마침내 EntityManager.getReference () 메서드를 사용했기 때문에 메서드가 프록시를 사용할 때 위의 예외가 발생한다는 것을 알게되었습니다.

이것은 언제 EntityManager.find () 메서드 대신 EntityManager.getReference () 메서드를 사용하는 것이 좋을까요?

EntityManager.getReference ()는 자체적으로 매우 편리한 검색이 많은 것을 사용하는 경우 EntityNotFoundException을 발생합니다. EntityManager.find () 메서드는 반환을 반환합니다.

트랜잭션 경계와하여 새로 발견 된 엔터티를 새 트랜잭션에 전달하기 전에 () 메서드를 전달하는 것입니다. getReference () 메서드를 사용하면 위의 예외를 제외하고 내 상황과 상황에 처하게됩니다.


일반적으로 데이터베이스 상태에 액세스 할 필요가 없을 때 getReference 메서드를 사용합니다 (getter 메서드를 의미합니다). 상태를 변경하기 위해 (내 말은 setter 메서드를 의미합니다) 아시다시피 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);
    }

}

찾을 메소드를 호출하면 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 플래그를 업데이트합니다. 업데이트 된 문 나중에 행이 업데이트되지 않은 문서 JPA 공급자는 JPA 사양에 따라 EntityNotFoundException을 throw합니다.

문안 인사,


이 문서 에서 설명했듯이 다음 다이어그램에 설명 된대로 부모 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);

Hibernate는 다음 문을 실행합니다.

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 외래 키 열만 설정합니다.

이제 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유일한 식별자 만 세트가 프록시를 반환합니다. 프록시에 액세스하는 경우 EntityManager가 아직 열려있는 한 이미지가 SQL 문이 트리거됩니다.

그러나이 경우 엔터티 프록시에 액세스 할 필요가 없습니다. 우리는 외래 키를 기본 테이블 레코드로만 전파하기를 원합니다.

Proxy를로드 할 때 EntityManager가 닫힌 후 Proxy 참조에 액세스하려고 LazyInitializationException이 숨겨져 있습니다. 에, 대한 자세한 취급 내용은 이 기사를LazyInitializationException 확인 하십시오 .


참조는 '관리'를 사용할 수 없습니다.

관리되지 않는 엔터티를 제거 할 수 없기 때문에 find (...) 또는 createQuery (...)를 사용하여 즉시 삭제하는 것뿐입니다.

MyLargeObject myObject = em.getReference(MyLargeObject.class, objectId);
em.remove(myObject);

이것은 언제 EntityManager.find () 메서드 대신 EntityManager.getReference () 메서드를 사용하는 것이 좋을까요?

EntityManager.getReference()오류가 발생하기 쉬운 방법이며 클라이언트 코드에서이를 사용하는 경우는 거의 없습니다.
개인적으로 나는 그것을 사용할 필요가 없습니다.

EntityManager.getReference () 및 EntityManager.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에서 얻는 행동이 javadoc은 getReference()그런 말 을하지 않습니다.

상태를 느리게보다 수있는 인스턴스를 가져옵니다. 인스턴스 상태에 처음 액세스 할 때 EntityNotFoundException이 발생합니다. (지속성 공급자 실행은 getReference가 호출 될 때 EntityNotFoundException을 발생시킬 수 있습니다.) 응용 프로그램은 관리자가 열려있는 동안 응용 프로그램에서 액세스하지 않는 한 분리시 인스턴스 상태를 사용할 수 있습니다.

EntityManager.getReference() 두 가지 경우에 항목을 검색하기 위해 쿼리를 예비합니다.

1) 엔터티가 Persistence 많은 경우에 첫 번째 수준 캐시입니다.
그리고이 문제는 특정하지 EntityManager.getReference(), EntityManager.find()기업이 영속 검색어에 저장되어있는 경우도 예비 쿼리는 개체를 검색하는 것입니다.

예를 들어 첫 번째 요점을 확인할 수 있습니다.
실제 Hibernate 구현에 의존 할 수도 있습니다.
실제로 클래스 EntityManager.getReference()메소드 createProxyIfNecessary()의존 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()한 다음 지속성이 발생하지 않을 경우 결과를 예측할 수 없음을 의미합니다. 예를 들어 프록시 생성이 발생합니다. 된 경우에 null생성 된 결과로 참조를 가져 오기 오거나 프록시에서 메소드가 호출 할 때 다음과 같은 예외 LazyInitializationException가 발생합니다.

이는 엔티티가 존재하지 않는 동안 오류 상황이 수행되지 않을 수 있으므로 데이터베이스에 존재하지 않는 인스턴스를 처리하는 EntityNotFoundException데 사용하는 주된 이유 getReference()입니다.

EntityManager.find()EntityNotFoundException개체가 발견되지 않은 던지 하려는 야망 이 없습니다. 그 행동은 간단하고 명확합니다. 항상로드 된 엔티티 또는 null(엔터티가 발견되지 않은 경우) 반환 되지만 효과적으로로드되지 않을 수있는 프록시 모양의 엔티티는 반환하지 않으므로 놀라지 않을을 구석으로입니다.
따라서 EntityManager.find()대부분의 경우 선호되어야합니다.


나는 동의하지 않습니다. davidxxx는 동의하지 않습니다. getReference는 선택하지 않고 동적 업데이트를 제공하지 않습니다. 이 답변의 유효성에 관한 질문을했습니다. 여기를 참조하십시오 - 최대 절전 모드 JPA의의 getReference () 후 세터를 사용하여 선택를 발행하지 않고 업데이트 할 수 없습니다 .

솔직히 그 기능을 실제로 사람은 본 적이 없습니다. 어딘가에. 그리고 나는 그것이 왜 그렇게 upvoted인지 이해하지 못합니다.

우선, 하이버 네이트 프록시에서 무엇을 호출하든 getter를 호출하든 SQL이 실행되고 객체가로드됩니다.

하지만 JPA getReference () 프록시가 해당 기능을 제공하지 어떻게 될까요? 나만의 프록시를 사용할 수 있습니다.

이제 우리는 기본 키에 대한 선택이 쿼리가 얻을 수있는 것만 큼 빠르며 피해야 할 큰 길이로 이동하는 것이 실제로는 아니라고 주장 할 수 있습니다. 그러나 어떤 MMT 든 처리 할 수없는 사람들을 위해 다음은 프록시의 구현입니다. 그러나 구현을보기 전에 사용법과 사용이 얼마나 간단한 지 확인하십시오.

용법

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;

삽입하려는 경우에도 PersistenceService.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

반응형