从零开始 Spring Boot 67:JPA 中的惰性元素

从零开始 Spring Boot 67:JPA 中的惰性元素

spring boot

图源:简书 (jianshu.com)

惰性加载带来的问题

在实体类之间建立关系时,可以选择“惰性加载”,比如:

@Entity
public class Student {// ...@OneToMany(mappedBy = "student",cascade = CascadeType.ALL,fetch = FetchType.LAZY)@Builder.Defaultprivate List<Email> emails = new ArrayList<>();// ...
}@Entity
@Table(uniqueConstraints = @UniqueConstraint(columnNames = {"account", "domain"}))
public class Email {// ...@Setter@ManyToOne@JoinColumn(name = "student_id")private Student student;
}

@OneToManyfetch属性可以选择FetchType.LAZYFetchType.EAGER,前者是惰性加载,后者是急切加载。如果是惰性加载,JPA 从数据库中加载Student实例时,不会立即加载关联的emails属性。而是直到真正使用emails属性时才会再查询并加载数据。

这样虽然可以避免不必要的关联查询,但同时也会产生另一个问题——获取到的关联数据是否有效?

为了避免这个问题,JPA 要求使用惰性加载时,相关数据的延迟加载与之前的实体数据加载在同一个事务下,否则就无法保证数据一致性。如果不这样做,就会产生异常。

比如下面这个示例,为Student创建服务层,并提供一个根据学生姓名查询学生实体实例的方法:

@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Service
public class StudentService {@Autowiredprivate StudentRepository studentRepository;public Student findStudentByName(String name) {return studentRepository.findOne(Example.of(Student.builder().name(name).build())).get();}// ...
}

调用示例:

var student = studentService.findStudentByName("icexmoon");
Assertions.assertEquals("icexmoon", student.getName());
Assertions.assertThrows(LazyInitializationException.class, () -> {student.getEmails().size();
});

因为调用示例中并没有使用事务,所以调用student.getEmails().size()时会产生一个LazyInitializationException异常。

要解决这个问题,最简单的是将关联集合的加载方式修改为FetchType.EAGER,但有时候我们可能不希望这么做,此时有一些其他方式可供我们选择。

使用 JPQL

可以直接使用 JPQL 进行关联查询,同时加载关联的数据:

@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Service
public class StudentService {@Autowiredprivate SessionFactory sessionFactory;public Student findStudentByName2(String name) {var session = sessionFactory.openSession();Student student = session.createQuery("select s from Student s join fetch s.emails where s.name=:name", Student.class).setParameter("name", name).getSingleResult();session.close();return student;}
}

SQL 日志:

select s1_0.id,e1_0.student_id,e1_0.id,e1_0.account,e1_0.domain,s1_0.name from student s1_0 join email e1_0 on s1_0.id=e1_0.student_id where s1_0.name=?
binding parameter [1] as [VARCHAR] - [icexmoon]

使用实体图

可以用实体图(Entity Graph)指定在 JPA 查询时需要加载的属性:

@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Service
public class StudentService {@Autowiredprivate SessionFactory sessionFactory;// ...public Student findStudentByName3(String name) {long id = this.findStudentByName("icexmoon").getId();var session = sessionFactory.openSession();RootGraph<Student> entityGraph = session.createEntityGraph(Student.class);entityGraph.addAttributeNodes("name", "emails");Map<String, Object> properties = new HashMap<>();properties.put("javax.persistence.fetchgraph", entityGraph);return session.find(Student.class, id, properties);}
}

可以阅读这篇文章了解实体图的更多信息。

使用事务

正如前面所说的,如果在延迟加载数据时能够在同一个事务中,也就不会出现类似的问题:

@Test
@Transactional
void test4(){var student = studentService.findStudentByName("icexmoon");Assertions.assertEquals("icexmoon", student.getName());Assertions.assertEquals(2, student.getEmails().size());
}

The End,谢谢阅读。

参考资料

  • Working with Lazy Element Collections in JPA | Baeldung

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/14467.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软件设计模式与体系结构-设计模式-行为型软件设计模式-状态模式

五、状态模式 概念 与策略模式类似&#xff0c;状态模式将不同状态下的行为封装在不同的类中&#xff0c;每个类代表一个状态 状态模式的组成 Context&#xff1a;定义了与客户程序的接口&#xff0c;它保持了一个concreteState的代表现在状态的实例State&#xff1a;定义了…

数据增强之裁剪、翻转与旋转

文章和代码已经归档至【Github仓库&#xff1a;https://github.com/timerring/dive-into-AI 】或者公众号【AIShareLab】回复 pytorch教程 也可获取。 文章目录 数据增强 Data Augmentation裁剪Croptransforms.CenterCroptransforms.RandomCroptransforms.RandomResizedCroptra…

数据库的备份与恢复(超详细讲解)

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于MySQL数据库的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.数据库的备份与恢复是什么 二. …

stable diffusion 调试天坑 (setup.py)

第一次下载V1的stable diffusion (https://github.com/CompVis/stable-diffusion) 到本地调试&#xff0c;根据其要求创建了虚拟环境&#xff0c;自动运行了setup.py文件&#xff0c;长这样 from setuptools import setup, find_packagessetup(namelatent-diffusion,version0.…

Android Studio实现内容丰富的安卓博客发布平台

如需源码可以添加q-------3290510686&#xff0c;也有演示视频演示具体功能&#xff0c;源码不免费&#xff0c;尊重创作&#xff0c;尊重劳动。 项目编号078 1.开发环境 android stuido jdk1.8 eclipse mysql tomcat 2.功能介绍 安卓端&#xff1a; 1.注册登录 2.查看博客列表…

RabbitMQ系列(8)--实现RabbitMQ队列持久化及消息持久化

概念&#xff1a;在上一章文章中我们演示了消费者宕机的情况下消息没有被消费成功后会重新入队&#xff0c;然后再被消费&#xff0c;但如何保障RabbitMQ服务停掉的情况下&#xff0c;生产者发过来的消息不会丢失&#xff0c;这时候我们为了消息不会丢失就需要将队列和消息都标…

MAYA传送带上放石头(新旧粒子系统)

播放试试 使用老的粒子系统 particleShape1.shuliangrand(0,5); particleShape1.daxiao<<rand(0.2,0.5),rand(0.2,0.5),rand(0.2,0.5)>>; particleShape1.xuanzhuan<<rand(360),rand(360),rand(360)>>; 使用新的粒子系统 粒子向后滑落 新粒子系统能进行…

33.RocketMQ之Broker启动源码

highlight: arduino-light Broker启动流程:BrokerStartup#main java public static void main(String[] args) { //手动指定了 nameServer start(createBrokerController(args)); } java public static BrokerController start(BrokerController controller)…

计算机网络 - http协议 与 https协议(2)

前言 本篇介绍了构造http请求的的五种方式&#xff0c;简单的使用postman构造http请求&#xff0c;进一步了解https, 学习https的加密过程&#xff0c;了解对称密钥与非对称密钥对于加密是如何进行的&#xff0c;如有错误&#xff0c;请在评论区指正&#xff0c;让我们一起交流…

数据结构算法题——链表

leetcode-2.两数之和 leetcode-2.两数之和 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数…

Linux调优–I/O 调度器

Linux 的 I/O 调度器是一个以块式 I/O 访问存储卷的进程&#xff0c;有时也叫磁盘调度器。Linux I/O 调度器的工作机制是控制块设备的请求队列&#xff1a;确定队列中哪些 I/O 的优先级更高以及何时下发 I/O 到块设备&#xff0c;以此来减少磁盘寻道时间&#xff0c;从而提高系…

【PCIE】hot-reset和link disable

Hot reset 规则 如果上游伪端口&#xff08;Pseudo Port&#xff09;的任何一个通道连续接收到两个带有热复位位设置为1b、禁用链路位和回环位设置为0b的TS1有序集合&#xff0c;并且两个伪端口上的任何一个通道&#xff08;接收到TS1有序集合&#xff09;要么收到EIOS&#xf…