Hibernate : 모든 게으른 컬렉션을 가져 오는 모범 사례


92

내가 가진 것 :

@Entity
public class MyEntity {
  @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
  @JoinColumn(name = "myentiy_id")
  private List<Address> addreses;

  @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
  @JoinColumn(name = "myentiy_id")
  private List<Person> persons;

  //....
}

public void handle() {

   Session session = createNewSession();
   MyEntity entity = (MyEntity) session.get(MyEntity.class, entityId);
   proceed(session); // FLUSH, COMMIT, CLOSE session!

   Utils.objectToJson(entity); //TROUBLES, because it can't convert to json lazy collections
}

무슨 문제 :

문제는 세션이 종료 된 후에 게으른 수집을 가져올 수 없다는 것입니다. 그러나 나는 또한 진행 방법 에서 세션을 닫을 수 없습니다 .

어떤 해결책 (거친 해결책) :

a) 세션이 닫히기 전에 최대 절전 모드에서 지연된 컬렉션을 가져옵니다.

entity.getAddresses().size();
entity.getPersons().size();

....

b) 아마도 더 우아한 방법은 @Fetch(FetchMode.SUBSELECT)주석 을 사용하는 것입니다.

질문:

모범 사례 / 일반적인 방법 /보다 우아한 방법은 무엇입니까? 내 개체를 JSON으로 변환하는 것을 의미합니다.

답변:


102

Hibernate.initialize()@Transactional에서 사용 하여 게으른 개체를 초기화합니다.

 start Transaction 
      Hibernate.initialize(entity.getAddresses());
      Hibernate.initialize(entity.getPersons());
 end Transaction 

이제 트랜잭션의 외부에서 게으른 개체를 얻을 수 있습니다.

entity.getAddresses().size();
entity.getPersons().size();

1
매력적으로 보입니다). @Fetch (FetchMode.SUBSELECT)를 사용할지 이해했듯이 Hibernate.initialize를 한 번만 호출하여 모든 컬렉션을 가져올 수 있습니다. 내가 맞아?
VB_ 2013

4
그리고 MyEntity 컬렉션을 검색 할 때 어떻게 관리합니까?
알렉시스 Dufrenoy

1
트랜잭션의 컬렉션에서 "size ()"와 같은 메서드를 호출하면 초기화도 수행되므로 초기화 후 예제가 가장 좋지 않습니다. 이것은 "Hibernate.initialize (...)"가 collection.size ()보다 의미 상 낫기 때문에 최고의 조언을 가지고 있습니다.
Tristan

7

동일한 트랜잭션에서 Hibernate 개체의 Getter를 탐색하여 다음 일반 도우미 클래스를 사용하여 모든 지연 자식 개체를 열심히 가져올 수 있습니다 .

HibernateUtil.initializeObject (myObject, "my.app.model");

package my.app.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

import org.aspectj.org.eclipse.jdt.core.dom.Modifier;
import org.hibernate.Hibernate;

public class HibernateUtil {

public static byte[] hibernateCollectionPackage = "org.hibernate.collection".getBytes();

public static void initializeObject( Object o, String insidePackageName ) {
    Set<Object> seenObjects = new HashSet<Object>();
    initializeObject( o, seenObjects, insidePackageName.getBytes() );
    seenObjects = null;
}

private static void initializeObject( Object o, Set<Object> seenObjects, byte[] insidePackageName ) {

    seenObjects.add( o );

    Method[] methods = o.getClass().getMethods();
    for ( Method method : methods ) {

        String methodName = method.getName();

        // check Getters exclusively
        if ( methodName.length() < 3 || !"get".equals( methodName.substring( 0, 3 ) ) )
            continue;

        // Getters without parameters
        if ( method.getParameterTypes().length > 0 )
            continue;

        int modifiers = method.getModifiers();

        // Getters that are public
        if ( !Modifier.isPublic( modifiers ) )
            continue;

        // but not static
        if ( Modifier.isStatic( modifiers ) )
            continue;

        try {

            // Check result of the Getter
            Object r = method.invoke( o );

            if ( r == null )
                continue;

            // prevent cycles
            if ( seenObjects.contains( r ) )
                continue;

            // ignore simple types, arrays und anonymous classes
            if ( !isIgnoredType( r.getClass() ) && !r.getClass().isPrimitive() && !r.getClass().isArray() && !r.getClass().isAnonymousClass() ) {

                // ignore classes out of the given package and out of the hibernate collection
                // package
                if ( !isClassInPackage( r.getClass(), insidePackageName ) && !isClassInPackage( r.getClass(), hibernateCollectionPackage ) ) {
                    continue;
                }

                // initialize child object
                Hibernate.initialize( r );

                // traverse over the child object
                initializeObject( r, seenObjects, insidePackageName );
            }

        } catch ( InvocationTargetException e ) {
            e.printStackTrace();
            return;
        } catch ( IllegalArgumentException e ) {
            e.printStackTrace();
            return;
        } catch ( IllegalAccessException e ) {
            e.printStackTrace();
            return;
        }
    }

}

private static final Set<Class<?>> IGNORED_TYPES = getIgnoredTypes();

private static boolean isIgnoredType( Class<?> clazz ) {
    return IGNORED_TYPES.contains( clazz );
}

private static Set<Class<?>> getIgnoredTypes() {
    Set<Class<?>> ret = new HashSet<Class<?>>();
    ret.add( Boolean.class );
    ret.add( Character.class );
    ret.add( Byte.class );
    ret.add( Short.class );
    ret.add( Integer.class );
    ret.add( Long.class );
    ret.add( Float.class );
    ret.add( Double.class );
    ret.add( Void.class );
    ret.add( String.class );
    ret.add( Class.class );
    ret.add( Package.class );
    return ret;
}

private static Boolean isClassInPackage( Class<?> clazz, byte[] insidePackageName ) {

    Package p = clazz.getPackage();
    if ( p == null )
        return null;

    byte[] packageName = p.getName().getBytes();

    int lenP = packageName.length;
    int lenI = insidePackageName.length;

    if ( lenP < lenI )
        return false;

    for ( int i = 0; i < lenI; i++ ) {
        if ( packageName[i] != insidePackageName[i] )
            return false;
    }

    return true;
}
}

이 답변에 감사드립니다. 나는 그것이 오래되었다는 것을 알고 있지만 이것을 해결하려고 노력했고 여기에서 귀하의 코드를 읽을 때까지 느리게 진행되었습니다. 또한 두 번째 메서드의 시작 부분에 ifs를 추가했습니다. initializeObject (object, seenObjects, insidePackageName) : if (object instanceof List) { for(Object item : (List<Object>) object) { initializeObject(item, seenObjects, insidePackageName); } return; } else if (object instanceof Set) { for(Object item : (Set<Object>) object) { initializeObject(item, seenObjects, insidePackageName); } return; } 그렇지 않으면 무시되는 목록 반복.
Chip

o.getClass (). getMethods ();에서 SecurityException이 발생하면 어떻게됩니까?
Oleksii Kyslytsyn

6

최선의 해결책은 아니지만 다음은 내가 얻은 것입니다.

1)이 주석으로 초기화하려는 getter에 주석을 추가하십시오.

@Retention(RetentionPolicy.RUNTIME)
public @interface Lazy {

}

2) 데이터베이스에서 읽은 다음 객체에이 메서드를 사용합니다 (일반 클래스에 넣거나 Object 클래스로 T를 변경할 수 있음).

    public <T> void forceLoadLazyCollections(T entity) {

    Session session = getSession().openSession();
    Transaction tx = null;
    try {

        tx = session.beginTransaction();
        session.refresh(entity);
        if (entity == null) {
            throw new RuntimeException("Entity is null!");
        }
        for (Method m : entityClass.getMethods()) {

            Lazy annotation = m.getAnnotation(Lazy.class);
            if (annotation != null) {
                m.setAccessible(true);
                logger.debug(" method.invoke(obj, arg1, arg2,...); {} field", m.getName());
                try {
                    Hibernate.initialize(m.invoke(entity));
                }
                catch (Exception e) {
                    logger.warn("initialization exception", e);
                }
            }
        }

    }
    finally {
        session.close();
    }
}

lazyCollections를로드하기 위해 반복에서 session.refresh를 사용합니다. 내 엔터티 중 하나에 대해서만 프로그램을 실행할 때마다 session.refresh를 호출 한 후 LazyInitializationException 및 기타 컬렉션이로드되었습니다. 어떻게 이런 일이 일어날 수 있었
는지

5

Utils.objectToJson (entity); 세션을 닫기 전에 호출하십시오.

또는 가져 오기 모드를 설정하고 다음과 같은 코드로 재생할 수 있습니다.

Session s = ...
DetachedCriteria dc = DetachedCriteria.forClass(MyEntity.class).add(Expression.idEq(id));
dc.setFetchMode("innerTable", FetchMode.EAGER);
Criteria c = dc.getExecutableCriteria(s);
MyEntity a = (MyEntity)c.uniqueResult();

FetchMode.EAGER는 더 이상 사용되지 않습니다. javadoc은 지금 FetchMode.JOIN을 사용할 것을 권장합니다.
알렉시스 Dufrenoy

4

Hibernate 4.1.6에서는 이러한 지연 연관 문제를 처리하기위한 새로운 기능이 도입되었습니다. hibernate.properties 또는 hibernate.cfg.xml에서 hibernate.enable_lazy_load_no_trans 속성을 활성화하면 더 이상 LazyInitializationException이 발생하지 않습니다.

자세한 내용은 https://stackoverflow.com/a/11913404/286588을 참조하십시오.


3
이것은 실제로 안티 패턴입니다. 자세한 정보 : vladmihalcea.com/…
Ph03n1x

3

여러 컬렉션을 가져와야하는 경우 다음을 수행해야합니다.

  1. 하나의 컬렉션 FETCH 가입
  2. Hibernate.initialize나머지 컬렉션 에는 을 사용하십시오 .

따라서 귀하의 경우 다음과 같은 첫 번째 JPQL 쿼리가 필요합니다.

MyEntity entity = session.createQuery("select e from MyEntity e join fetch e.addreses where e.id 
= :id", MyEntity.class)
.setParameter("id", entityId)
.getSingleResult();

Hibernate.initialize(entity.persons);

이렇게하면 2 개의 SQL 쿼리로 목표를 달성하고 Cartesian Product를 피할 수 있습니다.


안녕하세요 Vlad, Hibernate#initialize(entity.getSubSet())getSubSet가을 반환하면 호출 하면 작동합니까 Collections.unmodifyableSet(this.subSet)? 나는 시도했지만 그렇지 않았습니다. 언더 레이 컬렉션은 'PersistentSet'입니다. 전화와 같은 이야기#size()
Vadim Kirilchuk

그러나 아마도 문제는 내가 나중에 포함하고 내 등호가 게터가 아닌 직접 필드 액세스를 사용한다는 것입니다.
Vadim Kirilchuk

내 대답에 제공된 단계를 따르면 작동합니다.
Vlad Mihalcea

2

아마도 모범 사례에 접근하는 곳은 없지만 일반적으로 SIZE제안한 것처럼 동일한 트랜잭션에서 자식을로드하기 위해 컬렉션 에서을 호출합니다 . 깨끗하고 자식 요소의 구조 변경에 영향을받지 않으며 낮은 오버 헤드로 SQL을 생성합니다.


0

사용해보십시오 Gson라이브러리를 하여 객체를 Json으로 변환

서블릿의 예 :

  List<Party> parties = bean.getPartiesByIncidentId(incidentId);
        String json = "";
        try {
            json = new Gson().toJson(parties);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write(json);

0

jpa 저장소를 사용하는 경우 properties.put ( "hibernate.enable_lazy_load_no_trans", true); jpaPropertymap에


0

당신은 사용할 수 있습니다 @NamedEntityGraph엔터티에 주석을 쿼리에로드 할 컬렉션을 설정하는로드 가능한 쿼리를 만들 수 있습니다.

이 선택의 주요 이점은 hibernate가 엔티티와 컬렉션을 검색하기 위해 하나의 단일 쿼리를 만들고 다음과 같이이 그래프를 사용하기로 선택한 경우에만 생성된다는 것입니다.

엔티티 구성

@Entity
@NamedEntityGraph(name = "graph.myEntity.addresesAndPersons", 
attributeNodes = {
    @NamedAttributeNode(value = "addreses"),
    @NamedAttributeNode(value = "persons"
})

용법

public MyEntity findNamedGraph(Object id, String namedGraph) {
        EntityGraph<MyEntity> graph = em.getEntityGraph(namedGraph);

        Map<String, Object> properties = new HashMap<>();
        properties.put("javax.persistence.loadgraph", graph);

        return em.find(MyEntity.class, id, properties);
    }

0

JPA-Hibernate의 게으른 컬렉션에 대해 일종의 오해가 있습니다. 우선 지연 컬렉션을 읽으려고하면 예외가 발생하고 단순히 변환 또는 추가 사용 사례에 대해 NULL을 반환하는 것이 아니라는 점을 분명히하겠습니다 ..

데이터베이스의 Null 필드, 특히 조인 된 열에있는 Null 필드는 프로그래밍 언어와 같이 단순히 표시되지 않은 상태가 아니라 의미를 갖기 때문입니다. 지연 컬렉션을 Null 값으로 해석하려고 할 때 이는 (Datastore 측에서) 이러한 항목간에 관계가 없으며 사실이 아님을 의미합니다. 따라서 예외를 던지는 것은 일종의 모범 사례이며 Hibernate가 아닌이를 처리해야합니다.

따라서 위에서 언급했듯이 다음을 권장합니다.

  1. 원하는 개체를 수정하거나 쿼리를 위해 상태 비 저장 세션을 사용하기 전에 분리
  2. lazy 필드를 원하는 값 (0, null 등)으로 조작합니다.

또한 다른 답변에서 설명한 것처럼 많은 접근 방식 (eager fetch, joining 등) 또는 라이브러리 및 방법이 있지만 문제를 처리하고 해결하기 전에 무슨 일이 일어나고 있는지에 대한 견해를 설정해야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.