Environment:
- OS: Ubuntu 20.04 LTS
- Java: OpenJDK 17.0.3
- Spring Boot: 2.6.7
- MySQL: 8.0.29
Is it normal for Spring Data Jpa (or Jpa in general) to NOT update child identity attributes when the child is persisted (saved) via the parent entity repository?
Consider this:
@Entity( name = "Company" ) @Table( name = "COMPANY" ) final public class Company { @Id @GeneratedValue( strategy = GenerationType.IDENTITY ) @Column( name = "ID", updatable = false, nullable = false ) private Long id = null; @OneToMany( mappedBy = "company", cascade = CascadeType.ALL, orphanRemoval = true ) private Set< Phone > phoneSet = new LinkedHashSet<>(); ...getters/setters... } @Entity( name = "Phone" ) @Table( name = "PHONE" ) final public class Phone { @Id @GeneratedValue( strategy = GenerationType.IDENTITY ) @Column( name = "ID", updatable = false, nullable = false ) private Long id = null; @ManyToOne @JoinColumn( name = "COMPANY_ID" ) private Company company = null; @Column( name = "number" ) String number = null; ...getters/setters... } private void someClassFunction() { ..... Phone phone = new Phone( company, "1115551234" ); company.getPhoneSet().add( phone ); this.companyRespository.save( company ); // <<<< NOTE THIS LINE System.out.println( "phone.id: " + phone.getId() ); }
When I call someClassFunction() I get:
phone.id: null
The database assigned id is NOT available to me immediately. I did check the database and the record was inserted (persisted/saved) and MySQL did assign an ID. If I reload the parent class (company) in another function, the child is reloaded and has the correct id.
However, if I modify someClassFunction() to this:
private void someClassFunction() { ..... Phone phone = new Phone( company, "1115551234" ); company.getPhoneSet().add( phone ); this.phoneRespository.save( phone ); // <<<< NOTE THIS LINE System.out.println( "phone.id: " + phone.getId() ); }
and then call someClassFunction() I get:
phone.id: 47
The database assigned id is available to me immediately.
I don’t find the parent/child persist (ie: this.companyRespository.save( company ) ) behavior disclosed in any online documentation or tutorials. I am led to believe that persisting from the parent should work the same as persisting the child directly ( ie: this.phoneRespository.save( phone ) ).
Am I doing something wrong? Do I have an incorrect entity definition?
Advertisement
Answer
Yes, this is expected – check the contract on your ‘save’ method. It returns an instance which may be a copy of the one you passed in, and it is this instance you need to check and use – it will have its identity set when the entityManager is synchronized with the database (on transaction commit, or if the EntityManager is flushed).
ie
Phone phone = new Phone( company, "1115551234" ); company.getPhoneSet().add( phone ); phone.setCompany(company);//Don't forget to set this! Company mergedCompanyInstance = this.companyRespository.save( company ); Phone managedPhone = mergedCompanyInstance.getPhoneSet().get(0);//assuming you have only one in the set, otherwise, check by phone number. System.out.println( "phone.id: " + phone.getId() ); System.out.println( "managedPhone.id: " + managedPhone.getId() );
Spring does some internal checks in the save method to determine if it should call JPA EntityManager merge or persist api. If it calls merge, it returns the managed entity instance from the context (which may be a clone of the one you give it) while if it calls persist, it takes your instance and causes it to become managed. It is the managed instance that is returned on any findById and query methods.