通过无知解决 LazyInitializationException

Solving LazyInitializationException via ignorance

这里有无数的问题,如何通过急切获取,保持事务打开,打开另一个,OpenEntityManagerInViewFilter等等来解决“无法初始化代理”的问题。

但是是否可以简单地告诉 Hibernate 忽略该问题并假装集合为空?就我而言,之前不获取它只是意味着我不在乎。

这其实是一个XY问题,下面的Y:

我类喜欢

class Detail {
    @ManyToOne(optional=false) Master master;
    ...
}

class Master {
    @OneToMany(mappedBy="master") List<Detail> details;
    ...
}

并且想要服务于两种请求:一种是 returning 一个 master 及其所有 details,另一种是 returning 一个 masters 没有 details。结果被 Gson 转换为 JSON。

我试过 session.clearsession.evict(master),但它们没有触及用于代替 details 的代理。有效的是

 master.setDetails(nullOrSomeCollection)

这感觉很老套。我更喜欢“无知”,因为它通常适用于不知道代理的哪些部分。

写一个 Gson TypeAdapter 忽略 AbstractPersistentCollectioninitialized=false 的实例可能是一种方法,但这将取决于 org.hibernate.collection.internal,这肯定不是好事。在 TypeAdapter 中捕获异常听起来并没有好多少。

一些答案后更新

我的目标不是“加载数据而不是异常”,而是“如何获取 null 而不是异常

一个有效的观点是忘记获取和 returning 错误的数据比异常更糟糕。但有一个简单的解决方法:

这样,结果永远不会被错误解读。如果我忘记取东西,响应将包含无效的 null

您可以尝试如下解决方案。

正在创建一个名为 LazyLoader

的界面
@FunctionalInterface // Java 8
public interface LazyLoader<T> {
    void load(T t);
}

在您的服务中

public class Service {
    List<Master> getWithDetails(LazyLoader<Master> loader) {
        // Code to get masterList from session
        for(Master master:masterList) {
            loader.load(master);
        }        
    }
}

并像下面这样调用此服务

Service.getWithDetails(new LazyLoader<Master>() {
    public void load(Master master) {
        for(Detail detail:master.getDetails()) {
            detail.getId(); // This will load detail
        }
    }
});

并且在 Java 8 中,您可以使用 Lambda,因为它是单一抽象方法 (SAM)。

Service.getWithDetails((master) -> {
    for(Detail detail:master.getDetails()) {
        detail.getId(); // This will load detail
    }
});

您可以将上述解决方案与 session.clearsession.evict(master)

一起使用

您可以利用 Hibernate.isInitialized,它是 Hibernate public API.

的一部分

因此,在 TypeAdapter 中您可以添加如下内容:

if ((value instanceof Collection) && !Hibernate.isInitialized(value)) {
   result = new ArrayList();
}

但是,在我看来,您的方法通常不是正确的方法。

"In my case, not fetching it before simply means that I don't care."

或者这意味着你忘记获取它,现在你返回了错误的数据(比得到异常更糟糕;服务的消费者认为集合是空的,但它不是)。

我不想提出 "better" 解决方案(这不是问题的主题,每种方法都有其自身的优势),但我在大多数用例中解决此类问题的方式(并且它是常用的方法之一)是使用 DTO:简单地定义一个代表服务响应的 DTO,将其填充到事务上下文中(那里没有 LazyInitializationExceptions)并将其提供给将对其进行转换的框架到服务响应(json、xml 等)。

我过去曾提出过类似的问题 (why dependent collection isn't evicted when parent entity is),并且得到了一个答案,您可以针对您的情况进行尝试。

这个问题的解决方案是使用查询而不是关联(一对多或多对多)。甚至 Hibernate 的一位原作者也说 Collections are a feature 而不是最终目标。

在您的情况下,您可以更灵活地删除集合映射,并在数据访问层中需要时简单地获取关联关系。

您可以为每个实体创建一个 Java 代理,这样每个方法都被 try/catch 块包围,当 [=15= 时 returns null ] 被抓住了。

为此,您的所有实体都需要实现一个接口,并且您需要在整个程序中引用此接口(而不是实体 class)。

如果您不能(或只是不想)使用接口,那么您可以尝试使用 javassist or cglib, or even manually, as explained in this article 构建动态代理。

如果您使用常见的 Java 代理,这里有一个草图:

public static <T> T ignoringLazyInitialization(
    final Object entity, 
    final Class<T> entityInterface) {

    return (T) Proxy.newProxyInstance(
        entityInterface.getClassLoader(),
        new Class[] { entityInterface },
        new InvocationHandler() {

            @Override
            public Object invoke(
                Object proxy, 
                Method method, 
                Object[] args) 
                throws Throwable {

                try {
                    return method.invoke(entity, args);
                } catch (InvocationTargetException e) {
                    Throwable cause = e.getTargetException();
                    if (cause instanceof LazyInitializationException) {
                        return null;
                    }
                    throw cause;
                }
            }
        });
}

所以,如果你有一个实体 A 如下:

public interface A {

    // getters & setters and other methods DEFINITIONS

}

及其实施:

public class AImpl implements A {

    // getters & setters and other methods IMPLEMENTATIONS

}

然后,假设您有对实体 class 的引用(由 Hibernate 返回),您可以按如下方式创建代理:

AImpl entityAImpl = ...; // some query, load, etc

A entityA = ignoringLazyInitialization(entityAImpl, A.class);

注意 1: 您还需要代理 Hibernate 返回的集合(留作 reader 的练习);)

注意 2: 理想情况下,您应该在 DAO 或某种类型的外观中执行所有这些代理操作,以便所有内容对实体的用户都是透明的

注意 3: 这绝不是最优的,因为它会为每次访问未初始化字段创建堆栈跟踪

注意 4: 这可行,但增加了复杂性;考虑是否真的有必要。