延迟加载

本章节将介绍 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延迟原理

JPA 使用动态代理机制实现延迟加载,覆写该对象中的所有的 getter 方法,在 getter 方法中执行查询当前对象的 SQL;

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号