Spring-Data-Jpa

Spring Data JPA Repository 和数据库视图

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 图直观地表示这一点:

Spring Data Jpa 中的 Query Hint

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,以限制结果返回的大小:

在 Spring Boot 中使用 JPA 调用自定义数据库函数

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 源代码,它将被编译和执行:

Spring Data JPA 使用 findby 定义多个条件列

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 表中添加一些示例数据:

Hibernate 和 Spring Data JPA 中的 N+1 问题

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 策略:

JPA @OneToMany 关系中的 List 与 Set

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.

使用 Spring Data JPA 在 PostgreSQL 中存储和检索 JSON 数据,

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 数据:

Spring Data JPA 中的 getReferenceById() 和 findById() 方法

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; } 输出的日志如下:

Spring Data JPA Repository 返回 Map

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 的方法:

Spring Data JPA @Query 注解中的 SpEL 支持

1、概览 SpEL 是 Spring Expression Language(Spring 表达式语言)的缩写,它是一种强大的工具,能显著增强与 Spring 的交互,并为配置、属性设置和查询操作提供额外的抽象。 本文将带你了解如何使用该工具使自定义查询更具动态性,通过 @Query 注解,可以使用 JPQL 或原生 SQL 来定制与数据库的交互。 2、访问参数 首先先看看如何使用 SpEL 处理方法参数。 2.1、通过索引访问 通过索引访问参数并不是最佳选择,因为这可能会给代码带来难以调试的问题。尤其是当参数类型相同时。 同时,这也提供了更大的灵活性,特别是在开发阶段,当参数的名称经常发生变化时。IDE 可能无法正确处理代码和查询的更新。 JDBC 提供了 ? 占位符,可以用它来确定参数在查询中的位置。Spring 支持这一约定,并允许以如下方式来访问参数: @Modifying @Transactional @Query(value = "INSERT INTO articles (id, title, content, language) " + "VALUES (?1, ?2, ?3, ?4)", nativeQuery = true) void saveWithPositionalArguments(Long id, String title, String content, String language); 到目前为止,使用的方法与之前在 JDBC 应用中使用的方法相同。注意,在数据库中进行更改的任何查询都需要 @Modifying 和 @Transactional 注解,INSERT 就是其中之一。所有 INSERT 的示例都将使用原生查询,因为 JPQL 不支持。