1、概览 Spring Data JPA 中的 @DynamicInsert 注解通过在 SQL 语句中只包含非 null 字段来优化插入操作。这一过程加快了结果查询的速度,减少了不必要的数据库交互。
虽然它提高了对具有许多可为空字段的实体的效率,但也引入了一些运行时开销。因此,在只有在排除空列的好处超过性能成本的情况下,有选择地使用它。
2、JPA 中 INSERT 的默认行为 使用 EntityManager 或 Spring Data JPA 的 save() 方法持久化 JPA 实体时,Hibernate 会生成一条 SQL 插入(INSERT)语句。该语句包括每个实体列,即使某些列包含 null 值。因此,在处理包含许多可选字段的大型实体时,插入操作的效率会很低。
先来看一个简单的 Account 实体:
@Entity public class Account { @Id private int id; @Column private String name; @Column private String type; @Column private boolean active; @Column private String description; // Getter / Setter } 为 Account 实体创建 JPA Repository:
1、简介 本文将带你了解如何使用 Spring Data JPA 在 MongoDB 中创建具有多个 Criteria(条件)的查询。
2、项目设置 首先,在 pom.xml 文件中添加 Spring Data MongoDB Starter 依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> <version>3.3.1</version> </dependency> 通过该依赖,我们可以在 Spring Boot 项目中使用 Spring Data MongoDB 的功能。
2.1、定义 MongoDB Document 和 Repository 接下来,定义一个 MongoDB Document(文档),它是一个用 @Document 注解的 Java 类。该类映射到 MongoDB 中的一个 Collection(集合)。
例如,创建一个 Product Document:
@Document(collection = "products") public class Product { @Id private String id; private String name; private String category; private double price; private boolean available; // Getter 和 Setter 方法 } 在 Spring Data MongoDB 中,我们可以创建 Repository 来定义自己的查询方法。通过注入 MongoTemplate,可以对 MongoDB 数据库执行高级操作。该类为执行查询、聚合数据和有效处理 CRUD 操作提供了丰富的方法集:
1、概览 就 ORM 而言,数据库审计指的是跟踪和记录与实体相关的事件,或者简单地说是实体版本管理。受 SQL 触发器的启发,这些事件是对实体的插入、更新和删除操作。数据库审计的好处类似于源代码版本控制。
本文将带你了解在应用中使用审计的三种方法。首先介绍来自于 JPA 标准的审计实现、然后再介绍由 Hibernate 和 Spring Data 分别提供的审计的扩展实现。
下面是本文中使用的相关实体 Bar 和 Foo 示例:
2、JPA 审计 JPA 并没有明确包含审计 API,但我们可以通过使用实体生命周期事件来实现这一功能。
2.1、@PrePersist、@PreUpdate 和 @PreRemove 在 JPA 实体类中,我们可以使用 @PrePersist、@PreUpdate 和 @PreRemove 注解指定一个方法作为回调,以在相应的 DML 操作前执行它。
@Entity public class Bar { @PrePersist public void onPrePersist() { ... } @PreUpdate public void onPreUpdate() { ... } @PreRemove public void onPreRemove() { ... } } 实体内部的回调方法应是非 static 的,返回类型为 void,且不带参数。访问权限任意。
注意,JPA 中的 @Version 注解与本文的主题没有关系,它是一种乐观锁,与审计数据无关。
简介 本文将带你了解在 Spring Data JPA 中使用 Stream(流式)查询的最佳方式。
当需要获取较大的结果集时,使用 Java Stream 的好处是可以逐步迭代查询结果集,避免一次性获取所有数据可能导致的内存溢出异常。
JPA Stream 方法 自 2.2 版起,你可以使用 JPA 的 getResultStream 方法以 Stream 的形式处理结果集。
getResultStream 使用 JDBC ResultSet 对给定查询返回的记录进行流式处理。特别是在处理大结果集的时候,这种方法很有效率。
Spring Data JPA Stream 查询方法 如果要对查询结果集进行流式处理,则需要在 Spring Data JPA 查询方法中返回 Java Stream 类型,如下例所示:
@Repository public interface PostRepository extends BaseJpaRepository<Post, Long> { @Query(""" select p from Post p where date(p.createdOn) >= :sinceDate """ ) @QueryHints( @QueryHint(name = AvailableHints.HINT_FETCH_SIZE, value = "25") ) // 返回 Stream Stream<Post> streamByCreatedOnSince(@Param("sinceDate") LocalDate sinceDate); } 对于 PostgreSQL 和 MySQL,指定 FETCH_SIZE JPA QueryHint 是必要的,它指示 JDBC 驱动每次迭代的时候最多预取 25 条记录。否则,PostgreSQL 和 MySQL JDBC 驱动会在遍历底层 ResultSet 之前预取所有查询结果。
1、简介 本文将带你了解如何使用 Spring Data JPA 检索数据列中的最大值(Max Value)。
2、示例 首先,添加 spring-boot-starter-data-jpa 依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> 然后,定义一个简单的 Employee 实体表示员工:
@Entity public class Employee { @Id @GeneratedValue private Integer id; private String name; private Long salary; // 工资 // 构造函数、Getter、Setter 方法省略 } 接下来,看看有哪些方法可以检索出所有员工中薪水(salary)列的最大值。
3、使用 Repository 的派生查询 Spring Data JPA 提供了一个强大的机制,可以使用 Repository 方法来定义自定义查询。其中之一是派生查询,它允许我们通过声明方法名来实现SQL查询。
创建 Employee 实体类的 Repository 接口:
public interface EmployeeRepository extends JpaRepository<Employee, Integer> { Optional<Employee> findTopByOrderBySalaryDesc(); } 如上,我们实现了一个方法 findTopByOrderBySalaryDesc,该方法使用查询派生机制生成相应的 SQL。根据方法名,它将按照工资(salary)降序对所有员工进行排序,然后返回第一个员工,即工资最高的员工。
该方法会返回一个加载了所有属性的实体。如果我们只想检索一个工资(salary)值,可以使用投影查询:
创建 EmployeeSalary 投影接口:
1、概览 在使用 Spring Data JPA 构建持久层时,经常要处理带有枚举字段的实体。这些枚举字段代表一组固定的常量,例如订单的状态、用户的角色或业务的某个阶段。
本文将带你了解如何使用标准的 JPA 方法和原生查询来查询实体类中声明的枚举字段。
2、应用设置 2.1、数据模型 首先,定义数据模型,包括一个枚举字段。
我们示例中的中心实体是 Article 类,它声明了一个枚举字段 ArticleStage,用于表示文章可能处于的不同阶段:
public enum ArticleStage { TODO, IN_PROGRESS, PUBLISHED; } ArticleStage 枚举包含三个可能的阶段,代表文章从最初创建到最终发布的生命周期。
接下来,创建声明了 ArticleStage 枚举字段的 Article 实体类:
@Entity @Table(name = "articles") public class Article { @Id private UUID id; private String title; private String author; @Enumerated(EnumType.STRING) private ArticleStage stage; // 构造函数/Getter/Setter 方法省略 } 我们将 Article 实体类映射到 articles 数据库表。此外,还使用 @Enumerated 注解指定 stage 字段应作为字符串在数据库中持久化。
2.2、Repository 层 定义好数据模型后,就可以创建一个继承了 JpaRepository 的 Repository 接口,以便与数据库交互:
1、概览 在使用 Spring Data JPA 时,我们经常会利用派生和自定义查询,以我们喜欢的格式返回结果。一个典型的例子就是 DTO 投影,它提供了一种只 SELECT 某些特定列以减少不必要数据开销的好方法。
然而,DTO 投影并不总是那么容易,如果实现不当,可能会导致 ConverterNotFoundException 异常。本文将带你了解 ConverterNotFoundException 异常出现的原因,以及如何在使用 Spring Data JPA 时避免 ConverterNotFoundException 异常。
2、在实践中理解异常 通过一个实际例子来理解异常。
为了简单起见,使用 H2 数据库。首先,在 pom.xml 文件中添加其依赖:
<dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>2.2.224</version> </dependency> 2.1、H2 配置 Spring Boot 提供了对 H2 嵌入式数据库的支持。默认情况下,它会配置应用使用用户名 sa 和空密码连接到 H2。
将数据库连接凭证添加到 application.properties 文件中:
spring.datasource.url=jdbc:h2:mem:mydb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password= 如上就是使用 Spring Boot 设置 H2 配置所需的全部内容。
2.2、Entity 类 们定义一个 JPA 实体类 Employee:
@Entity public class Employee { @Id private int id; @Column private String firstName; @Column private String lastName; @Column private double salary; // Getter/Setter 方法省略 } 如上,员工类(Employee)定义了 id、firstName、lastName 和 salary 属性。
1、概览 本文将带你了解 JPA 如何自动保存复杂的实体模型(即由父实体和子实体元素组成的复杂模型)以及常见的问题。
2、缺失关系注解 我们可能会忽略的第一件事就是添加关系注解。
创建一个子实体:
@Entity public class BidirectionalChild { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; //Get/Set 方法省略 } 创建一个包含 List<BidirectionalChild> 的父实体:
@Entity public class ParentWithoutSpecifiedRelationship { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private List<BidirectionalChild> bidirectionalChildren; //Get/Set 方法省略 } 如上,bidirectionalChildren 字段上没有注解。尝试用这些实体创建一个 EntityManagerFactory:
@Test void givenParentWithMissedAnnotation_whenCreateEntityManagerFactory_thenPersistenceExceptionExceptionThrown() { PersistenceException exception = assertThrows(PersistenceException.class, () -> createEntityManagerFactory("jpa-savechildobjects-parent-without-relationship")); assertThat(exception) .hasMessage("Could not determine recommended JdbcType for Java type 'com.baeldung.BidirectionalChild'"); } 运行测试,出现了异常,无法确定子实体的 JdbcType。单向和双向关系都会出现类似的异常,其根本原因是父实体中缺失 @OneToMany 注解。
1、简介 Sequence (序列)是用于生成唯一 ID 的数字生成器,可避免数据库中出现重复记录。Spring JPA 为大多数情况提供了自动处理序列的方法。不过,在某些特定情况下,我们可能需要在持久化实体之前手动检索下一个序列值。例如,在将订单(Order)详细信息保存到数据库之前,需要生成一个唯一的订单号。
本文将带你了解使用 Spring Data JPA 从数据库序列中获取下一个值的几种方法。
2、设置项目依赖 首先要在 Maven pom.xml 文件中添加 Spring Data JPA 和 PostgreSQL 驱动依赖,并在数据库中创建序列。
2.1、Maven 依赖 首先,在 pom.xml 中添加必要的依赖项:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> 2.2、测试数据 下面是我们在运行测试用例之前用来准备数据库的 SQL 脚本,可以将该脚本保存为 .sql 文件,并将其放在项目的 src/test/resources 目录中:
DROP SEQUENCE IF EXISTS my_sequence_name; CREATE SEQUENCE my_sequence_name START 1; 该命令创建一个从 1 开始的序列,每调用一次 NEXTVAL 就递增一次。
然后,在测试类中使用 @Sql 注解,并将 executionPhase 属性设置为 BEFORE_TEST_METHOD,以便在每个测试方法执行之前将测试数据插入数据库:
@Sql(scripts = "/testsequence.sql", executionPhase = Sql.
1、概览 使用 Spring Data JPA 时,应用启动出现异常。大致如下:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerAdapter' ... Caused by: java.lang.IllegalArgumentException: Not a managed type: ...OurEntity at org.hibernate.metamodel.internal.MetamodelImpl.managedType(MetamodelImpl.java:583) at org.hibernate.metamodel.internal.MetamodelImpl.managedType(MetamodelImpl.java:85) ... 大意是说,一些 Bean 创建失败了,导致应用启动失败。
根异常是 IllegalArgumentException:“Not a managed type”,本文将带你了解出现这个异常的原因,以及如何解决该异常。
2、缺少 @Entity 注解 出现这种异常的一个可能原因是,忘记使用 @Entity 注解来标记实体。
2.1、重现问题 假设有以下实体类:
public class EntityWithoutAnnotation { @Id private Long id; } 及其对应的 Spring Data JPA repository:
public interface EntityWithoutAnnotationRepository extends JpaRepository<EntityWithoutAnnotation, Long> { } 最后是 Application 启动类,它会扫描上面定义的所有类:
@SpringBootApplication public class EntityWithoutAnnotationApplication { } 尝试使用此 Application 来启动 Spring Context: