原文:JPA implementation patterns: Lazy loading
作者:Vincent Partington
出处:http://blog.xebia.com/2009/04/27/jpa-implementation-patterns-lazy-loading/
在之前三篇关于JPA实施系列的博客中,我涵盖了保存实体、检索实体和删除实体等基本操作,在本篇博客中我会接着沿用不同的视角来探讨如何延迟加载实体这一主题,以及这一做法会如何影响你的应用。
任何使用了Hibernate一段时间的人,都有可能已经见过了一两个LazyInitializationException异常的出现,通常会尾随着“failed to lazily initialize a collection of role: com.xebia.jpaip.order.Order.orderLines, no session or session was closed”或者“could not initialize proxy - no Session”这一类的信息,即使这些信息可能会使Hibernate的新用户感到困惑,但它们还是比OpenJPA在这些情况下(至少是在使用运行时字节码增强的时候)抛出NullPointerExceptions异常要好得多。
既然JPA允许只要一访问哪怕仅是一个实体,就可以在无需加载整个数据库的情况下,为数据库建立起完整的关系模型,那么要充分发挥JPA的潜力就必须要理解延迟加载是如何工作的。
因为这一原因,所以说JPA 1.0规范没有深入地讨论这一主题而仅仅是用大致同于以下的几句话来进行描述是很令人遗憾的:
即时策略(EAGER strategy)是持久性提供程序(persistence provider)运行时方面的一个需求,即数据必须被及时抓取(eagerly fetched),而对于持久性提供程序运行时来说,延迟策略(LAZY strategy)则是一个提示(hint),示意数据在首次被访问时,其应该被延迟加载。这样的实现是允许的,即允许及时加载已经指定了延迟策略提示的数据。
截至撰写本文时止,JPA 2.0规范的拟议最后草案并未在这一部分中添加任何内容,现在我们能做的就是阅读JPA提供程序(JPA provider)的文档并做一些实验。
延迟加载何时会发生
@Basic、@OneToMany、@ManyToOne、@OneToOne、@ManyToMany等所有的注解都有一个被称为fetch的可选参数,如果该参数被设置为FetchType.LAZY的话,那么对于JPA提供程序来说,它会被解释成一个提示,示意可以延迟该域的加载直至其第一次被访问:
在@Basic注解情况下是属性的值
在@ManyToOne或者@OneToOne注解情况下是引用,或者
在@OneToMany或者@ManyToMany注解情况下是集合
在缺省情况下,即时加载属性的值而延迟加载集合,如果你之前用过纯Hibernate的话,那么以下情况是与你的预期相反的,即在缺省情况下,引用是被即时加载的。
在讨论如何使用延迟加载之前,让我们先来看看JPA提供程序有可能会如何来实现延迟加载。
构建时字节码编入、运行时字节码编入和运行时代理
为了使得延迟加载有效,JPA提供程序需要变变魔术,把一些并未在某个地方存在的对象显现出来,使它们显得就像在那里似的,JPA提供程序可以通过一些不同的方法来达到这一目的,其中最常用的方法是:
构建时字节码编入(Build-time bytecode instrumentation)—实体类在被编译之后及打包运行前就被编入。这一做法的一个缺点是其需要修改构建过程以及(因此)不能总是保持与IDE的兼容。被编入的类与其非编入版本之间可能会是二进制不兼容的,这可能会导致Java序列化问题以及其他类似问题,不过我还没有获悉有人提到过这方面的问题。
运行时字节码编入(Run-time bytecode instrumentation)—实体类还可以在运行时而不是构建时被编入,这就需要使用JDK 1.5或以上版本的-javaagent选项来安装Java代理,运行在JDK 1.6或更高版本之下时则是使用类的重转化(class retransformation),如果正在使用更旧版本的JDK的话,或者需要用到一些专有的方法。因此,虽然这种方法不需要修改构建过程,但却是与使用的JDK密切相关的。
运行时代理(Run-time proxies)—在这种情况下,类不需要被编入,不过JPA提供程序返回的对象是实际实体的代理,这些代理可以是动态代理类、由CGLIB创建的代理或者是代理集合类。这种方法虽然要求的设置最少,但却是可供JPA实现者使用的方法中最缺少透明度的一个,因此需要完全了解他们。
Hibernate的基于运行时代理的延迟加载
虽然Hibernate支持通过构建时的字节码编入来启用个别属性的延迟加载,但是大多数的Hibernate用户都会使用运行时代理,这是是默认的方法,并且大多数情况下都非常有效,所以,让我们来探讨一下Hibernate的运行时代理。
Hibernate创建两种类型的代理:
1. 在通过延迟的多对一或者一对一关联来延迟加载实体,或者通过调用EntityManager.getReference来延迟加载实体时,Hibernate使用CGLIB来创建实体类的一个子类,该子类作为到真正实体的代理。代理中的任一方法首次被调用时,从数据库中加载实体,并且把方法调用传递给被加载的实体。我的同事Maarten Winkels去年曾在他的博客上讲述了这些Hibernate代理存在的隐患。
2. 在通过一对多或者多对多关联来延迟加载实体集合时,Hibernate返回诸如PersistentSet或者PersistentMap一类的实现了PersistentCollection接口的类的一个实例,集合第一次被访问的时候,它的成员会被加载,成员实体被当作平常的类来加载,因此之前提到的Hibernate代理的隐患与这部分无关。
为了感受一下这里发生的事情,你可能希望在调试器中跟进一些简单的JPA代码来查看Hibernate创建的对象,如果这一机制涉及很多的话,这种做法会增进你理解。J
OpenJPA的运行时代码编入
OpenJPA提供了许多增强方法(enhancement method),因为文档中就是这样称呼它的,其中我发现运行时的字节码编入是最容易设置的。
通过调试器的步进,你能够看到OpenJPA并未创建代理,作为代替,一些额外的域出现在每一个实体类中,它们有着类似pcStateManager或者pcDetachedState这样的名字。更重要的是,你可以看到被延迟加载的实体的域都被设置为0或者null,这样的话它的内容只有在其方法被调用的时候才加载,更确切地说,被配置为延迟加载的属性只有在其getter方法被调用时才加载。
了解这一点是非常重要的,即直接访问延迟加载实体的域(或者是代表延迟加载属性的域)并不会触发对这一实体(或者域)的加载,此外,当会话(session)不再是可用的时,OpenJPA不会像Hibernate那样抛出异常,而只是让值处于非初始化状态,这在以后会引发我之前提过的NullPointException异常。
OpenJPA与Hibernate相比较
你可能会注意到这两种方法之间的首要区别是被代理/编入的对象:
OpenJPA对所有实体进行了编入,这意味着它能够检测到你何时访问正在使用的实体的一个延迟的引用或者集合,这样它就会返回实际的实体或者实际实体的集合。你只有在使用EntityManager.getReference延迟加载实体时,或者已把属性配置成延迟加载时,才会得到(部分)为空的实体。
就延迟引用(或者是已使用EntityManager.getReference来延迟加载的实体)这一情况来说,Hibernate使用CGLIB来代理延迟对象本身,这会带来之前提到过的代理隐患。在使用延迟集合的时候,Hibernate的处理则像OpenJPA一样是透明的。最后一点是,Hibernate并不支持使用代理的延迟加载属性。
如果把OpenJPA的编入和Hibernate的运行时代理相比较一下你就会发现,OpenJPA所采用的方法更加的透明化,遗憾的是,这一优势却因为OpenJPA较少强健的错误处理而有所缩小。
模式
既然现在已经知道了如何配置延迟加载以及它的工作方式,那么我们如何才能正确地使用它呢?
第一步是检查所有的关联,查看哪些应该被延迟加载而哪些应该被即时加载。我的经验法则是开始先把所有的*对一关联都保留为即时的(这是缺省情况),他们在通常情况下的查询数目的合计无论如何都很难达到一个很大的数量,如果数量真的很大的话,我可以修改这些关联。然后我会检查所有的*对多关联,任何它们中的关联如果其指向的实体总是被访问因而总是被加载的话,我就会把他们配置成即时加载的,有时候我会使用Hibernate特有的@CollectionOfElements注解来映射这种“值类型”的实体。
第二步是最重要的,为了防止所有的LazyInitializationException或者NullPointerException异常,你需要确保所有对领域对象的访问在同一个事务内部发生。当领域对象在事务完成之后被访问时,持久性上下文不能再被访问以加载持久对象,因此会导致这些问题的出现。有两种方法可用来化解这一矛盾:
1. 最纯粹的方式是在服务之前放置一个服务门面(Service Facade)(如果你喜欢的话远程门面(Remote Facade)也行),并通过传输对象(即Data Transfer Object,又名DTO)只与服务门面的客户通信。门面负责把所有适当的值从领域对象拷贝到数据传输对象中,包括引用和集合的深度拷贝。应用的事务范围应包括服务门面这样的工作模式,即给门面设定@Transactional注解,或者为它指定一个适当的@TransactionAttribute。
2. 如果你正在使用MVC框架来编写模型2(Model 2)web应用的话,另一种被广泛使用的办法是在视图模式中使用打开的EntityManager,在Spring中可配置一个Servlet过滤器或者Web MVC拦截器,这样当请求进来的时候,过滤器或者拦截器会打开实体管理器,并保持管理器的打开状态直到请求的处理完成。这意味着在控制器(controller)和视图(view)(JSP或者其他方式)中活动的是相同的事务。或者一些纯粹控会争辩说,这种做法会导致表现层依赖于领域对象,但对于简单的web应用来说,这是一种令人感兴趣的方式。
第三步是启用JPA提供程序的SQL日志功能来检查应用的一些用例。这对于了解实体被访问时哪些查询被执行很有启发作用。SQL日志还能够提供性能优化的输入,因此你能够重新审视在步骤一中所作的决定并对数据库进行调整。最终延迟加载都会与性能有关,所以不要忘记了本步骤。
我希望本篇博客给你带来了关于延迟加载如何工作以及如何在应用中使用它的一些见解,在下一篇博客中我会深入探讨DTO这一主题和服务门面模式。不过在结束之前,我要感谢来J-Spring 2009讨论这一主题的每个人,我非常开心!看起来确实有很多人都想知道如何有效地使用JPA,因为我收到了很多问题。遗憾的是这些问题用光了我的所有时间,下次我会更加注意举着时间卡的女孩,并带上我的手表,再次感谢你们的参与!
附:有人知道hibernate.org发生了什么事吗?一个多星期以来网站一直在显示消息说他们为了维护所以关闭网站。
分享到:
相关推荐
本资料主要探讨的是JPA中的一对多关系以及延迟加载机制,这两部分是JPA使用中的关键概念。 **一对多关系** 在关系数据库中,一对多关系是最常见的一种关联类型。例如,一个部门可以有多名员工,而每个员工只能属于...
本教程“11_传智播客JPA详解_JPA中的一对多延迟加载与关系维护”聚焦于JPA在处理一对多关系时的延迟加载机制以及如何有效地维护这些关系。 一、JPA一对多关系 在数据库中,一对多关系意味着一个实体可以与多个其他...
**JPA(Java Persistence API)**是Java平台上的一个标准,用于管理关系数据库中的数据,它简化了数据库操作,提供了一种面向对象的方式来处理数据库事务。JPA通过ORM(Object-Relational Mapping)映射机制将Java...
在处理关联关系时,Spring Data JPA提供了懒加载和急加载(Eager vs Lazy Fetching)机制,通过配置实体类的属性映射,可以在需要时按需加载关联的数据,避免了N+1查询问题。同时,它还支持级联操作(Cascade ...
Spring Data JPA 在 Spring Boot 项目中的集成与应用 Spring Data JPA 是一款基于 Java 持久层 API(JPA)的框架,提供了一个通用的数据访问层解决方案,能够简化 Java 应用程序中的数据访问工作。Spring Boot 作为...
一共有三个分卷。全部下载才能解压。 这本书不错,值得一看。
例如,级联操作允许一次操作就能影响到多个相关对象,延迟加载则可以提高性能,避免不必要的数据加载。 Hibernate还支持缓存机制,包括第一级缓存(Session级别的缓存)和第二级缓存(SessionFactory级别的缓存)。...
在大型项目中,由于数据量庞大,为了提高性能和减少数据库的负载,Hibernate引入了“延迟加载”(Lazy Loading)机制。标题和描述提到的“hibernate延迟加载解决”主要涉及如何理解和解决与之相关的常见问题。 **1....
后来,尝试使用 Hibernate 或 JPA 等框架搜索具有相同值的列失败,因为搜索词没有填充到n ,导致搜索词与列中存储的值不匹配。 例如,考虑以下表定义: CREATE TABLE CITY ( ... NAME CHAR(40) NOT NULL ...);如果...
1:JPA: : 2:对象数据库: ://www.objectdb.com/ 3:JPA 性能基准: ://www.jpab.org/All/All/All.html 支持功能 支持所有蓝图功能, 除外 支撑 支持 支持 Java 5、6 或 7 支持 JPA 你需要哪一个取决于你想用...
JTechLog JPA 延迟加载 该程序是JTechLog( )博客上“JPA延迟加载”文章的示例程序。 提供了EclipseLink和Hibernate持久性提供程序的JPA延迟加载。 具有JPA持久层和Spring MVC Web层的双层应用程序。 可以用Maven...
这主要是因为这两个工具能够极大地提升开发效率,特别是对于使用Spring Data JPA进行数据库操作的开发者。 首先,让我们详细了解一下如何使用IDEA来提升Spring Data JPA的开发效率。IntelliJ IDEA是一款强大的Java...
此外,合理设置实体的装载模式,如选择性地加载关联,有助于提升性能。 总结,JPA为Java开发者提供了简洁且强大的ORM框架,简化了数据库操作。理解并熟练掌握JPA的加载、更新和删除机制,有助于提高代码的可读性和...
在这个"06_传智播客JPA详解_大数据字段映射与字段延迟加载"的自学视频中,我们将会深入探讨两个关键概念:大数据字段的映射和字段的延迟加载。 首先,让我们来理解大数据字段映射。在数据库中,有些字段可能包含...
【标题】"notes_JPA_JSF:使用JSF和JPA实施来重建Notes项目"涉及的是在Java开发环境中,利用JavaServer Faces (JSF) 和 Java Persistence API (JPA) 技术重构建一个名为Notes的项目。JSF是Java EE平台上的一个用户...
Spring Data Jpa常用功能演示配套说明请查看:项目简介本项目采用当前最新版本的2.1.4.RELEASE做基础架构支撑,请参考本项目建议有一定的基础及经验。教程主要针对中文用户,如果您英文良好,建议直接阅读官网帮助...
春天数据jpa额外使用jpa的spring数据更舒适我爱spring-data-jpa,她放开我的双手,粗鲁的方法很无聊! 然而,尽管她为我们提供了规范解决方案,但她在动态本机查询上并不完美,而且她的返回类型必须是一个实体,但是...
在Java的持久化框架Hibernate中,懒加载(Lazy Loading)是一种重要的优化策略,它的核心思想是“延迟加载”或“按需加载”。默认情况下,当一个实体被加载时,并不会立即加载其关联的对象或集合,而是在真正需要...
本文是介绍Spring-data-jpa的PPT的学习笔记,整理了Spring Data JPA相关知识配置和实践源码. 本文介绍知识点有: JPA与Spring的相关配置 JPA 方法名常用查询 JPA 使用@Query注解实现JPQL和本地自定义查询 JPA API 条件...