Spring-Data-Jpa

解决 Spring JPA 异常:“Unable to Locate Attribute with the Given Name”

1、简介 Spring 为程序员简化 Java 应用程序中的数据库交互提供了一个最强大的框架,那就是 Spring JPA(Java Persistence API)。它为 JPA 提供了一个稳定的抽象。 然而,尽管使用方便,开发人员还是经常会遇到一些错误,而这些错误的排查和解决都非常具有迷惑性。其中一个常见问题就是 “Unable to Locate Attribute with the Given Name” 错误。 本文将带你了解 Spring JPA 出现 “Unable to Locate Attribute with the Given Name” 异常的原因以及解决办法。 2、案例 我们生产了一个可穿戴的小工具。经过最近的一项调查,我们的营销团队发现,在我们的平台上按传感器类型、价格和受欢迎程度对产品进行分类,可以突出最受欢迎的产品,从而帮助客户做出更好的购买决策。 3、添加 Maven 依赖 我们使用 H2 内存数据库在项目中创建一个可穿戴设备表,并将样本数据填充到该表中,以便在接下来的测试中使用。 首先,添加以下 Maven 依赖: <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>2.2.224</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>2.7.11</version> </dependency> 4、应用配置 在 src/main/resources 文件夹中,创建包含以下配置内容的 application-h2.properties: # H2 配置 hibernate.dialect=org.hibernate.dialect.H2Dialect hibernate.hbm2ddl.auto=create-drop # Spring Datasource URL spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 在 src/main/resources 文件夹中创建名为 testdata.

Spring Data 根据嵌套对象的属性检索数据

1、概览 在 Spring Data 中,使用基于方法名称的派生查询来查询实体是很常见的。在处理实体之间的关系(如嵌套对象)时,Spring Data 提供了各种机制来检索这些嵌套对象中的数据。 本文将带你了解如何使用查询派生和 JPQL(Java 持久性查询语言)通过嵌套对象的属性进行查询。 2、场景概述 考虑一个有两个实体的简单场景:Customer 和 Order。每个 Order 都通过 ManyToOne(多对一)关系关联到一个 Customer。 我们要查找属于某个 Customer 的所有 Order,该 Customer 有特定的 email。在这种情况下,email 是 Customer 实体的属性,而我们的主要查询将在 Order 实体上执行。 实体示例如下: @Entity @Table(name = "customers") public class Customer { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String email; // Getter / Setter 省略 } @Entity @Table(name = "orders") public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Date orderDate; @ManyToOne @JoinColumn(name = "customer_id") private Customer customer; // Getter / Setter 省略 } 3、使用派生查询 Spring Data JPA 允许开发者从 Repository 接口中的方法签名派生查询,从而简化了查询创建:

Spring Data JPA 中的 @DynamicInsert

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:

Spring Data MongoDB 构建多个条件的查询

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 操作提供了丰富的方法集:

JPA、Hibernate 和 Spring Data JPA 中的数据库审计

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(流式)查询

简介 本文将带你了解在 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 之前预取所有查询结果。

Spring Data JPA 检索最大值(Max Value)

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 投影接口:

在 Spring Data JPA 查询中使用枚举(Enum)

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 接口,以便与数据库交互:

解决 Spring Data JPA ConverterNotFoundException: No converter found

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 属性。

JPA 级联保存实体中的子实体

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 注解。