本章节将介绍 JPA 中的延迟加载概念,以及怎样使用延迟加载。
延迟加载(lazy load)也称为懒加载,OpenJPA 关联关系对象默认的加载方式,延迟加载机制是为了避免一些无谓的性能开销而提出来的,所谓延迟加载就是当在真正需要数据的时候,才真正执行数据加载操作(即向数据库发送 SQL 语句)。
在 OpenJPA 中,使用 fetch 属性来指定 FetchType 类型。FetchType 取值如下:
LAZY:延迟加载数据
EAGER:立即加载数据
下面是 @OneToMany 注解的源码,代码如下:
public @interface OneToMany { //... FetchType fetch() default LAZY; //... }
默认使用 FetchType.LAZY,即开启延迟数据加载。
JPA 延迟加载的有效期是在 Session 打开的情况下;当 Session 关闭后,会抛出异常。当调用 find() 方法加载对象时,返回代理对象,等到真正用到对象的内容时才发出 SQL 语句。
JPA中,根据主键查询数据可以使用下面两个方法完成:
<T> T find(Class<T> type, Object oid) 根据主键查询指定类型的数据。
<T> T getReference(Class<T> type, Object oid) 根据主键查询指定类型的数据,它和 find() 方法的区别是,在真实使用该对象的时候才会发送查询的 SQL 语句。
下面将使用老师(Teacher)和学生(Student)一对多关系来演示数据延迟加载。
Teacher.java(教师)
@Data @Entity @Table public class Teacher { @Id @GeneratedValue private int id; @Column private String name; @OneToMany(cascade = CascadeType.ALL) private List<Student> studentList; }
Student.java(学生)
@Data @Entity @Table public class Student { @Id @GeneratedValue private int id; @Column private String name; @Column private String sex; @Column private float salary; }
Demo.java(客户端)
public class Demo { /** 持久化单元名称 */ private static final String NAME = "demo1"; public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory(NAME); EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); // 初始化数据 int teacherId = 351; Teacher teacher = em.find(Teacher.class, teacherId); System.out.println("id = " + teacher.getId()); System.out.println("name = " + teacher.getName()); em.getTransaction().commit(); em.close(); emf.close(); System.out.println("finished."); } }
执行上面代码,输出的 SQL 语句如下:
2227 demo1 INFO [main] openjpa.Enhance - Creating subclass and redefining methods for "[class com.huangx.openjpa.lazy.demo1.Teacher, class com.huangx.openjpa.lazy.demo1.Student]". This means that your application will be less efficient than it would if you ran the OpenJPA enhancer. 3101 demo1 TRACE [main] openjpa.jdbc.SQL - <t 681008168, conn 960733886> executing prepstmnt 195381554 SELECT t0.name FROM Teacher t0 WHERE t0.id = ? [params=?] 3106 demo1 TRACE [main] openjpa.jdbc.SQL - <t 681008168, conn 960733886> [1 ms] spent
上面结果中,只发送了一条查询 teacher 的 SQL 语句。
如果我们调用 teacher 的 getStudentList() 方法,JPA 会发起查询学生 Student 的 SQL 语句。代码如下:
Teacher teacher = em.find(Teacher.class, 351); System.out.println("id = " + teacher.getId()); System.out.println("name = " + teacher.getName()); // 这会导致 JPA 发送一条查询 SQL 语句 teacher.getStudentList();
执行上面代码,输出的 SQL 语句如下:
2011 demo1 INFO [main] openjpa.Enhance - Creating subclass and redefining methods for "[class com.huangx.openjpa.lazy.demo1.Teacher, class com.huangx.openjpa.lazy.demo1.Student]". This means that your application will be less efficient than it would if you ran the OpenJPA enhancer. 2755 demo1 TRACE [main] openjpa.jdbc.SQL - <t 681008168, conn 960733886> executing prepstmnt 195381554 SELECT t0.name FROM Teacher t0 WHERE t0.id = ? [params=?] 2757 demo1 TRACE [main] openjpa.jdbc.SQL - <t 681008168, conn 960733886> [1 ms] spent 2813 demo1 TRACE [main] openjpa.jdbc.SQL - <t 681008168, conn 960733886> executing prepstmnt 330382173 SELECT t1.id, t1.name, t1.salary, t1.sex FROM Teacher_Student t0 INNER JOIN Student t1 ON t0.STUDENTLIST_ID = t1.id WHERE t0.TEACHER_ID = ? [params=?] 2817 demo1 TRACE [main] openjpa.jdbc.SQL - <t 681008168, conn 960733886> [4 ms] spent
根据执行的打印结果可以看到,是我们在真正使用该对象的时候才会执行查询的 SQL。
如果我们将 @OneToMany 注解的 fetch 改为 FetchType.EAGER,如下:
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) private List<Student> studentList;
再次执行客户端代码,输出的 SQL 语句如下:
SELECT t0.name, t1.TEACHER_ID, t2.id, t2.name, t2.salary, t2.sex FROM Teacher t0 LEFT OUTER JOIN Teacher_Student t1 ON t0.id = t1.TEACHER_ID LEFT OUTER JOIN Student t2 ON t1.STUDENTLIST_ID = t2.id WHERE t0.id = 351 ORDER BY t1.TEACHER_ID ASC;
但是,OpenJPA 执行失败了,抛出的错误信息如下:
3328 demo1 TRACE [main] openjpa.jdbc.SQL - <t 1729779847, conn 1431467659> executing prepstmnt 142247393 SELECT t0.name, t1.TEACHER_ID, t2.id, t2.name, t2.salary, t2.sex FROM Teacher t0 LEFT OUTER JOIN Teacher_Student t1 ON t0.id = t1.TEACHER_ID LEFT OUTER JOIN Student t2 ON t1.STUDENTLIST_ID = t2.id WHERE t0.id = ? ORDER BY t1.TEACHER_ID ASC [params=?] 3344 demo1 TRACE [main] openjpa.jdbc.SQL - <t 1729779847, conn 1431467659> [16 ms] spent Exception in thread "main" <openjpa-2.4.2-r422266:1777108 nonfatal general error> org.apache.openjpa.persistence.PersistenceException: null FailedObject: 351 [org.apache.openjpa.util.IntId] [java.lang.String] at org.apache.openjpa.kernel.BrokerImpl.find(BrokerImpl.java:1029) at org.apache.openjpa.kernel.BrokerImpl.find(BrokerImpl.java:923) at org.apache.openjpa.kernel.DelegatingBroker.find(DelegatingBroker.java:230) at org.apache.openjpa.persistence.EntityManagerImpl.find(EntityManagerImpl.java:488) at com.huangx.openjpa.lazy.demo1.Demo.main(Demo.java:18) Caused by: java.lang.NullPointerException at org.apache.openjpa.jdbc.kernel.JDBCStoreManager.setInverseRelation(JDBCStoreManager.java:452)
暂时还没有解决方案,具体分析信息参考“”。
注意:
find() 方法查询到的结果。如果查询到了对应的数据,返回查询到的结果即可;反之,返回null;所以可以使用 ifnull 判断是否有数据;
getReference() 方法查询到的结果,无论是否查询到了数据,结果都不会是null;所以不能使用 ifnull 判断是否有对应的数据;如果在表中没有对应的数据,抛出异常 javax.persistence.EntityNotFoundException: Unable to find com.hxstrive.openjpa.User with id 2
JPA 使用动态代理机制实现延迟加载,覆写该对象中的所有的 getter 方法,在 getter 方法中执行查询当前对象的 SQL;