Java Hibernate:拉取所有惰性集合的最佳实践

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19928568/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 21:36:22  来源:igfitidea点击:

Hibernate: best practice to pull all lazy collections

javahibernatelazy-loading

提问by VB_

What I have:

我拥有的:

@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
}

What a problem:

有什么问题:

The problem is that I can't pull lazy collection after session has been closed. But I also can't not close a session in proceedmethod.

问题是我无法在会话关闭后拉延迟收集。但是我也不能在继续方法中关闭会话。

What a solution (coarse solution):

什么解决方案(粗解):

a) Before session is closed, force hibernate to pull lazy collections

a) 在 session 关闭之前,强制休眠拉取惰性集合

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

....

....

b) Maybe more ellegant way is to use @Fetch(FetchMode.SUBSELECT)annotation

b) 也许更优雅的方式是使用@Fetch(FetchMode.SUBSELECT)注解

Question:

题:

What is a best practice/common way/more ellegant way to do it? Means convert my object to JSON.

什么是最佳实践/常用方法/更优雅的方法?意味着将我的对象转换为 JSON。

采纳答案by Prabhakaran Ramaswamy

Use Hibernate.initialize()within @Transactionalto initialize lazy objects.

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

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

Now out side of the Transaction you are able to get lazy objects.

现在在事务之外,您可以获得惰性对象。

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

回答by davek

It's probably not anywhere approaching a best practice, but I usually call a SIZEon the collection to load the children in the same transaction, like you have suggested. It's clean, immune to any changes in the structure of the child elements, and yields SQL with low overhead.

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

回答by StanislavL

Place the Utils.objectToJson(entity); call before session closing.

放置 Utils.objectToJson(entity); 在会话结束前调用。

Or you can try to set fetch mode and play with code like this

或者您可以尝试设置获取模式并使用这样的代码

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();

回答by Florian Sager

You can traverse over the Getters of the Hibernate object in the same transaction to assure all lazy child objects are fetched eagerly with the following generichelper class:

您可以在同一事务中遍历 Hibernate 对象的 Getter,以确保使用以下通用帮助程序类急切地获取所有惰性子对象:

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

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;
}
}

回答by Farm

With Hibernate 4.1.6 a new feature is introduced to handle those lazy association problems. When you enable hibernate.enable_lazy_load_no_trans property in hibernate.properties or in hibernate.cfg.xml, you will have no LazyInitializationException any more.

Hibernate 4.1.6 引入了一个新特性来处理那些惰性关联问题。当您在 hibernate.properties 或 hibernate.cfg.xml 中启用 hibernate.enable_lazy_load_no_trans 属性时,您将不再有 LazyInitializationException。

For More refer : https://stackoverflow.com/a/11913404/286588

更多请参考:https: //stackoverflow.com/a/11913404/286588

回答by Damian

Not the best solution, but here is what I got:

不是最好的解决方案,但这是我得到的:

1) Annotate getter you want to initialize with this annotation:

1) 使用此注释注释要初始化的 getter:

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

}

2) Use this method (can be put in a generic class, or you can change T with Object class) on a object after you read it from database:

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

回答by Mohamed Nagy

Try use Gsonlibrary to convert objects to Json

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

Example with servlets :

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);

回答by Vlad Mihalcea

When having to fetch multiple collections, you need to:

当必须获取多个集合时,您需要:

  1. JOIN FETCH one collection
  2. Use the Hibernate.initializefor the remaining collections.
  1. 加入获取一个集合
  2. Hibernate.initialize用于剩余的集合。

So, in your case, you need a first JPQL query like this one:

因此,在您的情况下,您需要一个像这样的第一个 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);

This way, you can achieve your goal with 2 SQL queries and avoid a Cartesian Product.

这样,您可以通过 2 个 SQL 查询实现您的目标并避免笛卡尔积。

回答by userSait

if you using jpa repository, set properties.put("hibernate.enable_lazy_load_no_trans",true); to jpaPropertymap

如果您使用 jpa 存储库,请设置 properties.put("hibernate.enable_lazy_load_no_trans",true); 到 jpaPropertymap

回答by gutors

You can use the @NamedEntityGraphannotation to your entity to create a loadable query that set which collections you want to load on your query.

您可以使用@NamedEntityGraph实体的注释来创建可加载的查询,以设置要在查询中加载的集合。

The main advantage of this choice is that hibernate makes one single query to retrieve the entity and its collections and only when you choose to use this graph, like this:

这种选择的主要优点是 hibernate 进行一个单一的查询来检索实体及其集合,并且仅当您选择使用此图时,如下所示:

Entity configuration

实体配置

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

Usage

用法

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);
    }