Spring Data JDBC 和 R2DBC 4.0 将支持复合 ID

工程 | Jens Schauder | 2025年7月22日 | ...

我很高兴地宣布,Spring Data JDBC 和 R2DBC 从版本 4.0.0-M4 开始最终支持复合 ID。

你们中的大多数人可能知道,但为了确保每个人都有相同的理解:从数据库的角度来看,复合 ID(或复合键)是由多个列组成的主键。在 Java 端,这些列被映射到一个实体,每个列都有一个属性。用法应该很简单,我将在下面的文章中为 JDBC 进行演示。R2DBC 中的用法类似。

要开始使用,只需在您的聚合根中引用实体的字段上添加 @Id 注解,而不是简单的类型。

class Employee {

	@Id
	EmployeeId id;

	String name;

	// ...
}

record EmployeeId(
		Organization organization,
		Long employeeNumber) {
}

enum Organization {
	RND,
	SALES,
	MARKETING,
	PURCHASING
}

该引用会自动成为嵌入式引用,即实体的字段会成为聚合根表的列。

create table employee
(
    organization    varchar(20),
    employee_number int,
    name            varchar(100)
);

如果您想调整名称,可以添加 @Embedded 注解,它允许您提供一个前缀。当加载实体时所有值都为 null 时,指定应该发生什么看起来有点奇怪。但是使用 Embedded,您必须这样做,尽管为 null 的主键会在所有地方引起问题并且根本无法工作。

class Employee {

	@Id
	@Embedded.Nullable(prefix = "id_")
	EmployeeId id;

	String name;
	
	// ... 
}

create table employee
(
    id_organization    varchar(20),
    id_employee_number int,
    name            varchar(100)
);

与普通 ID 一样,Spring Data Relational 使用 ID 值作为新实体的指示:如果 ID 值为 null,则该实体被视为新实体,将执行插入操作。如果 ID 值不为 null,则需要进行更新。

当保存具有复合 ID 的新实体时,您现在面临一个小问题:复合 ID 无法通过自增列轻松生成,因为它根据定义由多列组成。处理此问题的一种方法是使用 BeforeConvertCallback

@Bean
BeforeConvertCallback<Employee> idGeneration() {
	return new BeforeConvertCallback<>() {
		AtomicLong counter = new AtomicLong();

		@Override
		public Employee onBeforeConvert(Employee employee) {
			if (employee.id == null) {
				employee.id = new EmployeeId(Organization.RND, counter.addAndGet(1));
			}
			return employee;
		}
	};
}
repository.save(new Employee("Mark Paluch"));

在大多数具有复合 ID 的情况下,预先设置 ID 并使用乐观锁(即 null 版本字段将标记实体为新实体)或显式调用 JdbcAggregateTemplate.insert 可能会更容易。

interface EmployeeRepository extends Repository<Employee, EmployeeId>, InsertRepository<Employee> {
	Employee findById(EmployeeId id);

	Employee save(Employee employee);
}
interface InsertRepository<E> {
	E insert(E employee);
}
class InsertRepositoryImpl<E> implements InsertRepository<E> {
	@Autowired
	private JdbcAggregateTemplate template;
	@Override
	public E insert(E employee) {
		return template.insert(employee);
	}
}
@Autowired
EmployeeRepository repository;

// ...

repository.insert(new Employee(new EmployeeId(Organization.RND, 23L), "Jens Schauder"));

我希望您发现 Spring Data Relational 的这一新增功能很有用。本文中使用的示例的完整代码可以在 https://github.com/spring-projects/spring-data-examples/tree/main/jdbc/composite-ids 找到。

如果您发现错误或有改进建议,请在 https://github.com/spring-projects/spring-data-relational/issues 创建工单。

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,助您加速进步。

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一份简单的订阅。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看所有