Что я имею:
@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.
Вы можете перемещаться по геттерам объекта Hibernate в той же транзакции, чтобы гарантировать, что все ленивые дочерние объекты будут быстро извлечены с помощью следующего универсального вспомогательного класса:
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; } }
источник
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; }
итерация списков в противном случае игнорируется.Не лучшее решение, но вот что у меня получилось:
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(); } }
источник
Поместите 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();
источник
В Hibernate 4.1.6 представлена новая функция для решения этих проблем с ленивыми ассоциациями. Когда вы включаете свойство hibernate.enable_lazy_load_no_trans в hibernate.properties или в hibernate.cfg.xml, у вас больше не будет LazyInitializationException.
Для получения дополнительной информации см .: https://stackoverflow.com/a/11913404/286588
источник
Когда вам нужно получить несколько коллекций, вам необходимо:
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()
Вероятно, это нигде не подходит к лучшей практике, но я обычно вызываю
SIZE
коллекцию, чтобы загрузить дочерние элементы в одной транзакции, как вы предложили. Он чист, невосприимчив к любым изменениям в структуре дочерних элементов и дает SQL с низкими накладными расходами.источник
Попробуйте использовать
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);
источник
если вы используете репозиторий jpa, установите properties.put ("hibernate.enable_lazy_load_no_trans", true); в jpaPropertymap
источник
Вы можете использовать
@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); }
источник
Есть какое-то недоразумение насчет ленивых коллекций в JPA-Hibernate. Прежде всего, давайте проясним, почему попытка прочитать ленивую коллекцию вызывает исключения, а не просто возвращает NULL для преобразования или дальнейшего использования?.
Это потому, что пустые поля в базах данных, особенно в соединенных столбцах, имеют значение, а не просто непредставленное состояние, как языки программирования. когда вы пытаетесь интерпретировать ленивую коллекцию в значение Null, это означает (на стороне хранилища данных), что между этими объектами нет отношений, и это не так. поэтому выброс исключения - это своего рода передовой опыт, и вам придется иметь дело с этим, а не с Hibernate.
Итак, как упоминалось выше, я рекомендую:
также, как описано в других ответах, существует множество подходов (активная выборка, присоединение и т. д.) или библиотек и методов для этого, но вы должны настроить свое представление о том, что происходит, прежде чем решать проблему и решать ее.
источник