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
Когда нужно получить несколько коллекций, вам необходимо:
- ПРИСОЕДИНЯЙТЕСЬ к одной коллекции
- Используйте
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);
}