Spring JPA:一对一关系中的共享 PK 问题

Spring JPA: Issue with shared PK in one-to-one relationship

我正在尝试创建一对一的共享 PK 关系,但在尝试了很多事情后我感到很困惑...

我会尽量提供所有可能的信息:

我使用的技术:

数据源配置:

spring.datasource.url = jdbc:mysql://localhost:3306/customers?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true&useSSL=false
spring.datasource.username = admin
spring.datasource.password = root

spring.jpa.database-platform = org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql = true

数据库模型:

@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "customer", schema = "customers")
public class Customer {

    @Id
    @GeneratedValue(generator = "uuid2")
    @GenericGenerator(name = "uuid2", strategy = "uuid2")
    @Column(name = "customer_id", columnDefinition = "BINARY(16)")
    private UUID customerId;

    @OneToOne(mappedBy = "customer", cascade = CascadeType.ALL)
    private Address address;
}

@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "address", schema = "customers")
public class Address {

    @Id
    @Column(name = "address_id", columnDefinition = "BINARY(16)")
    private UUID addressId;

    @OneToOne(fetch = FetchType.LAZY)
    @MapsId
    private Customer customer;
}

Flyway迁移文件:

CREATE SCHEMA IF NOT EXISTS CUSTOMERS;

CREATE TABLE CUSTOMERS.CUSTOMER
(
    customer_id BINARY(16) NOT NULL PRIMARY KEY,
) ENGINE = InnoDB;

CREATE TABLE CUSTOMERS.ADDRESS
(
    address_id BINARY(16) NOT NULL PRIMARY KEY,
) ENGINE = InnoDB;

ALTER table CUSTOMERS.ADDRESS
ADD CONSTRAINT fk_customer_address
FOREIGN KEY (address_id)
REFERENCES CUSTOMERS.CUSTOMER (customer_id);

集成测试:

@RunWith(SpringRunner.class)
@SpringBootTest
public class CustomerRepositoryIntegrationTest {

    @Autowired
    private CustomerRepository cut;

    @Test
        public void testSaveAndDeleteCustomer() {
            Address address = new Address();
            Customer customer = new Customer();
            customer.setAddress(address);

            cut.save(customer);

            Customer retrievedCustomer = cut.findAll().get(0);
            assertEquals(customer, retrievedCustomer);
        }
}

错误:

ERROR] testSaveAndDeleteCustomer(com.foxhound.customers.repositories.CustomerRepositoryIntegrationTest)  Time elapsed: 0.034 s  <<< ERROR!
org.springframework.orm.jpa.JpaSystemException: attempted to assign id from null one-to-one property [com.foxhound.customers.models.Address.customer]; nested exception is org.hibernate.id.IdentifierGenerationException: attempted to assign id from null one-to-one property [com.foxhound.customers.models.Address.customer]
    at com.foxhound.customers.repositories.CustomerRepositoryIntegrationTest.testSaveAndDeleteCustomer(CustomerRepositoryIntegrationTest.java:47)
Caused by: org.hibernate.id.IdentifierGenerationException: attempted to assign id from null one-to-one property [com.foxhound.customers.models.Address.customer]
    at com.foxhound.customers.repositories.CustomerRepositoryIntegrationTest.testSaveAndDeleteCustomer(CustomerRepositoryIntegrationTest.java:47)

提前感谢您的帮助。

干杯!

您不了解双向映射的工作原理,因此您需要仔细考虑一下。代码

@OneToOne(fetch = FetchType.LAZY)
@MapsId
private Customer customer;

in Address 使映射成为单向的。通常为了保存它,您需要设置 customer 字段并保存它。

address.setCustomer(customer);        
addressRepo.save(address);

但是您已经定义了双向映射并提供了级联注释。

@OneToOne(mappedBy = "customer", cascade = CascadeType.ALL)
private Address address;

级联注释使您不必执行两个持久化操作(在您的代码中),但这并不意味着您不必在地址中设置客户字段。此外,正如您所注意到的,为了使级联操作起作用,您需要设置客户的地址字段。

customer.setAddress(address);

因此,为了使代码正常工作,您需要更改它以设置客户地址。

Address address = new Address();
Customer customer = new Customer();
customer.setAddress(address);
address.setCustomer(customer);
customerRepo.save(customer);

使用双向映射,您必须管理关系的两边,或者以单向方式使用它来持久化,以双向方式使用它来检索。如果您添加级联注释,那么您还必须管理关系的双方以保持持久性。