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.
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 接口中的方法签名派生查询,从而简化了查询创建:
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 注解。