教程

清除 JPA/Hibernate 中托管的实体

1、概览 本文将带你了解 JPA 是如何托管实体的,以及 Persistence Context(持久化上下文)由于外部变化而无法返回最新数据的情况。 2、Persistence Context 每个 EntityManager 都与一个 Persistence Context 相关联,该上下文在内存中存储所管理的实体。每当通过 EntityManager 对实体执行任何数据操作时,该实体就会变成由 Persistence Context 管理的实体。 当再次检索实体时,JPA 会从 Persistence Context 返回托管实体,而不是从数据库中获取。这种缓存机制有助于提高性能,而无需从数据库中重复获取相同的数据。 Persistence Context 在 JPA 中也被称为一级(first-level,L1)缓存。 3、Demo 设置 首先,创建一个简单的实体类: @Entity @Table(name = "person") public class Person { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; // 构造函数、Getter、Setter 方法省略 } 接下来,创建一个 Person 实体并将其持久化到数据库中: EntityTransaction transaction = entityManager.getTransaction(); transaction.begin(); Person person = new Person(); person.setName("David Jones"); entityManager.persist(person); transaction.

JPA 中实体的继承与组合

1、简介 继承(Inheritance)和组合(Composition)是面向对象编程(OOP)中的两个基本概念,我们也可以在 JPA 中利用它们进行数据建模。在 JPA 中,继承和组合都是对实体间关系进行建模的技术,但它们代表的是不同类型的关系。本文将带你了解这两种方法及其影响。 2、JPA 中的继承 继承是一种 “is-a” 关系,即子类继承超类的属性和行为。这允许子类从超类继承属性和方法,从而促进了代码的重用。JPA 提供了几种策略来模拟实体与其对应的数据库表之间的继承关系。 2.1、单表继承(STI) 单表继承(Single Table Inheritance,STI)将所有子类映射到单个数据库表中。通过利用 区分列 来区分子类实例,这简化了 Schema 管理和查询执行过程。 首先,使用 @Entity 注解将 Employee 实体类定义为超类。接下来,将继承策略设置为 InheritanceType.SINGLE_TABLE,这样所有子类都会映射到同一个数据库表*。 然后,使用 @DiscriminatorColumn 注解来指定 Employee 类中的 区分列。该列用于区分单个表中不同类型的实体。 示例如下,使用 name = "employee_type" 将列名称指定为 employee_type,并使用 discriminatorType = DiscriminatorType.STRING 表示列包含字符串值: @Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = "employee_type", discriminatorType = DiscriminatorType.STRING) public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // Get / Set 方法省略 } 对于每个子类,使用 @DiscriminatorValue 注解来指定与该子类相对应的 区别列 的值。在本例中,我们使用 manager 和 developer 分别作为 Manager 和 Developer 子类的 区别值:

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 Boot 设置日期(Date/LocalDate/LocalDateTime)的 JSON 格式化

1、概览 本文将带你了解如何在 Spring Boot 应用中格式化 JSON Date 字段。 Spring Boot 默认使用 Jackson 作为 JSON 的序列化/反序列化框架。 2、在 Date 字段上使用 @JsonFormat 2.1、设置格式化 我们可以使用 @JsonFormat 注解来格式化特定字段: public class Contact { // 其他字段 @JsonFormat(pattern="yyyy-MM-dd") private LocalDate birthday; @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss") private LocalDateTime lastUpdate; // getter/setter 方法省略 } 如上,用了 Java 8 的日期类型,它在处理时间类型时非常方便。birthday 字段只显示日期,而 lastUpdate 字段则包括了时间。 当然,如果需要使用 java.util.Date 等传统类型,也可以同样的方式使用注解: public class ContactWithJavaUtilDate { // 其他字段 @JsonFormat(pattern="yyyy-MM-dd") private Date birthday; @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss") private Date lastUpdate; // getter/setter 方法省略 } 最后,来看看使用 @JsonFormat 格式化日期字段后的输出:

配置 Mybatis 的 SQL 查询日志

1、概览 MyBatis 是 Java 界流行的持久化框架,它通过将 SQL 查询映射到 Java 方法来简化数据库操作。 在使用 MyBatis 开发应用时,查看正在执行的 SQL 查询通常对调试很有用,本文将带你了解如何在 MyBatis 中将 SQL 查询日志输出到控制台。 2、支持的日志实现 MyBatis 是一个灵活的框架,可以与各种日志框架集成,包括 SLF4J、Apache Commons Logging、Log4j 2 和 JDK Logging。本文主要关注 Stdout (标准输出,即控制台)日志和 SLF4J。 Stdout 日志在本地功能开发过程中非常有用,它提供了一种简单的调试方法。而 SLF4J 更适合生产应用,它提供了更高级的抽象,可与其他的日志框架无缝集成。 3、在 MyBatis 中配置 Stdout 日志 使用 stdout 记录 MyBatis SQL,可以直接在控制台上查看执行的 SQL 语句。这种方法在开发和调试过程中非常方便。 要启用 MyBatis SQL 的 stdout 日志,需要在应用的 mybatis-config 文件中添加日志设置: <configuration> <settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> </configuration> 将 logImpl 属性配置为 STDOUT_LOGGING 后,MyBatis 将在执行 SQL 查询时输出原始 SQL 语句、查询参数和查询结果。输出通常包括执行的 SQL、绑定的参数和返回的结果集等详细信息:

Spring AI - 结构化输出

科学处理事物的片段和碎片,并假设存在连续性,而艺术则只关注事物的连续性,假设存在片段和碎片。- 罗伯特·M·皮尔西格 LLM(大型语言模型)生成结构化输出的能力对于依赖于可靠解析输出值的下游应用非常重要。开发人员希望将 AI 模型的结果快速转化为数据类型,如 JSON、XML 或 Java 类,以便传递给应用中的其他函数和方法。 Spring AI Structured Output Converter(结构化输出转换器)有助于将 LLM 输出转换为结构化格式。如下图所示,这种方法围绕 LLM 文本补全端点进行操作: 使用通用的补全 API 从大型语言模型(LLM)生成结构化输出需要对输入和输出进行仔细处理。结构化输出转换器在 LLM 调用之前和之后发挥着关键作用,确保实现所需的输出结构。 在进行 LLM 调用之前,转换器会将格式指令附加到提示中,为模型提供明确的指导,以生成所需的输出结构。这些指令充当蓝图,使模型的响应符合指定的格式。 在 LLM 调用之后,转换器会获取模型的输出文本,并将其转换为结构化类型的实例。转换过程包括解析原始文本输出,并将其映射到相应的结构化数据表示,如 JSON、XML 或特定领域(Domain)的数据结构。 注意,AI 模型不能保证按要求返回结构化输出。它可能不理解提示,也可能无法按要求生成结构化输出。 TIP: 如果你不想深入了解 API 的细节,可以过下一段,直接看 “使用转换器 ”部分。 1、结构化输出 API StructuredOutputConverter 接口定义如下: public interface StructuredOutputConverter<T> extends Converter<String, T>, FormatProvider { } 它以目标结构化类型 T 为参数,结合了 Spring Converter<String, T> 接口和 FormatProvider 接口: public interface FormatProvider { String getFormat(); } 下图说明了通过结构化输出 API 组件的数据流程。

解决 Spring Boot H2 JdbcSQLSyntaxErrorException “Table not found”

1、简介 H2 是一个简单的轻量级内存数据库,Spring Boot 可以自动对其进行配置,使开发人员可以轻松测试数据访问逻辑。 通常情况下,org.h2.jdbc.JdbcSQLSyntaxErrorException 是用于表示与 SQL 语法相关的错误的异常。“Table not found” 表示 H2 无法找到指定的表。 本文将带你了解 H2 抛出 JdbcSQLSyntaxErrorException 异常的原因以及解决办法。 2、示例 既然知道了异常背后的根本原因,来看看如何重现异常。 2.1、H2 配置 Spring Boot 会配置应用使用用户名 sa 和空密码连接到可嵌入的数据库 H2。将这些属性添加到 application.properties 文件中: spring.datasource.url=jdbc:h2:mem:mydb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password= 现在,假设有一个名为 person 的表。在此,使用一个基本的 SQL 脚本为数据库添加数据。默认情况下,Spring Boot 会加载 data.sql 文件: INSERT INTO "person" VALUES (1, 'Abderrahim', 'Azhrioun'); INSERT INTO "person" VALUES (2, 'David', 'Smith'); INSERT INTO "person" VALUES (3, 'Jean', 'Anderson'); 2.2、对象关系映射 接下来,使用 JPA 注解将表 person 映射到一个实体。

一种极简单的 Spring Boot 单元测试方法

本文主要介绍了一种单元测试方法,力求零基础人员可以从本文中受到启发,可以搭建一套好用的单元测试环境,并能切实提高交付代码的质量。极简体现在除了 POM 依赖和单元测试类之外,其他什么都不需要引入,只需要一个本地能启动的 Spring Boot 项目。 1、POM依赖 Springboot版本: 2.6.6 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>3.12.4</version> </dependency> 2、单元测试类示例 主要有两种。 第一种,偏集成测试 需要启动项目,需要连接数据库、RPC 注册中心等。 主要注解:@SpringBootTest + @RunWith(SpringRunner.class) + @Transactional + @Resource + @SpyBean + @Test @SpringBootTest + @RunWith(SpringRunner.class) 启动了一套 Spring Boot 的测试环境; @Transactional 对于一些修改数据库的操作,会执行回滚,能测试执行 sql,但是又不会真正的修改测试库的数据; @Resource 主要引入被测试的类; @SpyBean Spring Boot 环境下 mock 依赖的 Bean,可以搭配 Mockito.doAnswer(...).when(xxServiceImpl).xxMethod(any()) Mock 特定方法的返回值; @Test 标识一个测试方法; TIP:对于打桩有这几个注解 @Mock @Spy @MockBean @SpyBean,每一个都有其对应的搭配,简单说 @Mock 和 @Spy 要搭配 @InjectMocks 去使用,@MockBean 和 @SpyBean 搭配 @SpringBootTest + @RunWith(SpringRunner.

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 WebClient 中的 exchange() 和 retrieve() 方法

1、概览 WebClient 是一个简化 HTTP 请求执行过程的接口。与 RestTemplate 不同,它是一个响应式非阻塞客户端,可以消费和操作 HTTP 响应。虽然它被设计为非阻塞型,但也可用于阻塞型场景。 本文将带你了解 WebClient 接口中的关键方法,包括 retrieve()、exchangeToMono() 和 exchangeToFlux(),以及它们之间的差异。 2、示例项目设置 首先,创建一个 Spring Boot 应用,在 pom.xml 中添加 spring-boot-starter-webflux 依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> <version>3.2.4</version> </dependency> 该依赖提供了 WebClient 接口,用于执行 HTTP 请求。 另外,来看看 https://jsonplaceholder.typicode.com/users/1 请求的 GET 响应示例: { "id": 1, "name": "Leanne Graham", // ... } 创建一个名为 User 的 POJO 类: class User { private int id; private String name; // 构造函数、Getter、Setter 方法省略 } 来自 JSONPlaceholder API 的 JSON 响应将被反序列化并映射到 User 类的实例。