I followed the example of Modeling With a Shared Primary Key as below:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
//...
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
private Address address;
//... getters and setters
}
@Entity
@Table(name = "address")
public class Address {
@Id
@Column(name = "user_id")
private Long id;
//...
@OneToOne
@MapsId
@JoinColumn(name = "user_id")
private User user;
//... getters and setters
}
However, if there are already a record with id 123456 in address table, then I tried to update the record like below:
Address po = new Address();
po.setId(123456L);
po.setCountry("TW");
AddressRepository.save(po);
Duplicate entry '123456' for key Exception will occur. Why JPA will insert a new record instead of merging it? How to solve this problem?
CodePudding user response:
I know the reason finally. It is because the entity has version field and the version field in the new entity is null.
We need to dig into the source of of save() method in JPA.
@Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
Then, if we don't override the isNew(), it will use the default isNew() of JpaMetamodelEntityInformation.
@Override
public boolean isNew(T entity) {
if (!versionAttribute.isPresent()
|| versionAttribute.map(Attribute::getJavaType).map(Class::isPrimitive).orElse(false)) {
return super.isNew(entity);
}
BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
return versionAttribute.map(it -> wrapper.getPropertyValue(it.getName()) == null).orElse(true);
}
Here, we can see that if version is present and the version is different from the existing record in the database, the entity will be a new entity and JPA will execute the insert action. Then, it will occur the error of duplicate entry.