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
}

真是个问题:

The problem is that I can't pull lazy collection after session has been closed. But I also can't not close a session in proceed method.

什么样的解决方案(粗糙的解决方案) :

A)在关闭会话之前,强制休眠以拉取延迟集合

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

....

B)也许更优雅的方法是使用 @Fetch(FetchMode.SUBSELECT)注释

问题:

什么是最佳实践/常用方法/更优雅的方法? 意思是将我的对象转换为 JSON。

160496 次浏览

这可能不是最佳实践,但是我通常在集合上调用 SIZE来加载同一事务中的子事务,就像您建议的那样。它是干净的,不受子元素结构中任何变化的影响,并且产生的 SQL 开销很低。

Use Hibernate.initialize() within @Transactional to initialize lazy objects.

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

现在,在事务之外,您可以获得惰性对象。

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

在会话结束前放置 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();

您可以在同一个事务中遍历 Hibernate 对象的 Getters,以确保使用以下 一般助手类急切地获取所有惰性子对象:

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

在 Hibernate 4.1.6中引入了一个新特性来处理这些懒惰的关联问题。当在 hibernate.properties 或 hibernate.cfg.xml 中启用 hibernate.able _ LazyInitializationException 属性时,将不再有 LazyInitializationException 异常。

详情请参阅: https://stackoverflow.com/a/11913404/286588

这不是最好的解决办法,但是我有一个办法:

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

尝试使用 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);

当必须获取多个集合时,您需要:

  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 查询来实现你的目标,避免笛卡儿积。

if you using jpa repository, 将 properties. put (“ hibernate.able _ Lazy _ load _ no _ trans”,true) ; 设置为 jpaPropertymap

可以使用实体的 @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-Hibernate 中的惰性集合存在一些误解 为什么尝试读取一个延迟集合会抛出异常,而不仅仅是为了转换或进一步的用例而返回 NULL?.

这是因为数据库中的 Null 字段,特别是联接列中的 Null 字段具有意义,而不是像编程语言那样简单地不呈现状态。 当您试图将一个惰性集合解释为 Null 值时,这意味着(在 Datastore 端)这些实体之间没有关系,而且这不是真的。所以抛出异常是某种最佳实践,你必须处理它,而不是 Hibernate。

因此,如上所述,我建议:

  1. 在修改对象或使用无状态会话进行查询之前分离所需的对象
  2. 将延迟字段操作为所需的值(0、 null 等)

正如其他答案中所描述的那样,有很多方法(渴望获取、加入等)或者库和方法可以做到这一点,但是在处理和解决问题之前,你必须建立起对正在发生的事情的看法。