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
}

Яка проблема:

Проблема в тому, що я не можу витягнути ліниві колекції після закриття сеансу. Але я також не можу не закрити сесію методом продовження .

Який розчин (грубий розчин):

а) Перед тим, як сеанс закрито, змусьте сплячий режим витягнути ліниві колекції

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

....

б) Можливо, більш витончений спосіб - використовувати @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_

4
І як ви керуєте, коли отримуєте колекцію MyEntity?
Alexis Dufrenoy

1
Якщо ви викликаєте будь-який метод на зразок "size ()" для колекції в транзакції, він також ініціалізує його, тому ваш приклад після вашої ініціалізації не найкращий. Це означає, що "Hibernate.initialize (...)" семантично краще, ніж collection.size (), тому ви маєте найкращу пораду.
Трістан

7

Ви можете здійснити перехід через Getters об’єкта 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;
}
}

Дякую за цю відповідь. Я знаю, що минув якийсь час, але я намагався це вирішити, і це йшло повільно, поки я не прочитав тут ваш код. Я також додав 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; } Ітераційні списки інакше ігноруються.
Чіп

Що робити, якщо SecurityException викинуто на o.getClass (). GetMethods () ;?
Олексій Кислицин

6

Не найкраще рішення, але ось що я отримав:

1) Анотуйте геттер, який ви хочете ініціалізувати, за допомогою цієї анотації:

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

Я використовую session.refresh в ітерації для завантаження lazyCollections. і кожен раз, коли я запускаю програму лише для однієї з моїх сутностей, я отримую колекцію LazyInitializationException та інші, завантажену після виклику session.refresh. Як це могло статися
saba safavi

5

Розмістіть 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();

FetchMode.EAGER застарілий. Javadoc рекомендує використовувати FetchMode.JOIN, зараз.
Alexis Dufrenoy

4

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

Докладніше див .: https://stackoverflow.com/a/11913404/286588


3
Це насправді анти-шаблон. Для отримання додаткової інформації: vladmihalcea.com/…
Ph03n1x

3

Щоб отримати кілька колекцій, потрібно:

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

Таким чином, ви можете досягти своєї мети за допомогою 2 SQL-запитів та уникнути декартового продукту.


Привіт Владе, чи працює це, якщо я зателефоную, Hibernate#initialize(entity.getSubSet())якщо getSubSet повернеться Collections.unmodifyableSet(this.subSet). Я спробував, але ні. Основна колекція - 'PersistentSet'. Та сама історія із телефоном#size()
Вадим Кирильчук

Але, можливо, проблема в тому, що я пізніше дзвоню містить і моя рівна використовує прямий доступ до поля, а не геттери ..
Вадим Кирильчук

Це працює, якщо ви дотримуєтесь інструкцій, наведених у моїй відповіді.
Влад Міхалча

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", істина); до jpaPropertymap


0

Ви можете використовувати @NamedEntityGraph анотацію до своєї сутності для створення завантажуваного запиту, який встановлює, які колекції ви хочете завантажити у свій запит.

Головною перевагою цього вибору є те, що сплячий режим робить один запит для отримання сутності та її колекцій, і лише тоді, коли ви вирішите використовувати цей графік, наприклад:

Конфігурація сутності

@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, це означає (на стороні сховища даних), що між цими сутностями немає ніяких відносин, і це не відповідає дійсності. отже, кидання винятків - це якась найкраща практика, і вам доведеться мати справу з цим, а не зі сплячкою.

Отже, як уже згадувалося вище, я рекомендую:

  1. Від'єднайте бажаний об'єкт, перш ніж змінювати його або використовувати сеанс без стану для запитів
  2. Маніпулюйте ледачими полями до бажаних значень (нуль, нуль тощо)

також, як описано в інших відповідях, існує безліч підходів (нетерплячий вибір, приєднання тощо) або бібліотек та методів для цього, але перед тим, як вирішувати проблему та вирішувати її, потрібно сформулювати свій погляд на те, що відбувається.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.