Spring-Data-Jpa

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

Spring JPA 从序列(SEQUENCE)中获取下一个值

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.

Spring Data JPA 异常 “IllegalArgumentException: Not a Managed Type”

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:

Spring Data JPA 实现 updateOrInsert(更新或保存)

1、简介 在应用开发中,执行 “更新或插入” 操作(也称为 “upsert”)的需要是很常见的。这个操作涉及将新记录存入数据库表中,如果记录不存在,则插入新记录;如果记录已经存在,则更新现有记录。 本文将带你了解使用 Spring Data JPA 执行 “更新或插入” 操作的不同方法。 2、实体类 定义 CreditCard 实体类用于演示: @Entity @Table(name="credit_card") public class CreditCard { @Id @GeneratedValue(strategy= GenerationType.SEQUENCE, generator = "credit_card_id_seq") @SequenceGenerator(name = "credit_card_id_seq", sequenceName = "credit_card_id_seq", allocationSize = 1) private Long id; private String cardNumber; private String expiryDate; private Long customerId; // Get / Set 方法省略 } 3、实现 本文介绍三种不同的方法来实现 “更新或插入”。 3.1、使用 Repository 方法 使用从 CrudRepository 接口继承的 save(entity) 方法在 Repository 中编写一个带事务的 default 方法。

Spring Data JPA 出现异常后不回滚,继续处理事务

1、概览 JPA 中的事务机制是一个功能强大的工具,它通过提交所有更改或在出现异常时回滚更改来确保原子性和数据完整性。然而,在某些情况下,我们可能需要在遇到异常的情况下继续进行事务处理而不回滚数据更改。 2、出现异常后事务自动回滚 在事务中可能会出现两种主要的异常情况。 2.1、在 Service 层出现异常后回滚事务 我们可能遇到回滚(Rollback)的第一个地方是在 Service 层,外部异常可能会影响数据库更改。 让我们通过下面的示例更仔细地了解一下这种情况。首先,添加 InvoiceEntity,作为数据模型: @Entity @Table(uniqueConstraints = {@UniqueConstraint(columnNames = "serialNumber")}) public class InvoiceEntity { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; // 自增 ID private String serialNumber; // 序列号 private String description; // 说明 // Getter / Setter } 如上,包含了一个自增 ID、一个需要在整个系统中唯一的序列号和一个说明。 现在,创建负责发票事务操作的 InvoiceService: @Service public class InvoiceService { @Autowired private InvoiceRepository repository; @Transactional public void saveInvoice(InvoiceEntity invoice) { repository.save(invoice); sendNotification(); } private void sendNotification() { throw new NotificationSendingException("Notification sending is failed"); } } 在 saveInvoice() 方法中,我们添加了事务性保存发票(invoice)和发送相关通知的逻辑。但是,在发送通知的过程中,会抛出异常:

Spring Data JPA 查询 JSOB 类型的列

1、简介 Spring Data JPA 为与关系数据库交互提供了强大的抽象层。然而,传统的关系表可能并不适合存储复杂的、半结构化的数据,如产品详细信息或用户偏好。这就是 JSONB 数据类型的用武之地。 本文将带你学习使用 Spring Data JPA 查询 JSONB 列的各种方法。 2、JSONB 列 JSONB(JavaScript Object Notation for Databases)是一种数据类型,专门用于在 PostgreSQL 等关系数据库中存储 JSON 数据。它允许我们在单列中使用键值对和嵌套对象来表示复杂的数据结构。通过 JPA Provider(如 Hibernate),Spring Data JPA 允许我们将这些 JSONB 列映射到实体类中的属性。 3、映射 JSONB 列 我们可以使用 @Column 注解的 columnDefinition 属性,在实体类中明确定义列类型: @Column(columnDefinition = "jsonb") private String attributes; 这种方法主要与 PostgreSQL 相关,因为 PostgreSQL 本身支持 jsonb 数据类型。通过在实体类的相应属性中添加此注解,我们就能为数据库提供所需列类型的提示。Spring Data JPA 通常会根据数据库列定义自动检测 jsonb 数据类型,因此在很多情况下,此注解是可选的。 4、设置项目依赖和测试数据 创建一个基本的 Spring Boot 项目,其中包含测试 JSONB 查询所需的依赖和测试数据。 4.1、项目设置 首先,在 Maven pom.xml 文件中添加必要的依赖:

Querydsl 和 JPA Criteria

1、概览 Querydsl 和 JPA Criteria 是在 Java 中构建类型安全查询的流行框架。它们都提供了以静态类型表达查询的方法,使编写与数据库交互的高效、可维护代码变得更容易。本文将从多个角度对它们进行比较。 2、设置 首先,设置依赖。在所有示例中,都使用 HyperSQL 数据库: <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>2.7.1</version> </dependency> 添加 maven-processor-plugin,使用 JPAMetaModelEntityProcessor 和 JPAAnnotationProcessor 为框架生成元数据。配置如下: <plugin> <groupId>org.bsc.maven</groupId> <artifactId>maven-processor-plugin</artifactId> <version>5.0</version> <executions> <execution> <id>process</id> <goals> <goal>process</goal> </goals> <phase>generate-sources</phase> <configuration> <processors> <processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor> <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor> </processors> </configuration> </execution> </executions> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-jpamodelgen</artifactId> <version>6.2.0.Final</version> </dependency> </dependencies> </plugin> <persistence-unit name="com.baeldung.querydsl.intro"> <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider> <properties> <property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver"/> <property name="hibernate.connection.url" value="jdbc:hsqldb:mem:test"/> <property name="hibernate.connection.username" value="sa"/> <property name="hibernate.connection.password" value=""/> <property name="hibernate.hbm2ddl.auto" value="update"/> <property name="hibernate.

Spring Data JPA 中的 Refresh 和 Fetch

1、简介 Java Persistence API(JPA)是连接 Java 对象和关系数据库的桥梁,允许我们无缝地持久化和检索数据。本文将会带你了解各种策略和技术,以便在 JPA 中进行保存操作后有效地刷新(Refresh)和获取(Fetch)实体。 2、了解 Spring Data JPA 中的实体管理 在 Spring Data JPA 中 ,实体管理围绕 JpaRepository 接口展开,该接口是与数据库交互的主要机制。通过继承了 CrudRepository 的 JpaRepository 接口 ,Spring Data JPA 为实体的持久化、检索、更新和删除提供了一套强大的方法。 此外, Spring 容器会将实体管理器(EntityManager) 自动注入这些 repository 接口。该组件是嵌入 Spring Data JPA 的 JPA 基础架构的一个组成部分,可促进与底层持久化上下文(Persistence Context)的交互和 JPA 查询的执行。 2.1、Persistence Context JPA 中的一个关键组件是持久化上下文(Persistence Context)。把这个上下文想象成一个临时存放区,JPA 在这里管理检索或创建实体的状态。 它可以确保: 实体是唯一的: 在任何时候,上下文中都只存在一个具有特定主键的实体实例。 跟踪更改: 实体管理器(EntityManager)会跟踪上下文中对实体属性所做的任何修改。 保持数据一致性: 在事务处理期间,实体管理器会将上下文中的更改与底层数据库同步。 2.2、JPA 实体的生命周期 JPA 实体有四个不同的生命周期阶段: 瞬时(New)、托管(Managed)、删除(Removed)和游离(Detached)。 当我们使用实体的构造函数创建一个新实体实例时,它处于 “瞬时” 状态。我们可以通过检查实体的 ID(主键)是否为 null 来验证这一点: Order order = new Order(); if (order.

Spring Data 3 中的新 CRUD Repository 接口

1、概览 本文将带你了解 Spring Data 3 中引入的新 Repository 接口。 Spring Data 3 引入了基于 List 的 CRUD Repository 接口,可用于替换返回 Iterable 的现有 CRUD Repository 接口。此外,分页和排序接口默认不继承原始 CRUD Repository,而是由用户自己选择。接下来看看这些接口与现有接口有何不同,以及如何使用它们。 2、项目设置 首先创建一个 Spring Boot 应用,其中包含一个简单的实体和将使用新接口的 Repository。 2.1、依赖 首先,为项目添加所需的 spring-boot-starter-data-jpa 依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>3.2.3</version> </dependency> 除此之外,还要配置数据库。可以使用任何 SQL 数据库,本文将使用 H2 内存数据库: <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> 2.2、Entity 创建一个 Book 实体: @Entity public class Book { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String title; private String author; private String isbn; // 构造器、Getter、Setter } 有了实体后,来看看如何使用新接口与数据库交互。

使用 JUnit 和 @DataJpaTest 测试 Spring Data Repository

1、简介 在使用 Spring Data JPA 进行数据持久化的 Spring Boot 应用中,测试与数据库交互的 Repository 至关重要。 本文将带你了解如何使用 Spring Boot 提供的 @DataJpaTest 注解和 JUnit 对 Spring Data JPA Repository 进行有效地测试。 2、了解 @DataJpaTest 和 Repository 类 本节主要介绍在 Spring Boot 应用中,@DataJpaTest 和 Repository 之间的交互。 2.1、@DataJpaTest @DataJpaTest 注解用于测试 Spring Boot 应用中的 JPA Repository。它是一个专门的测试注解,为测试持久层提供了一个最小的 Spring Context。该注解可与 @RunWith 和 @SpringBootTest 等其他测试注解结合使用。 此外,@DataJpaTest 的范围仅限于应用的 JPA Repository 层。它不会加载整个 Application Context,从而使测试更快、更集中。此注解还为测试 JPA 实体提供了预配置的 EntityManager 和 TestEntityManager。 2.2、Repository 在 Spring Data JPA 中,Repository 是 JPA 实体之上的一个抽象层。它为执行 CRUD(创建、读取、更新、删除)操作和执行自定义查询提供了一组方法。这些 Repository 通常从 JpaRepository 等接口继承而来,负责处理与特定实体类型相关的数据库交互。