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
}

有什么问题:

问题是,在会话关闭后,我无法拉动惰性收集。但是我也无法在proceed方法中关闭会话。

什么解决方案(粗略解决方案):

a)在会话关闭之前,强制休眠以拉动惰性集合

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

....

b)也许更轻松的方法是使用@Fetch(FetchMode.SUBSELECT)注释

题:

什么是最佳做法/常见方式/更轻松的方式?意味着将我的对象转换为JSON。

Answers:


102

Hibernate.initialize()在内部使用@Transactional初始化惰性对象。

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

现在,在Transaction之外,您可以获取惰性对象。

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

1
看起来很吸引人)。据我了解,如果我将使用@Fetch(FetchMode.SUBSELECT),那么我只能调用Hibernate.initialize一次来提取所有集合。我对吗?
VB_

4
以及在检索MyEntity的集合时如何管理?
Alexis Dufrenoy 2014年

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; } 迭代列表,否则将被忽略。
2016年

如果在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();
    }
}

我在迭代中使用session.refresh加载lazyCollections。每次我为一个实体运行程序时,都会在调用session.refresh之后加载LazyInitializationException和其他集合。这怎么可能发生
saba safavi

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。
Alexis Dufrenoy 2014年


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查询实现目标,并避免使用笛卡尔积。


嗨,弗拉德,Hibernate#initialize(entity.getSubSet())如果getSubSet返回的话我会打电话吗 Collections.unmodifyableSet(this.subSet)?我尝试了,但没有。底层集合是“ PersistentSet”。与通话相同的故事#size()
Vadim Kirilchuk

但是,也许问题是,我后来调用包含和我平等使用直接字段访问,而不是干将..
瓦迪姆Kirilchuk

如果您按照我的回答中提供的步骤进行操作,它将起作用。
Vlad Mihalcea

2

它可能并不是最佳实践的地方,但是我通常会SIZE像您建议的那样,在集合上调用a来将子对象加载到同一事务中。它很干净,不受子元素结构任何更改的影响,并且产生的SQL开销很低。


0

尝试使用Gson库将对象转换为Json

servlet的示例:

  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值时,这意味着(在数据存储区方面)这些实体之间没有任何关系,这是不正确的。因此,抛出异常是一种最佳实践,您必须处理该问题而不是休眠。

因此,如上所述,我建议:

  1. 在修改所需对象或使用无状态会话进行查询之前,请先分离该对象
  2. 将惰性字段处理为所需的值(零,空等)

同样,如其他答案中所述,有很多方法(急切地获取,加入等)或库和方法来执行此操作,但是您必须在处理问题和解决问题之前对发生的事情建立看法。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.