远程案例中的延迟/急切加载策略(JPA)

Posted

技术标签:

【中文标题】远程案例中的延迟/急切加载策略(JPA)【英文标题】:Lazy/Eager loading strategies in remoting cases (JPA) 【发布时间】:2010-12-19 05:05:56 【问题描述】:

我遇到了 LazyLoading 异常,就像大多数尝试使用 ORM 进行远程处理的人一样。 在大多数情况下,切换到 Eager fetch 可以解决问题(延迟加载/非原子查询/线程安全/n+1 问题......)。但是,如果您正在处理一个非常大的对象图,那么急切获取也有缺点。

在大多数用例中不需要加载整个对象图。加载更多数据然后需要(或从数据库加载它们并提取所需的子集)感觉不好。

那么有哪些替代方法可以解决此类问题(在运行时)? 我看过:

将数据访问依赖注入到域对象中,让对象决定是加载惰性还是急切:感觉不好!领域层应该独立于任何服务。域注入也是一项昂贵的操作。该域应该是数据访问无知的,并且应该在有或没有数据访问的情况下使用。 除了需要更多数据的用例之外,获取所有懒惰的东西:似乎性能更好,但这种方式会强制许多客户端=>服务器/数据库往返。惰性字段的初始化也会遭受痛苦(使用 JPA 尝试过)。这种方式感觉不一般,并且受到上述相同的惰性限制。 在 Lazy 类中封装持久性:更复杂,没有与 ORM 互操作的最佳实践。臃肿的服务层(这么多“手写”代码感觉很糟糕)。 对每个用例使用完整的预测:我们最终会使用 SQL 并放弃 ORM 的好处。 DTO / 虚拟代理层会增加复杂性并使代码更难维护(虫洞反模式 >> 膨胀)。

我想了很多关于另一种方式。也许通用投影白/黑名单是一种解决方案。

想法(黑名单):定义一个类名列表,其中包含一个获取操作的边界。如果某个属性匹配并且它是惰性的,则删除惰性 (CGLIB) 代理并用 null 填充该值。否则,简单地阻止获取(并将值保留为空)。所以我们可以在我们的 DAO 中设定明确的界限。

示例:ProductDao.findByName("Soap",Boundaries.BLACKLIST,"Category, Discount") 最后两个参数也可以绑定到 Boundaries 对象中。

想法(白名单):与黑名单类似,但您必须声明应在白名单中加载的属性。

您如何看待这样的解决方案? (可能的问题、限制、优势……) 我应该如何用java写这个?也许通过 AOP 来匹配 DAO 方法(因为我可以在那里修改 cglib 代理行为)?

【问题讨论】:

你有什么样的架构?例如,您使用 GWT 吗? JAX-WS Webservices via reference implementation (Metro) 【参考方案1】:

    您可以摆脱所有集合并改用NamedQueries。我们在一个项目(EJB + Swing)中使用了这种方法,它工作得很好——因此您可以确定要获取的确切数据。 NamedQueries 是普通查询,可以将它们想象成 PreparedStatement-s。这个想法不是用查询创建/检索/更新/删除单个对象。这个想法是您通过查询获取您的 Collections。例如,不是映射 @ManyToMany 列表,而是定义一个 NamedQuery 来获取该列表。因此,您可以单独获取集合数据,并且仅在需要时获取,而不是自动获取。

    对传输的对象使用自定义代理(使用 CGLIB) - 每当引用集合时(通过其 getter),尝试检索,并捕获任何 LazyInitializationException 并调用服务器层以获取请求的数据.

    和上一个一样,但是只代理集合,就像 Hibernate 在需要延迟初始化时代理它们一样。

    另外,看看Value List Handler 模式 - 可能有用。

(如果适合您的情况,您也可以将hibernate.max_fetch_depth(如果使用Hibernate)与上述组合使用。)

【讨论】:

命名查询如果应用于每个域类,最终会导致 CRUD 操作的代码重复。我正在使用由 spring 连接和创建的通用 JPA DAO。避免hibernate依赖也很重要。通过 AOP 的装饰器/代理似乎不错。但是您的方法没有与数据层(域/服务组合|域/数据访问组合)分离,因为域获取器应该进行服务器调用。我尽量避免像我的问题中描述的那样注入模型(调用逻辑等)。 嗯,Hibernate 例如使用相同的想法来在会话仍然存在时获取惰性集合 - 它创建包含逻辑的集合的代理。所以做这件事没什么大不了的。 NamedQueries 不是 CURD 操作的重复,而是代替 @*ToMany 注释(和相关的东西)——不过有点冗长。 P.S.为什么避免 Hibernate 依赖很重要?如果您使用 Hibernate 作为持久性提供程序,那么在 JPA 没有所需选项的情况下,您最终将使用它的注解。 :) 我们经常使用 EclipseLink。 JPA 满足了我们所有的需求,我们不想使用 Hibernate 注释。为什么 NamedQueries 不是 CRUD 重复的?参见:bit.ly/7WEdaIProfessor.findByPrimaryKey 与 GenericDao.read(PK) 相同! NamedQueries 与一般 JPA 查询有何不同?对于不受查询影响的引用,是否还有惰性代理? 好的。 :) 只是要补充一点,我们项目中的情况是相同的(除了它是 RMI 而不是 JAX-WS),并且因为我们使用命名查询,所以不会发生 LazyInitializationException - 实体中没有集合。 【参考方案2】:

如今(2013 年),如果您使用 GraniteDS 远程服务,则可以保持延迟加载。

这应该通过不初始化惰性关系并为客户端保持惰性状态来正确序列化您的 JPA 或 Hibernate 实体。如果您在客户端访问这些关系,它将在后台透明地获取它们。

此外,GraniteDS 似乎能够进行反向延迟加载,这意味着当您将修改的对象发送回服务器时,它不会发送未更改的实体,从而使必要的服务器通信非常高效。

我(还)不是 GraniteDS 专家,但它似乎能够与 JEE6 和 Spring 服务层集成,并与所有最重要的 JPA 提供者一起工作。

当然,您需要将基于 GraniteDS 的远程处理隐藏在服务接口后面,以最大限度地提高透明行为,但如果客户端也使用 Spring,这很容易做到(因此您可以根据环境的需要注入服务)。

【讨论】:

【参考方案3】:

虽然这需要一些工作并且对于 JAX-WS/JAXB 需要这些库的最新版本,但这是一个非常优雅的解决方案:创建一个可以测试对象/集合是否已初始化的编组器。

如此处所述: https://forum.hibernate.org/viewtopic.php?f=1&t=998896

【讨论】:

以上是关于远程案例中的延迟/急切加载策略(JPA)的主要内容,如果未能解决你的问题,请参考以下文章

LINQ中的First()会导致急切或延迟加载吗?

延迟加载孩子,里面有急切的集合

JPA 急切未加入

何时在休眠中使用延迟加载/急切加载?

EF急切加载和延迟加载的区别?

我可以同时使用急切和延迟加载吗?