1、概览 在某些情况下,当使用 Spring Data JPA Repository 保存实体时,可能会在日志中遇到额外的 SELECT。这可能会因大量额外调用而导致性能问题。
本文将带你了解如何在 Spring Data JPA 中执行 INSERT 时跳过 SELECT,以提高性能。
2、设置 在深入 Spring Data JPA 并对其进行测试之前,先要做一些准备工作。
2.1、依赖 为了创建测试 Repository,需要使用 Spring Data JPA 依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> 使用 H2 数据库作为测试数据库:
<dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> 使用 Spring Context 进行集成测试。添加 spring-boot-starter-test 依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> 2.2、配置 本例中使用的 JPA 配置如下:
spring.jpa.hibernate.dialect=org.hibernate.dialect.H2Dialect spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.hibernate.show_sql=true spring.jpa.hibernate.hbm2ddl.auto=create-drop 如上,让 Hibernate 生成 schema,并将所有 SQL 查询记录到日志中。
3、导致 SELECT 查询的原因 首先,创建一个 Task 实体:
1、概览 数据库视图(Database View)是关系型数据库系统中的一种类似表的结构,其中的数据源来自一个或多个连接在一起的表。
Spring Data Repository 通常用于数据库表,但也可以有效地应用于数据库视图。本文将带你了解如何在 Spring Data JPA 中使用 Repository 从数据库视图检索数据。
2、数据库表设置 本文使用 H2 数据库系统进行数据定义,并使用 SHOP 和 SHOP_TRANSACTION 这两个示例表演示数据库视图概念。
SHOP 表存储商店信息:
CREATE TABLE SHOP ( shop_id int AUTO_INCREMENT, shop_location varchar(100) NOT NULL UNIQUE, PRIMARY KEY(shop_id) ); SHOP_TRANSACTION 表存储与商店相关的交易记录,并通过 shop_id 对 SHOP 表进行引用:
CREATE TABLE SHOP_TRANSACTION ( transaction_id bigint AUTO_INCREMENT, transaction_date date NOT NULL, shop_id int NOT NULL, amount decimal(8,2) NOT NULL, PRIMARY KEY(transaction_id), FOREIGN KEY(shop_id) REFERENCES SHOP(shop_id) ); 在实体-关系(ER)模型中,可以将其说明为 “一对多” 的关系,即一个商店可以有多笔交易。但是,每笔交易只与一家商店相关联。可以用 ER 图直观地表示这一点:
1、简介 本文将带你了解 Spring Data JPA 中 Query Hint (查询提示)的功能、基本原理以及如何有效地应用它们。
这些提示有助于优化数据库查询,并通过影响优化器的决策过程来改善应用性能。
2、理解 Query Hint Spring Data JPA 中的 Query Hint 是一种强大的工具,可帮助优化数据库查询并提高应用性能。与直接控制执行不同,Query Hint 会影响优化器(Optimizer)的决策过程。
在 Spring Data JPA 中,可以在 org.hibernate.annotations 包中找到这些 Hint,同时还有与 Hibernate 相关的各种注解和类。需要注意的是,这些 Hint 的解释和执行通常取决于底层持久化层实现(Persistence Provider),如 Hibernate 或 EclipseLink,因此它们是特定于厂商的。
3、使用 Query Hint Spring Data JPA 提供了多种利用 Query Hint 优化数据库查询的方法。
3.1、基于注解的配置 Spring Data JPA 提供了一种使用注解为 JPA 查询添加 Query Hint 的简便方法。通过 @QueryHints 注解,可以指定用于生成 SQL 查询的 JPA @QueryHint 数组。
示例如下,设置了 JDBC fetch size Hint,以限制结果返回的大小:
1、概览 数据库函数是数据库管理系统的重要组成部分,可将逻辑和执行封装在数据库中。它们有助于高效的数据处理和操作。
本文将带你了解在 Spring Boot 应用中使用 Spring Data JPA 调用自定义数据库函数的各种方法。
2、项目设置 为了简单,本文使用 H2 数据库。
首先在 pom.xml 中加入 Spring Boot Data JPA 和 H2 依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>3.2.2</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>2.2.224</version> </dependency> 3、数据库函数 数据库函数是通过在数据库中执行一组 SQL 语句或操作来执行特定任务的数据库对象。当逻辑是数据密集型的时候,这可以提高性能。虽然数据库函数和存储过程的操作类似,但它们也有不同之处。
3.1、函数与存储过程 虽然不同的数据库系统之间可能会有具体的差异,但它们之间的主要差异可归纳在下表中:
特点 数据库函数 存储过程 执行 可在查询中调用 必须明确调用 返回值 始终返回一个值 可返回无值、单值或多值 参数 仅支持输入参数 支持输入和输出参数 调用 无法使用函数调用存储过程 可使用存储过程调用函数 用途 通常执行计算或数据转换 通常用于复杂的业务逻辑 3.2、H2 函数 为了说明如何从 JPA 调用数据库函数,我们将创建一个在 H2 数据库中的数据库函数来说明如何从 JPA 中调用它。
H2 数据库函数只是一个嵌入的 Java 源代码,它将被编译和执行:
1、简介 Spring Data JPA 提供了查询推导功能(派生查询),只需遵循方法名称约定就能自动推导出查询。
本文将带你了解如何使用查询推到功能,通过一列或多列查找实体。
2、设置 定义一个 Account 实体,其中包含与用户账户相关的属性:
@Entity @Table(name = "ACCOUNTS") public class Account { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "accounts_seq") @SequenceGenerator(name = "accounts_seq", sequenceName = "accounts_seq", allocationSize = 1) @Column(name = "user_id") private int userId; private String username; private String password; private String email; private Timestamp createdOn; private Timestamp lastLogin; @OneToOne @JoinColumn(name = "permissions_id") private Permission permission; // getter / setter } 为了演示,在 ACCOUNTS 表中添加一些示例数据:
1、概览 Spring JPA 和 Hibernate 为无缝数据库通信提供了强大的工具。不过,由于客户端将更多控制权委托给了框架,因此生成的查询可能远非最佳。
本文将带你了解使用 Spring JPA 和 Hibernate 时常见的 N+1 问题,以及可能导致该问题的不同情况。
2、社交媒体平台 为了更好地将问题形象化,我们需要概述实体之间的关系。以一个简单的社交网络平台为例。这里只有用户(User)和帖子(Post):
我们在图表中使用了 Iterable,并且我们将为每个示例提供具体的实现:List 或 Set。
为了测试请求的数量,我们将使用一个专用库,而不是检查日志。不过,我们会参考日志,以便更好地了解请求的结构。
如果在每个示例中没有明确指定关系的获取类型(Fetch Type),则默认情况下假定为默认值。所有的一对一关系都使用急切加载(Eager Fetch),而一对多关系则使用延迟加载(Lazy)。此外,代码示例中使用了 Lombok 来减少代码中的冗余。
3、N+1 问题 N+1 问题指的是,对于单个请求(例如检索用户),会对每个用户发出额外请求,以获取其信息。虽然这个问题通常与懒加载有关,但并非总是如此。
任何类型的关系都可能出现这种问题。不过,它通常出现在多对多或一对多关系中。
3.1、延迟加载 首先,来看看懒加载是如何导致 N+1 问题的,示例如下:
@Entity public class User { @Id private Long id; private String username; private String email; @OneToMany(cascade = CascadeType.ALL, mappedBy = "author") protected List<Post> posts; // 构造函数/getter/setter } User 与 Post 之间是一对多的关系。这意味着每个 User 都有多个 Post。我们没有明确确定字段的 Fetch 策略。策略是从注解中推断出来的。如前所述,@OneToMany 默认采用 Lazy Fetch 策略:
1、概览 Spring JPA 和 Hibernate 为不同据库通信提供了强大的工具。然而,随着开发者将更多的控制权(包括查询生成)委托给框架,结果可能与我们的预期相去甚远。
开发者通常会对在多对多关系中使用列表(List)还是集合(Set)产生困惑。而且,Hibernate 对其 bag、list 和 set 使用了类似的名称,但它们之间有稍微不同的含义,这进一步增加了混淆的可能性。
在大多数情况下,Set 更适用于一对多或多对多关系。不过,它们对性能有特殊的影响,需要注意。
本文将带你了解 JPA 实体关系中 List 和 Set 的区别,以及各自的优缺点。
2、测试 这里使用专门的测试库来测试请求数。检查日志不是一个好的解决方案,因为它不是自动化的,可能只适用于简单的示例。当请求产生数十或数百个查询时,使用日志是不够高效的。
首先,需要 io.hypersistence。注意,artifact ID 中的数字是 Hibernate 版本:
<dependency> <groupId>io.hypersistence</groupId> <artifactId>hypersistence-utils-hibernate-63</artifactId> <version>3.7.0</version> </dependency> 此外,还使用 util 库进行日志分析:
<dependency> <groupId>com.vladmihalcea</groupId> <artifactId>db-util</artifactId> <version>1.0.7</version> </dependency> 我们应该使用所提供的 Util 来 封装数据源,使其正常工作。这可以通过 BeanPostProcessor 来做实现:
@Component public class DataSourceWrapper implements BeanPostProcessor { public Object postProcessBeforeInitialization(Object bean, String beanName) { return bean; } public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof DataSource originalDataSource) { ChainListener listener = new ChainListener(); SLF4JQueryLoggingListener loggingListener = new SLF4JQueryLoggingListener(); loggingListener.
1、概览 本文将带你全面了解如何使用 Spring Data JPA 在 PostgreSQL JSONB 列中存储、检索 JSON 数据。
2、VARCHAR 映射 本节将介绍如何使用 AttributeConverter 将 VARCHAR 类型的 JSON 值转换为自定义 Java POJO。
其目的是方便 Java 数据类型中的实体属性值与数据库列中的相应值之间的转换。
2.1、Maven 依赖 要创建 AttributeConverter,必须在 pom.xml 中加入 Spring Data JPA 依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>2.7.18</version> </dependency> 2.2、数据表定义 数据库表定义如下:
CREATE TABLE student ( student_id VARCHAR(8) PRIMARY KEY, admit_year VARCHAR(4), address VARCHAR(500) ); student 表有三个字段,其中我们希望 address 列能存储具有以下结构的 JSON 值:
{ "postCode": "TW9 2SF", "city": "London" } 2.3、Entity 类 创建一个相应的 POJO 类,用 Java 表示 address 数据:
1、概览 JpaRepository 供了 CRUD 操作的基本方法。其中 getReferenceById(ID) 和 findById(ID) 是经常引起混淆的方法。这些方法是 getOne(ID)、findOne(ID) 和 getById(ID) 的新 API 名称。
本文将带你了解这些方法之间的区别,以及各自的适用场景。
2、findById() 这个方法按照其名称所示,根据给定的 ID 在 Repository 中查找实体:
@Override Optional<T> findById(ID id); 该方法返回一个 Optional。因此,如果传递了一个不存在的 ID,返回的 Optional 对象将为 empty。
该方法使用了急切加载功能,因此只要调用该方法,就会向数据库发送请求。
执行如下示例:
public User findUser(long id) { log.info("Before requesting a user in a findUser method"); Optional<User> optionalUser = repository.findById(id); log.info("After requesting a user in a findUser method"); User user = optionalUser.orElse(null); log.info("After unwrapping an optional in a findUser method"); return user; } 输出的日志如下:
1、概览 Spring JPA 为与数据库交互提供了非常灵活方便的 API。而且,还可以对其进行定制,以返回其他数据结构类型的返回值。
使用 Map 作为 JPA Repository 方法的返回类型有助于在服务和数据库之间创建更直接的交互。本文将带你了解如何在 Spring Data JPA Repository 接口的方法中返回 Map。
2、手动实现 当框架不提供某些功能时,最明显的解决方法就是自己实现。
2.1、List 可以把返回的 List 映射为 Map。通过 Stream API 只用一行代码就能实现:
default Map<Long, User> findAllAsMapUsingCollection() { return findAll().stream() .collect(Collectors.toMap(User::getId, Function.identity())); } 2.2、Stream Repository 接口方法可以直接返回 Stream:
@Query("select u from User u") Stream<User> findAllAsStream(); 之后,实现一个自定义方法,将结果映射到需要的数据结构中:
@Transactional default Map<Long, User> findAllAsMapUsingStream() { return findAllAsStream() .collect(Collectors.toMap(User::getId, Function.identity())); } 返回 Stream 的 Repository 方法应在事务中调用。所以,直接在 default 方法中添加 @Transactional 注解。
2.3、Streamable 这与之前的方法类似。唯一的变化是返回 Streamable。先定义一个返回 Streamable 的方法: