문제

EntityManager.getReference (lobj.getClass (), lobj.getId ())를 사용하여 데이터베이스 엔티티를 얻은 다음 반환 된 개체를 다른 테이블에 지속됩니다.

기본적으로 흐름은 다음과 같습니다.

class TFacade{

  createT(FObj, AObj) {
    T TObj = new 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 = new FLH();
    FLHObj.setF(FObj);
    FLHObj.setL(LObj);
    ....
    EntityManager.persist(FLHObj);
    ...
  }
}

다음과 같은 예외를 얻었습니다.

한동안 조사한 후, 나는 마침내 메소드가 프록시를 반환함에 따라 위의 예외를 얻고 있다는 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);
    }

}

내가 전화하면 찾기 Method, JPA 제공 업체는 무대 뒤에서

SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

내가 전화하면 GetReference Method, 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을 던집니다.

문안 인사,

다른 팁

내가 설명했듯이 이 기사, 부모가 있다고 가정합니다 Post 실체와 아이 PostComment 다음 다이어그램에 설명 된대로 :

enter image description here

전화하면 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)

포스트 엔티티를 가져올 필요가 없기 때문에 선택 쿼리는 이번에는 쓸모가 없습니다. 우리는 기본 포스트 _id 외국 키 열만 설정하려고합니다.

이제 사용하는 경우 getReference 대신에:

PostComment comment = new PostComment();
comment.setReview("Just awesome!");

Post post = entityManager.getReference(Post.class, 1L);
comment.setPost(post);

entityManager.persist(comment);

이번에는 최대 절전 모드가 삽입문 만 발급합니다.

INSERT INTO post_comment (post_id, review, id)
VALUES (1, 'Just awesome!', 1)

같지 않은 find,, getReference 식별자 설정 만있는 엔티티 프록시 만 반환합니다. 프록시에 액세스하면 EntityManager가 여전히 열려있는 한 관련 SQL 문이 트리거됩니다.

그러나이 경우 엔터티 프록시에 액세스 할 필요가 없습니다. 우리는 외국 키를 기본 테이블 레코드로 전파하려고하므로 프록시를로드하면이 유스 케이스에 충분합니다.

프록시를로드 할 때는 EntityManager가 닫힌 후 프록시 참조에 액세스하려고하면 LazyInitializationException을 던질 수 있습니다. 처리에 대한 자세한 내용은 LazyInitializationException, 체크 아웃 이 기사.

참조는 '관리'이지만 수화되지 않았으므로 먼저 메모리에로드 할 필요없이 ID로 엔티티를 제거 할 수 있습니다.

관리되지 않는 엔티티를 제거 할 수 없으므로 Find (...) 또는 Createquery (...)를 사용하여 모든 필드를로드하는 것이 어리석은 일입니다.

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

EntityManager.find () 메소드 대신 EntityManager.getReference () 메소드를 사용하는 것이 언제입니까?

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

EntityManager.getReference () 및 EntityManager.Find () : 오버 헤드 측면에서 차이가 없습니다.

나는 받아 들여진 답변에 동의하지 않습니다.

내가 전화하면 찾기 Method, JPA 제공 업체는 무대 뒤에서

SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

내가 전화하면 GetReference Method, JPA 제공 업체는 무대 뒤에서

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

내가 최대 절전 모드 5와 Javadoc에서 얻는 것은 행동이 아닙니다. getReference() 그런 말을하지 않습니다 :

상태가 게으르게 가져올 수있는 인스턴스를 얻으십시오. 요청 된 인스턴스가 데이터베이스에 존재하지 않으면 인스턴스 상태에 처음 액세스 할 때 EntityNotFoundException이 발생합니다. (getReference가 호출 될 때 지속성 제공 업체 런타임은 EntityNotFoundException을 던질 수 있습니다.) 응용 프로그램은 Entity Manager가 열리는 동안 응용 프로그램에서 액세스하지 않는 한 인스턴스 상태가 분리시 인스턴스 상태를 사용할 수있을 것으로 기대해서는 안됩니다.

EntityManager.getReference() 두 가지 경우에 엔티티를 검색하기 위해 쿼리를 스패링합니다.

1) 엔티티가 지속성 컨텍스트에 저장되면 이는 첫 번째 레벨 캐시입니다.
그리고이 행동은 구체적이지 않습니다 EntityManager.getReference(), EntityManager.find() 엔티티가 지속성 컨텍스트에 저장된 경우 엔티티를 검색하기 위해 쿼리를 예비합니다.

예제로 첫 번째 포인트를 확인할 수 있습니다.
실제 최대 절전 모드 구현에 의존 할 수도 있습니다.
물론, 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.find ()가 EntityManager.getReference ()를 선호하는 이유

오버 헤드 측면에서 getReference() 보다 낫지 않습니다 find() 이전 시점에서 논의 된 바와 같이.
그렇다면 왜 하나 또는 다른 것을 사용합니까?

호출 getReference() 게으르게 가져온 엔티티를 반환 할 수 있습니다.
여기서 게으른 페치는 엔티티의 관계가 아니라 실체 자체를 가리 킵니다.
우리가 호출하면 getReference() 그런 다음 지속 컨텍스트가 닫히고 엔티티가 절대로드되지 않을 수 있으므로 결과는 실제로 예측할 수 없습니다. 예를 들어 프록시 객체가 직렬화되면 null 직렬화 된 결과 또는 프록시 오브젝트에서 메소드가 호출되는 경우, 예외는 다음과 같은 예외입니다. LazyInitializationException 던져졌다.

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

EntityManager.find() 던지는 야망이 없습니다 EntityNotFoundException 엔티티를 찾을 수없는 경우. 그 행동은 간단하고 명확합니다. 항상로드 된 엔티티 또는 null (엔티티를 찾을 수없는 경우) 그러나 효과적으로로드되지 않을 프록시 모양의 엔티티는 절대로 없습니다.
그래서 EntityManager.find() 대부분의 경우에 선호해야합니다.

선택한 답변에 동의하지 않으며 DavidXXX가 올바르게 지적했듯이 GetReference는 선택없이 동적 업데이트의 동작을 제공하지 않습니다. 이 답변의 타당성에 관한 질문을했습니다. 여기를 참조하십시오. Hibernate JPA의 getReference () 후 Setter를 사용하여 SELECT를 발행하지 않고 업데이트 할 수 없습니다..

나는 솔직히 그 기능을 실제로 사용한 사람을 본 적이 없습니다. 어딘가에. 그리고 왜 그것이 그렇게 당겨 졌는지 이해가 안 돼요.

우선, 최대 절전 모드 프록시 객체, 세터 또는 getter에서 무엇을 호출하든 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;

그리고 삽입하려고하더라도 여전히 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);
    }

}
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top