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.ExecutionPhase.BEFORE_TEST_METHOD)
3、使用 @SequenceGenerator 注解
通常我们使用 JPA 中的 @SequenceGenerator
注解来配置序列生成器。该生成器可用于在实体类中自动生成主键。
它还经常与 @GeneratedValue
注解结合使用,以指定主键的生成策略。下面是使用 @SequenceGenerator
配置主键生成策略的示例:
@Entity
public class MyEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "mySeqGen")
@SequenceGenerator(name = "mySeqGen", sequenceName = "my_sequence_name", allocationSize = 1)
private Long id;
// 其他实体字段和方法
}
strategy
为 GenerationType.SEQUENCE
策略的 @GeneratedValue
注解表示将使用序列生成主键值。随后,generator
属性将此策略与名为 mySeqGen
的指定序列生成器关联起来。
@SequenceGenerator
注解配置了名为 mySeqGen
的序列生成器。它指定了数据库序列的名称 my_sequence_name
和一个可选参数 allocationSize
(分配大小)。
allocationSize
是一个整数值,指定一次从数据库中预获取多少个序列号。例如,如果我们将 allocationSize
设置为 50
,Persistence Provider 就会在一次调用中请求 50
个序列号,并将其存储在内部。然后,它就会使用这些预获取的序列号来生成实体 ID,减少了与数据库的交互,这非常适合并发写入高的应用。
通过这种配置,当我们持久化一个新的 MyEntity
实例时,Persistence Provider 会自动从名为 my_sequence_name
的序列中获取下一个值。检索到的序列号会分配给实体的 id
字段,然后再保存到数据库中。
下面的示例演示了如何在持久化实体后检索序列号和实体 ID:
MyEntity entity = new MyEntity();
myEntityRepository.save(entity);
long generatedId = entity.getId(); // 自动生成的序列 ID
assertNotNull(generatedId);
assertEquals(1L, generatedId);
保存实体后,可以使用实体对象上的 getId()
方法访问生成的 ID。
4、Spring Data JPA 自定义查询
在某些情况下,我们可能需要在保存到数据库之前获得下一个序列号或唯一 ID。为此可以在 Repository 中使用原生 SQL 查询来访问序列。
检索下一个序列值的具体语法取决于数据库系统。例如,在 PostgreSQL 或 Oracle 中,使用 NEXTVAL
函数从序列中获取下一个值。
下面是一个使用 @Query
注解实现的示例:
@Repository
public interface MyEntityRepository extends JpaRepository<MyEntity, Long> {
@Query(value = "SELECT NEXTVAL('my_sequence_name')", nativeQuery = true)
Long getNextSequenceValue();
}
如上,用 @Query
对 getNextSequenceValue()
方法进行了注解。通过 @Query
注解,我们可以指定一个原生 SQL 查询,使用 NEXTVAL
函数从序列中检索下一个值。这样就能直接访问序列值:
@Autowired
private MyEntityRepository myEntityRepository;
long generatedId = myEntityRepository.getNextSequenceValue();
assertNotNull(generatedId);
assertEquals(1L, generatedId);
不同数据库获取序列的 SQL 不同 ,如果更换数据库,可能需要调整 SQL 查询。
5、使用 EntityManager
另外,Spring JPA 还提供了 EntityManager
API,可以用于直接检索下一个序列值。这种方法提供了更精细的控制,但绕过了 JPA 的对象关系映射功能。
使用 Spring Data JPA 中的 EntityManager
API 从序列中检索下一个值的示例如下:
@PersistenceContext
private EntityManager entityManager;
public Long getNextSequenceValue(String sequenceName) {
BigInteger nextValue = (BigInteger) entityManager.createNativeQuery("SELECT NEXTVAL(:sequenceName)")
.setParameter("sequenceName", sequenceName)
.getSingleResult();
return nextValue.longValue();
}
使用 createNativeQuery()
方法创建一个原生 SQL 查询。在查询中,调用 NEXTVAL
函数从序列中获取下一个值。可以看到,PostgreSQL 中的 NEXTVAL
函数返回 BigInteger
类型的值。因此,使用 longValue()
方法将 BigInteger
转换为 Long
。
getNextSequenceValue()
方法调用如下:
@Autowired
private MyEntityService myEntityService;
long generatedId = myEntityService.getNextSequenceValue("my_sequence_name");
assertNotNull(generatedId);
assertEquals(1L, generatedId);
6、总结
本文介绍了使用 Spring Data JPA 从数据库序列中获取下一个值的各种方法。
Spring JPA 通过 @SequenceGenerator
和 @GeneratedValue
等注解提供了与数据库序列的无缝集成。在保存实体前需要下一个序列值的情况下,可以使用 Spring Data JPA 进行自定义的本地 SQL 查询。
Ref:https://www.baeldung.com/spring-jpa-sequence-nextval