Spring Boot Data JPA一对一共享主键关联报错求助
Hey there, let's figure out why you're hitting that detached entity passed to persist error with your Spring Boot Data JPA one-to-one shared primary key setup.
First, let's break down the root causes and fix them step by step:
1. Fix the Entity Mapping Mistake
Looking at your Customer entity, you've added @PrimaryKeyJoinColumn alongside mappedBy = "customer" on the @OneToOne annotation—and that's a problem.
When you use mappedBy, you're telling JPA that the Phone entity is the owner of this relationship (it maintains the foreign key mapping). The @PrimaryKeyJoinColumn annotation is redundant here (and conflicting) because the relationship's primary key mapping is already defined in the Phone entity with @MapsId and @JoinColumn(name = "id").
Update your Customer entity to remove the extra annotation and add cascade support (this will make saving related entities smoother):
@Entity public class Customer { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String firstName; private String lastName; // Remove @PrimaryKeyJoinColumn, add cascade for easier related entity handling @OneToOne(mappedBy = "customer", cascade = CascadeType.ALL) private Phone phone; // Constructors, getters, and setters }
Your Phone entity is already correctly mapped, so leave it as is:
@Entity public class Phone { @Id private Long id; private String number; @OneToOne @MapsId @JoinColumn(name = "id") private Customer customer; // Constructors, getters, and setters }
2. Adjust Your Test Code (Two Valid Approaches)
Now that the mapping is fixed, let's fix the test logic to avoid the detached entity issue.
Approach 1: Use Cascade Save (Recommended)
Since we added cascade = CascadeType.ALL to the Customer's phone association, we can save just the Customer, and JPA will automatically save the associated Phone. This is cleaner and follows JPA best practices for bidirectional relationships:
@Test public void test() { customerRepository.deleteAll(); phoneRepository.deleteAll(); final Customer customer = new Customer(); customer.setFirstName("John"); customer.setLastName("Doe"); final Phone phone = new Phone(); phone.setNumber("1234567890"); phone.setCustomer(customer); // Link phone to customer (owner side) customer.setPhone(phone); // Link customer to phone (inverse side) customerRepository.save(customer); // Saves both customer and phone via cascade }
Approach 2: Ensure You're Using a Managed Customer Instance
If you prefer not to use cascading, make sure the Customer instance you link to the Phone is managed (i.e., part of Hibernate's persistence context). Even though customerRepository.save() returns a managed instance, explicitly using the saved instance guarantees it's in the correct state:
@Test public void test() { customerRepository.deleteAll(); phoneRepository.deleteAll(); final Customer customer = new Customer(); customer.setFirstName("John"); customer.setLastName("Doe"); // Save and retain the managed customer instance Customer savedCustomer = customerRepository.save(customer); final Phone phone = new Phone(); phone.setNumber("1234567890"); phone.setCustomer(savedCustomer); // Use the managed instance here phoneRepository.save(phone); }
Why Did the Original Error Happen?
- The conflicting mapping annotations (
mappedBy+@PrimaryKeyJoinColumn) confused JPA, making it unable to correctly recognize that the Customer instance was already persisted and managed. - Even without mapping issues, if the Customer instance becomes detached (for example, if the batch
deleteAll()operations cleared the persistence context), saving the Phone would trigger an error because JPA tries to persist the detached Customer again.
内容的提问来源于stack exchange,提问作者user12091373

