Erwan Moutymbo (
https://hibernate.atlassian.net/secure/ViewProfile.jspa?accountId=640210c...
) *created* an issue
Hibernate ORM (
https://hibernate.atlassian.net/browse/HHH?atlOrigin=eyJpIjoiODJhYzU4ZTA4...
) / Bug (
https://hibernate.atlassian.net/browse/HHH-17021?atlOrigin=eyJpIjoiODJhYz...
) HHH-17021 (
https://hibernate.atlassian.net/browse/HHH-17021?atlOrigin=eyJpIjoiODJhYz...
) Composite primary key is altered when part of it is from a lazy non optional ManyToOne (
https://hibernate.atlassian.net/browse/HHH-17021?atlOrigin=eyJpIjoiODJhYz...
)
Issue Type: Bug Affects Versions: 6.3.0.CR1, 6.2.7, 6.3.0, 6.2.8 Assignee: Unassigned
Components: hibernate-core Created: 01/Aug/2023 09:20 AM Environment: Hibernate:
6.2.8-SNAPSHOT
initially found with Postgresql: 14.5
reproduced with h2 2.1.214
Spring Data JPA: 3.0.6
Spring Boot: 3.0.7
ehcache 3.10.8
JDK: OpenJDK 64-Bit Server VM Temurin-17.0.6
OS: Fedora 38 Priority: Major Reporter: Erwan Moutymbo (
https://hibernate.atlassian.net/secure/ViewProfile.jspa?accountId=640210c...
)
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 and ProductServiceTest 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_f...
(
https://hibernate.atlassian.net/browse/HHH-17021#add-comment?atlOrigin=ey...
) Add Comment (
https://hibernate.atlassian.net/browse/HHH-17021#add-comment?atlOrigin=ey...
)
Get Jira notifications on your phone! Download the Jira Cloud app for Android (
https://play.google.com/store/apps/details?id=com.atlassian.android.jira....
) or iOS (
https://itunes.apple.com/app/apple-store/id1006972087?pt=696495&ct=Em...
) This message was sent by Atlassian Jira (v1001.0.0-SNAPSHOT#100233- sha1:b4f309b )