Hibernate: лучшая практика, чтобы вытащить все ленивые коллекции

Что у меня:

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

Ответ 1

Используйте Hibernate.initialize() внутри @Transactional для инициализации ленивых объектов.

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

Теперь на стороне транзакции вы можете получить ленивые объекты.

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

Ответ 2

Вы можете перемещаться по объекту Getters of Hibernate в той же транзакции, чтобы гарантировать, что все ленивые дочерние объекты с нетерпением ожидают следующего общего класса-помощника:

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

Ответ 3

Не лучшее решение, но вот что я получил:

1) Аннотировать getter, который вы хотите инициализировать с помощью этой аннотации:

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

}

2) Используйте этот метод (можно поместить в общий класс или изменить T с классом Object) на объект после его чтения из базы данных:

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

Ответ 4

Поместите Utils.objectToJson(сущность); вызов перед закрытием сессии.

Или вы можете попробовать установить режим выборки и играть с кодом, подобным этому

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

Ответ 5

С Hibernate 4.1.6 вводится новая функция для решения этих проблем с ленивыми ассоциациями. Когда вы включаете свойство hibernate.enable_lazy_load_no_trans в файле hibernate.properties или в файле hibernate.cfg.xml, у вас больше не будет исключения LazyInitializationException.

Подробнее см. fooobar.com/questions/97734/...

Ответ 6

Скорее всего, он не подходит ни к какой лучшей практике, но обычно я называю SIZE в коллекции, чтобы загрузить детей в ту же транзакцию, как вы уже сказали. Он чист, невосприимчив к любым изменениям в структуре дочерних элементов и дает SQL с низкими накладными расходами.

Ответ 7

Попробуйте использовать библиотеку 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);

Ответ 8

Когда нужно получить несколько коллекций, вам необходимо:

  1. ПРИСОЕДИНЯЙТЕСЬ к одной коллекции
  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);

Таким образом, вы можете достичь своей цели с помощью двух SQL-запросов и избежать декартового произведения.

Ответ 9

если вы используете репозиторий jpa, задайте properties.put("hibernate.enable_lazy_load_no_trans", true); в jpaPropertymap

Ответ 10

Вы можете использовать аннотацию @NamedEntityGraph для вашей сущности, чтобы создать загружаемый запрос, который задает, какие коллекции вы хотите загрузить по вашему запросу.

Основным преимуществом этого выбора является то, что hibernate делает один запрос для извлечения сущности и ее коллекций, и только тогда, когда вы решите использовать этот график, например:

Конфигурация объекта

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

Usage

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