我有:
@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
}
什么问题:
问题是会话关闭后我无法拉动惰性集合。但我也无法不使用 proceed 方法关闭会话。
多么好的解决方案(粗略的解决方案):
a) 在会话关闭之前,强制休眠拉取惰性集合
entity.getAddresses().size();
entity.getPersons().size();
....
b)也许更优雅的方法是使用
@Fetch(FetchMode.SUBSELECT)
注释
问题:
最佳实践/常见方法/更优雅的方法是什么?意味着将我的对象转换为 JSON。
在
Hibernate.initialize()
中使用 @Transactional
来初始化惰性对象。
start Transaction
Hibernate.initialize(entity.getAddresses());
Hibernate.initialize(entity.getPersons());
end Transaction
现在在事务之外,您可以获取惰性对象。
entity.getAddresses().size();
entity.getPersons().size();
您可以在同一事务中遍历 Hibernate 对象的 Getters,以确保使用以下 generic 帮助器类急切地获取所有惰性子对象:
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;
}
}
不是最好的解决方案,但这是我得到的:
1) 使用此注解注释要初始化的 getter:
@Retention(RetentionPolicy.RUNTIME)
public @interface Lazy {
}
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();
}
}
放置 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.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 4.1.6 引入了一个新功能来处理这些惰性关联问题。当您在 hibernate.properties 或 hibernate.cfg.xml 中启用 hibernate.enable_lazy_load_no_trans 属性时,您将不再有 LazyInitializationException 。
这可能不是最佳实践,但我通常在集合上调用
SIZE
以在同一事务中加载子级,就像您所建议的那样。它很干净,不受子元素结构中任何变化的影响,并且生成的 SQL 开销较低。
如果您使用 jpa 存储库, 设置properties.put("hibernate.enable_lazy_load_no_trans",true);到 jpaPropertymap
JPA-Hibernate 中对惰性集合存在一些误解。首先我们要明确的是 为什么尝试读取惰性集合会引发异常,而不仅仅是简单地返回 NULL 进行转换或进一步使用?.
这是因为数据库中的空字段(尤其是连接列中的空字段)有意义,而不仅仅是像编程语言那样的未呈现状态。 当您尝试将惰性集合解释为 Null 值时,这意味着(在数据存储端)这些实体之间没有关系,这不是真的。所以抛出异常是某种最佳实践,你必须处理它而不是 Hibernate。
所以如上所述,我建议:
也如其他答案中所述,有很多方法(热切获取、加入等)或库和方法可以做到这一点,但在处理问题和解决问题之前,您必须建立对正在发生的情况的看法。
您可以使用实体的
@NamedEntityGraph
注释来创建可加载查询,以设置要在查询中加载的集合。
这种方法的主要优点是,只有当您选择使用此图时,hibernate 才会进行一次查询来检索实体及其集合,如下所示:
实体配置
@Entity
@NamedEntityGraph(name = "graph.myEntity.addressesAndPersons",
attributeNodes = {
@NamedAttributeNode(value = "addresses"),
@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 来做到这一点的一种方法是添加
@NamedEntityGraph(includeAllAttributes = true)
您的实体将提供一个与您的实体名称相同的命名实体图(因为未提供名称)。
然后假设您已经从现有方法中获得了
entity
,那么可以将以下代码应用于它
// this is needed to ensure the existing
// object that you have retrieved is disconnected
// from the entity manager, otherwise the find
// method will return the same object.
entityManager.detach(entity);
// Locate the entity graph based on the class name of the entity.
EntityGraph<?> entityGraph =
entityManager.getEntityGraph(
entityManager
.getMetamodel()
.entity(entity.getClass()).getName());
var fullyLoadedEntity = entityManager
.find(
entity.getClass(),
entity.getId(),
Map.of(
// SpecHints is hibernate specific the value is
// "jakarta.persistence.loadgraph"
SpecHints.HINT_SPEC_LOAD_GRAPH, entityGraph));
尝试使用
Gson
库将对象转换为Json
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);