Atlassian uses cookies to improve your browsing experience, perform analytics and research, and conduct advertising. Accept all cookies to indicate that you agree to our use of cookies on your device. Atlassian cookies and tracking notice, (opens new window)
Composite primary key is altered when part of it is from a lazy non optional ManyToOne
Description
Hello, I'm migrating from hibernate 5.6.15 to hibernate 6.2 and I have noticed some issues. it seems that I can’t use a composite primary key when part of it is from a lazy non optional ManyToOne, as it gives me a error saying primary key was altered.
@Test
void shouldDeleteProduct() {
// Given
String string = "ID2";
String operatorID = "operatorID2";
String test = "test";
Operator operator = new Operator(operatorID);
operatorService.addOperator(operator);
Product product = new Product(string, operator);
product.setDescription(test);
productService.addProduct(product);
// When
ProductPK productPK = new ProductPK(string, operatorID, USA);
productService.deleteProduct(productPK);
// Then
Optional<Product> byId2 = productService.getProduct(productPK);
assertThat(byId2).isEmpty();
}
an exception occurs when product is being deleted :
org.springframework.orm.jpa.JpaSystemException: identifier of an instance of com.example.demo.local.Product was altered from Product.ProductPK(productId=ID2, operator=Operator.OperatorPK(operatorId=null, country=null)) to Product.ProductPK(productId=ID2, operator=Operator.OperatorPK(operatorId=operatorID2, country=USA))
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:320)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:229)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:565)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:743)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:711)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:660)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:410)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:702)
at com.example.demo.service.ProductService$$SpringCGLIB$$0.deleteProduct(<generated>)
at com.example.demo.service.ProductServiceTest.shouldDeleteProduct(ProductServiceTest.java:107)
...
All tests on ProductServiceWithCacheTest and ProductServiceTest fails except the test where the operator is delete so it cascade delete the product (shouldDeleteProductsWithBenefitsFromOperator)
Hello, I'm migrating from hibernate 5.6.15 to hibernate 6.2 and I have noticed some issues. it seems that I can’t use a composite primary key when part of it is from a lazy non optional ManyToOne, as it gives me a error saying primary key was altered.
Entities
note
I had to use tiny ints for my enums due to https://hibernate.atlassian.net/browse/HHH-17020
Product
@Getter @IdClass(ProductPK.class) @EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString(onlyExplicitlyIncluded = true) @NoArgsConstructor(access = PROTECTED) @Entity @OptimisticLocking(type = OptimisticLockType.DIRTY) @DynamicUpdate @Cacheable @Cache(usage = READ_WRITE) @Table(name = "PRODUCTS") public class Product { public Product(String productId, Operator operator) { this.productId = productId; this.operator = operator; } public Product(String productId, Operator operator, Benefits benefits) { this.productId = productId; this.operator = operator; this.benefits = benefits; } @EqualsAndHashCode.Include @ToString.Include @Id @Column(name = "PRODUCT_ID", nullable = false) private String productId; @Id @EqualsAndHashCode.Include @ToString.Include @Getter @Setter @Cache(usage = READ_WRITE) @ManyToOne(fetch = LAZY, optional = false) @JoinColumn(name = "OPERATOR_ID", nullable = false) @JoinColumn(name = "COUNTRY", nullable = false) private Operator operator; @Column(name = "DESCRIPTION") @Setter private String description; @Embedded private Benefits benefits; @EqualsAndHashCode @ToString @Embeddable @NoArgsConstructor(access = PROTECTED) public static class ProductPK implements Serializable { private String productId; @Embedded @AttributeOverride(name = "operatorId", column = @Column(name = "OPERATOR_ID", nullable = false)) @AttributeOverride(name = "country", column = @Column(name = "COUNTRY", nullable = false)) private Operator.OperatorPK operator; public ProductPK(String productId, Operator.OperatorPK operator) { this.productId = productId; this.operator = operator; } public ProductPK(String productId, String operatorID, Country country) { this.productId = productId; this.operator = new Operator.OperatorPK(operatorID, country); } } @Embeddable @Value @AllArgsConstructor @NoArgsConstructor(access = PROTECTED) public static class Benefits { @Embedded @NonFinal @Setter TypeOneBenefit credit; @Embedded @NonFinal @Setter TypeTwoBenefit data; } @Embeddable @Value @AllArgsConstructor @NoArgsConstructor(access = PROTECTED) public static class TypeOneBenefit { @NonFinal @Column(name = "BENEFIT_ONE_BASE_AMOUNT") BigDecimal baseAmount; } @Embeddable @Value @AllArgsConstructor @NoArgsConstructor(access = PROTECTED) public static class TypeTwoBenefit { @NonFinal @Column(name = "BENEFIT_TWO_BASE_AMOUNT") String baseAmount; } }
Operator
@Getter @Entity @ToString(onlyExplicitlyIncluded = true) @EqualsAndHashCode(onlyExplicitlyIncluded = true) @NoArgsConstructor(access = PROTECTED) @Table(name = "OPERATORS") @IdClass(Operator.OperatorPK.class) @OptimisticLocking(type = OptimisticLockType.DIRTY) @DynamicUpdate @Cacheable @Cache(usage = READ_WRITE) public class Operator { @EqualsAndHashCode.Include @ToString.Include @Id @Column(name = "COUNTRY", nullable = false) private Country country; @EqualsAndHashCode.Include @ToString.Include @Id @Column(name = "OPERATOR_ID", nullable = false) private String operatorId; @ManyToOne @JoinColumn(name = "meta_operator_id", referencedColumnName = "ID") private MetaOperator metaOperator; @OneToMany(mappedBy = "operator", cascade = { PERSIST, MERGE, REMOVE }, orphanRemoval = true, fetch = FetchType.LAZY) private List<Product> products = new ArrayList<>(); public Operator(String operatorId) { this.operatorId = operatorId; this.country = USA; } public void setMetaOperator(MetaOperator metaOperator) { this.metaOperator = metaOperator; } public void setProducts(List<Product> products) { this.products = products; } @Embeddable @Value @AllArgsConstructor @NoArgsConstructor(access = PROTECTED) public static class OperatorPK implements Serializable { @NonFinal String operatorId; @NonFinal Country country; } }
Country
public enum Country { USA, FRA; }
Tests
should delete product
@Test void shouldDeleteProduct() { // Given String string = "ID2"; String operatorID = "operatorID2"; String test = "test"; Operator operator = new Operator(operatorID); operatorService.addOperator(operator); Product product = new Product(string, operator); product.setDescription(test); productService.addProduct(product); // When ProductPK productPK = new ProductPK(string, operatorID, USA); productService.deleteProduct(productPK); // Then Optional<Product> byId2 = productService.getProduct(productPK); assertThat(byId2).isEmpty(); }
an exception occurs when product is being deleted :
org.springframework.orm.jpa.JpaSystemException: identifier of an instance of com.example.demo.local.Product was altered from Product.ProductPK(productId=ID2, operator=Operator.OperatorPK(operatorId=null, country=null)) to Product.ProductPK(productId=ID2, operator=Operator.OperatorPK(operatorId=operatorID2, country=USA)) at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:320) at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:229) at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:565) at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:743) at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:711) at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:660) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:410) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:702) at com.example.demo.service.ProductService$$SpringCGLIB$$0.deleteProduct(<generated>) at com.example.demo.service.ProductServiceTest.shouldDeleteProduct(ProductServiceTest.java:107) ...
All tests on
ProductServiceWithCacheTest
andProductServiceTest
fails except the test where the operator is delete so it cascade delete the product (shouldDeleteProductsWithBenefitsFromOperator
)Sources
sources can be found in
pk_altered_when_part_is_from_lazy_association
: https://github.com/emouty/hibernate-issues/tree/pk_altered_when_part_is_from_lazy_association