[JIRA] (HHH-16673) Fail to get access lazy fetched field ( @ManyToOne ) wich is part of a composite Id (using an @IdClass) when stored in L2 cache
by Erwan Moutymbo (JIRA)
Erwan Moutymbo ( https://hibernate.atlassian.net/secure/ViewProfile.jspa?accountId=640210c... ) *created* an issue
Hibernate ORM ( https://hibernate.atlassian.net/browse/HHH?atlOrigin=eyJpIjoiMTFiY2U1M2Y1... ) / Bug ( https://hibernate.atlassian.net/browse/HHH-16673?atlOrigin=eyJpIjoiMTFiY2... ) HHH-16673 ( https://hibernate.atlassian.net/browse/HHH-16673?atlOrigin=eyJpIjoiMTFiY2... ) Fail to get access lazy fetched field ( @ManyToOne ) wich is part of a composite Id (using an @IdClass) when stored in L2 cache ( https://hibernate.atlassian.net/browse/HHH-16673?atlOrigin=eyJpIjoiMTFiY2... )
Issue Type: Bug Affects Versions: 6.1.7, 6.2.0, 6.2.1, 6.2.3 Assignee: Unassigned Components: hibernate-jcache Created: 23/May/2023 01:54 AM Environment: Hibernate: tried 6.1.7.Final to 6.2.3.Final
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: Oracle OpenJDK 17.0.2
OS: Fedora 38 Priority: Major Reporter: Erwan Moutymbo ( https://hibernate.atlassian.net/secure/ViewProfile.jspa?accountId=640210c... )
I'm migrating from hibernate 5.6.15 to hibernate 6.2.3 and I have noticed some issues with the L2 cache when an entity has a composite Id with one of its field mapped from a many to one association.
Entities
--------
@Getter
@IdClass(ProductPK.class)
@EqualsAndHashCode(onlyExplicitlyIncluded = true )
@ToString(onlyExplicitlyIncluded = true )
@NoArgsConstructor(access = PROTECTED)
@Entity
@OptimisticLocking(type = OptimisticLockType.ALL)
@DynamicUpdate
@Cacheable
@Cache(usage = READ_WRITE)
@Table(name = "PRODUCTS" )
public class Product {
public Product( String productId, Operator operator ) {
this.productId = productId;
this. operator = operator ;
}
@EqualsAndHashCode.Include
@ToString.Include
@Id
@Column(name = "PRODUCT_ID" , nullable = false )
private String productId;
@Id
@EqualsAndHashCode.Include
@ToString.Include
@Getter
@Setter
@ManyToOne(fetch = LAZY)
@JoinColumn
private Operator operator ;
@Column(name = "DESCRIPTION" )
@Setter
private String description;
@AllArgsConstructor
@EqualsAndHashCode
@Getter
@Setter
@NoArgsConstructor(access = PRIVATE)
public static class ProductPK implements Serializable {
private String productId;
private String operator ;
}
}
@Getter
@Entity
@ToString(onlyExplicitlyIncluded = true )
@EqualsAndHashCode(onlyExplicitlyIncluded = true )
@NoArgsConstructor(access = PROTECTED)
@Table(name = "OPERATORS" )
@OptimisticLocking(type = OptimisticLockType.DIRTY)
@DynamicUpdate
@Cacheable
@Cache(usage = READ_WRITE)
public class Operator {
public Operator( String operatorId) {
this.operatorId = operatorId;
}
@EqualsAndHashCode.Include
@ToString.Include
@Id
@Column(name = "OPERATOR_ID" , nullable = false )
private String operatorId;
@OneToMany(mappedBy = " operator " , cascade = {CascadeType.ALL}, orphanRemoval = true )
private List<Product> products = new ArrayList<>();
public void setProducts(List<Product> products) {
this.products = products;
}
}
First Test
----------
@Test
void addProductAndReadFromCacheTest() {
String string = "ID" ;
String operatorID = "operatorID" ;
ProductPK id = new ProductPK(string, operatorID);
String test = "test" ;
Operator operator = new Operator(operatorID);
operatorDao.save( operator );
Product product = new Product(string, operator );
product.setDescription(test);
productService.addProduct(product);
// getProduct has @Transactional(propagation = REQUIRES_NEW)
// First read is made from DB
Optional<Product> byId = productService.getProduct(id);
assertThat(byId.orElseThrow().getOperator().getOperatorId()).isEqualTo(operatorID);
// Second read is from cache
Optional<Product> byId2 = productService.getProduct(id);
assertThat(byId2.orElseThrow().getOperator().getOperatorId()).isEqualTo(operatorID);
}
This test throw the following exception during Optional<Product> byId2 = productService.getProduct(id); execution:
Caused by: org.hibernate.HibernateException: identifier of an instance of com.example.demo.local.Product was altered from com.example.demo.local.Product$ProductPK@226fd to com.example.demo.local.Product$ProductPK@44bf91
at org.hibernate.event.internal.DefaultFlushEntityEventListener.checkId(DefaultFlushEntityEventListener.java:93)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.getValues(DefaultFlushEntityEventListener.java:175)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:134)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:221)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:90)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:38)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1412)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:485)
at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2296)
at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:1961)
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:439)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:169)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:267)
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:101)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:561)
... 80
Second Test
-----------
@Test
void addProductAndReadFromCacheReadOnlyTest() {
String string = "ID" ;
String operatorID = "operatorID" ;
ProductPK id = new ProductPK(string, operatorID);
String test = "test" ;
Operator operator = new Operator(operatorID);
operatorDao.save( operator );
Product product = new Product(string, operator );
product.setDescription(test);
productService.addProduct(product);
// getProduct has @Transactional(propagation = REQUIRES_NEW) annotation
// First read is made from DB
Optional<Product> byId = productService.getProduct(id);
assertThat(byId.orElseThrow().getOperator().getOperatorId()).isEqualTo(operatorID);
// readProduct has @Transactional(readOnly = true ) annotation
// Second read is from cache
Optional<Product> byId2 = productService.readProduct(id);
assertThat(byId2.orElseThrow().getOperator().getOperatorId()).isEqualTo(operatorID);
}
This test raise the following exception. It seems that hibernate ignore that operator is a lazy fetched field.
java.lang.NullPointerException: Cannot invoke "com.example.demo.local.Operator.getOperatorId()" because the return value of "com.example.demo.local.Product.getOperator()" is null
( https://hibernate.atlassian.net/browse/HHH-16673#add-comment?atlOrigin=ey... ) Add Comment ( https://hibernate.atlassian.net/browse/HHH-16673#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=EmailN... ) This message was sent by Atlassian Jira (v1001.0.0-SNAPSHOT#100225- sha1:bbd69a1 )
1 year, 4 months
[JIRA] (HHH-16672) Locking entities create in the same transaction fails
by DM (JIRA)
DM ( https://hibernate.atlassian.net/secure/ViewProfile.jspa?accountId=712020%... ) *created* an issue
Hibernate ORM ( https://hibernate.atlassian.net/browse/HHH?atlOrigin=eyJpIjoiZDY3NDM5YWZj... ) / Bug ( https://hibernate.atlassian.net/browse/HHH-16672?atlOrigin=eyJpIjoiZDY3ND... ) HHH-16672 ( https://hibernate.atlassian.net/browse/HHH-16672?atlOrigin=eyJpIjoiZDY3ND... ) Locking entities create in the same transaction fails ( https://hibernate.atlassian.net/browse/HHH-16672?atlOrigin=eyJpIjoiZDY3ND... )
Issue Type: Bug Affects Versions: 5.6.15, 6.2.1 Assignee: Unassigned Created: 23/May/2023 01:08 AM Priority: Major Reporter: DM ( https://hibernate.atlassian.net/secure/ViewProfile.jspa?accountId=712020%... )
When creating and directly locking an entity, hibernate throws a org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) exception.
I guess, this is because the row does not exist yet in the table, so there is nothing to lock. However, there is also no way for another transaction to access the new entity, so it is already “locked” in a way. Thus, from my perspective, locking a freshly persisted entity should do nothing.
I have made a test to reproduce this behavior: https://github.com/mensinda/quarkus-stuff/tree/createAndLock (on the createAndLock branch).
Additionally, the createAndLockeclipseLink branch contains the same test with eclipselink. Interestingly, locking freshly created entities works here, with the exception of find(..., LockModeType.PESSIMISTIC_WRITE.
( https://hibernate.atlassian.net/browse/HHH-16672#add-comment?atlOrigin=ey... ) Add Comment ( https://hibernate.atlassian.net/browse/HHH-16672#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=EmailN... ) This message was sent by Atlassian Jira (v1001.0.0-SNAPSHOT#100225- sha1:bbd69a1 )
1 year, 4 months