[JIRA] (HHH-16827) Avoid useless lockings in NaturalId second level cache.
by Guenther Demetz (JIRA)
Guenther Demetz ( https://hibernate.atlassian.net/secure/ViewProfile.jspa?accountId=557058%... ) *created* an issue
Hibernate ORM ( https://hibernate.atlassian.net/browse/HHH?atlOrigin=eyJpIjoiMmQzZTM1NjI1... ) / Improvement ( https://hibernate.atlassian.net/browse/HHH-16827?atlOrigin=eyJpIjoiMmQzZT... ) HHH-16827 ( https://hibernate.atlassian.net/browse/HHH-16827?atlOrigin=eyJpIjoiMmQzZT... ) Avoid useless lockings in NaturalId second level cache. ( https://hibernate.atlassian.net/browse/HHH-16827?atlOrigin=eyJpIjoiMmQzZT... )
Issue Type: Improvement Affects Versions: 5.6.15, 6.2.5 Assignee: Unassigned Created: 20/Jun/2023 06:58 AM Environment: Hibernate 6.2, Java17, Windows10, SQLServer Priority: Minor Reporter: Guenther Demetz ( https://hibernate.atlassian.net/secure/ViewProfile.jspa?accountId=557058%... )
When populating second-level-cache by concurrent threads and several threads read the same entities annotated with it comes to a locking-nightmare. This scenario is described in ticket https://hibernate.atlassian.net/browse/HHH-16726.
One of the reasons for the bad performance, is that when verifying if the shared cache already contains the key for the natural-id resolution, the current code calls
if ( CacheHelper.fromSharedCache( s, cacheKey, cacheAccess ) != null ) {
in order to prevent identical re-cachings (NaturalIdResolutionsImpl.java line 251)
This is suboptimal and an overshoot for multiple reasons:
* fromSharedCache call does not only verify if the shared cache contains the cacheKey,
it also tests if the item is readable by comparing session timestamps.
If the item is found but considered ‘unreadable', then identical re-caching isn’t prevented.
Even more bad: the re-caching attempted is done in vain: NaturaId items aren’t versioned so existing entries get never overriden.
public boolean isWriteable(long txTimestamp, Object newVersion, Comparator versionComparator) {
...
return version != null && versionComparator.compare( version, newVersion ) < 0;
2. fromSharedCache call does return the item if found and 'readable', but we just need the information if the key is cached or not
3. fromSharedCache call does produce unnecessary read-locks in NaturalId-region and as conseguence of the useless re-caching attempts (point 1) also useless write-locks are done with putFromLoad
All this contributes to a huge amount of useless locks in NaturalId cache when it’s get populated.
It can avoided by simply calling cacheAccess.contains(cacheKey), see linked PR.
( https://hibernate.atlassian.net/browse/HHH-16827#add-comment?atlOrigin=ey... ) Add Comment ( https://hibernate.atlassian.net/browse/HHH-16827#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#100227- sha1:8ffa416 )
1 year, 4 months
[JIRA] (HHH-16826) IN-Clause Parameter Padding should grow exponentially for Dialects with InExpressionCountLimit
by Adrodoc (JIRA)
Adrodoc ( https://hibernate.atlassian.net/secure/ViewProfile.jspa?accountId=5b2a4a0... ) *created* an issue
Hibernate ORM ( https://hibernate.atlassian.net/browse/HHH?atlOrigin=eyJpIjoiMWQ5OTNkN2M4... ) / Improvement ( https://hibernate.atlassian.net/browse/HHH-16826?atlOrigin=eyJpIjoiMWQ5OT... ) HHH-16826 ( https://hibernate.atlassian.net/browse/HHH-16826?atlOrigin=eyJpIjoiMWQ5OT... ) IN-Clause Parameter Padding should grow exponentially for Dialects with InExpressionCountLimit ( https://hibernate.atlassian.net/browse/HHH-16826?atlOrigin=eyJpIjoiMWQ5OT... )
Issue Type: Improvement Affects Versions: 6.2.5 Assignee: Unassigned Created: 20/Jun/2023 06:39 AM Priority: Major Reporter: Adrodoc ( https://hibernate.atlassian.net/secure/ViewProfile.jspa?accountId=5b2a4a0... )
In-clause padding (see hibernate.query.in_clause_parameter_padding in https://docs.jboss.org/hibernate/orm/6.2/userguide/html_single/Hibernate_... ) pads the number of bind variables used for an in-clause to the next power of 2 (1, 2, 4, 8, …). This exponential growth of bind variables makes for a good utilisation of the database execution plan cache for databases that have such a cache (such as Oracle-SQL).
For databases that also have a limit to the number of in-clause parameters (see Dialect.getInExpressionCountLimit , this is 1000 for OracleDialect ) the padding is only applied to the remainder of dividing the number of parameters by the limit. For example an in-clause with 4001 parameters is split into 5 in-clauses with 4*1000 and 1*1 bind variables, an in-clause with 4002 parameters is split into 5 in-clauses with 4*1000 and 1*2 bind variables and an in-clause with 4003 parameters is split into 5 in-clauses with 4*1000 and 1*4 bind variables. This results in 3 different queries, which means the execution plan cache can not be used. For databases that don’t have an in-clause limit all three examples would pad to the same query with 2^12=4096 bind variables and would use the same execution plan from cache.
Instead of padding the remainder, a better approach would be to use a normal power of two padding up to the in-clause limit and then continue by padding to a power of two times the in-clause limit. For a limit of 1000 this would mean that 1001 parameters would result in a query with 2^1*1000=2000 bind variables and 4001 parameters would result in a query with 2^3*1000=8000 bind variables.
In the current approach the number of bind variables grows linearly with the number of parameters which requires a linear amount of execution plan cache size, whereas in the proposed algorithm the number of bind variables grows exponentially with the number of parameters which requires only a logarithmic amount of execution plan cache size, just like when there no in-clause limit in play.
The following table compares the number of bind variables used in the three approaches for an in-clause limit of 1000:
*current* *proposal* *next power of two (no limit)* 1 1 1 2 2 2 4 4 4 8 8 8 16 16 16 32 32 32 64 64 64 128 128 128 256 256 256 512 512 512 1000 1000 1024 1001 2000 2048 1002 4000 4096 1004 8000 8192 1008 16000 16384 1016 32000 32768 1032 64000 65536
It is a bad idea to use a normal power of two padding for dialects with an in-clause limit, because this could cause unneccessary and/or-conjunctions for less than 2*limit parameters. For example 900 parameters should not be padded to 1024 bind variables, because the execution plan is likely better for a query with only one in-clause compared to a query with multiple in-clauses. The proposal avoids unnecessary conjunctions as much as possible while still only requiring a logarithmic amount of execution plan cache size.
This approach was already implemented in https://github.com/hibernate/hibernate-orm/pull/6555 , along with several bug fixes, but was seperated out into this issue as it is a change in behaviour.
( https://hibernate.atlassian.net/browse/HHH-16826#add-comment?atlOrigin=ey... ) Add Comment ( https://hibernate.atlassian.net/browse/HHH-16826#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#100227- sha1:8ffa416 )
1 year, 4 months
[JIRA] (HHH-16825) Cascading an entity with a composite key causes NullPointerException in AbstractClassJavaType.extractHashCode
by Tomáš Müller (JIRA)
Tomáš Müller ( https://hibernate.atlassian.net/secure/ViewProfile.jspa?accountId=5b50823... ) *created* an issue
Hibernate ORM ( https://hibernate.atlassian.net/browse/HHH?atlOrigin=eyJpIjoiYWE5ZGY3MDU5... ) / Bug ( https://hibernate.atlassian.net/browse/HHH-16825?atlOrigin=eyJpIjoiYWE5ZG... ) HHH-16825 ( https://hibernate.atlassian.net/browse/HHH-16825?atlOrigin=eyJpIjoiYWE5ZG... ) Cascading an entity with a composite key causes NullPointerException in AbstractClassJavaType.extractHashCode ( https://hibernate.atlassian.net/browse/HHH-16825?atlOrigin=eyJpIjoiYWE5ZG... )
Issue Type: Bug Affects Versions: 6.2.5 Assignee: Unassigned Attachments: Bottom.java, HashTest.java, Middle.java, Top.java Components: hibernate-core Created: 20/Jun/2023 06:14 AM Environment: Java 11 using MySQL 8 or Oracle 18 Priority: Major Reporter: Tomáš Müller ( https://hibernate.atlassian.net/secure/ViewProfile.jspa?accountId=5b50823... )
I am in the process of migrating an application from Hibernate 4.3 to Hibernate 6.2. I have noticed something that works differently, causing a NullPointerException when there are two levels of one-to-many nesting with the bottom entity containing a composite id.
That is when the composite id contains a relation that is being persisted together with the entity it refers to.
Here is our example (simplified). Imagine having a Top entity that has a one-to-many relation to a Middle entity that has a one-to-many relation to a Bottom entity with a composite key (including id of the middle entity):
Top entity:
@Entity
@Table(name = "top")
public class Top {
private UUID id;
private String name;
private Set<Middle> middles;
@Id
@GeneratedValue
@Column(name = "id")
public UUID getId() { return id; }
public void setId(UUID id) { this.id = id; }
@Column(name = "name")
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@OneToMany(mappedBy = "top", cascade = { CascadeType.ALL })
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public Set<Middle> getMiddles() { return middles; }
public void setMiddles(Set<Middle> middles) { this.middles = middles; }
public void addMiddle(Middle middle) {
if (middles == null) middles = new HashSet<Middle>();
middles.add(middle);
}
}
Middle entity:
@Entity
@Table(name = "middle")
public class Middle {
private UUID id;
private Top top;
private Set<Bottom> bottoms;
@Id
@GeneratedValue
@Column(name = "id")
public UUID getId() { return id; }
public void setId(UUID id) { this.id = id; }
@ManyToOne(optional = false)
@JoinColumn(name = "top_id", nullable = false)
public Top getTop() { return top; }
public void setTop(Top student) { this.top = student; }
@OneToMany(mappedBy = "middle", cascade = {CascadeType.ALL})
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public Set<Bottom> getBottoms() { return bottoms; }
public void setBottoms(Set<Bottom> bottoms) { this.bottoms = bottoms; }
public void addBottom(Bottom bottom) {
if (bottoms == null) bottoms = new HashSet<Bottom>();
bottoms.add(bottom);
}
}
Bottom entity:
@Entity
@Table(name = "bottom")
public class Bottom {
private Middle middle;
private Integer type;
private String note;
@Id
@ManyToOne(optional = false)
@JoinColumn(name = "middle_id", nullable = false)
public Middle getMiddle() { return middle; }
public void setMiddle(Middle middle) { this.middle = middle; }
@Id
@Column(name = "type")
public Integer getType() { return type; }
public void setType(Integer type) { this.type = type; }
@Column(name = "note", nullable = true, length = 2048)
public String getNote() { return note; }
public void setNote(String note) { this.note = note; }
}
Now, imagine that we want to update the top entity by adding a middle entity with a bottom entity to it. Like this:
final Session hibSession1 = sf.openSession();
Transaction t1 = hibSession1.beginTransaction();
// Lookup a top entity
Top top = hibSession1.createQuery("from Top where name = :name", Top.class).setParameter("name", "Top 1").uniqueResult();
System.out.println("Top: " + top.getId() + ", " + top.getName());
// Add one middle entity with a single bottom entity to the top entity
Middle m1 = new Middle();
m1.setTop(top); top.addMiddle(m1);
Bottom b1 = new Bottom();
b1.setMiddle(m1); m1.addBottom(b1); b1.setType(0); b1.setNote("Bottom 1");
// update the top entity
hibSession1.merge(top);
t1.commit();
hibSession1.close();
However, this fails with the following exception:
java.lang.NullPointerException
at org.hibernate.type.descriptor.java.AbstractClassJavaType.extractHashCode(AbstractClassJavaType.java:93)
at org.hibernate.type.AbstractStandardBasicType.getHashCode(AbstractStandardBasicType.java:216)
at org.hibernate.type.AbstractStandardBasicType.getHashCode(AbstractStandardBasicType.java:225)
at org.hibernate.type.EntityType.getHashCode(EntityType.java:362)
at org.hibernate.type.ComponentType.getHashCode(ComponentType.java:270)
at org.hibernate.engine.spi.EntityKey.generateHashCode(EntityKey.java:60)
at org.hibernate.engine.spi.EntityKey.<init>(EntityKey.java:53)
at org.hibernate.internal.AbstractSharedSessionContract.generateEntityKey(AbstractSharedSessionContract.java:618)
at org.hibernate.event.internal.DefaultMergeEventListener.entityState(DefaultMergeEventListener.java:189)
at org.hibernate.event.internal.DefaultMergeEventListener.merge(DefaultMergeEventListener.java:147)
at org.hibernate.event.internal.DefaultMergeEventListener.doMerge(DefaultMergeEventListener.java:142)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:126)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:138)
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:869)
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:840)
at org.hibernate.engine.spi.CascadingActions$6.cascade(CascadingActions.java:253)
at org.hibernate.engine.spi.CascadingActions$6.cascade(CascadingActions.java:243)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:513)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:434)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:220)
at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:547)
at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:477)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:437)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:220)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:153)
at org.hibernate.event.internal.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:490)
at org.hibernate.event.internal.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:239)
at org.hibernate.event.internal.DefaultMergeEventListener.merge(DefaultMergeEventListener.java:152)
at org.hibernate.event.internal.DefaultMergeEventListener.doMerge(DefaultMergeEventListener.java:142)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:126)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:138)
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:869)
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:840)
at org.hibernate.engine.spi.CascadingActions$6.cascade(CascadingActions.java:253)
at org.hibernate.engine.spi.CascadingActions$6.cascade(CascadingActions.java:243)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:513)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:434)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:220)
at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:547)
at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:477)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:437)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:220)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:153)
at org.hibernate.event.internal.DefaultMergeEventListener.cascadeOnMerge(DefaultMergeEventListener.java:570)
at org.hibernate.event.internal.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:212)
at org.hibernate.event.internal.DefaultMergeEventListener.merge(DefaultMergeEventListener.java:155)
at org.hibernate.event.internal.DefaultMergeEventListener.doMerge(DefaultMergeEventListener.java:142)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:126)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:80)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127)
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:848)
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:834)
at org.hibernate.bugs.hash.HashTest.testMerge(HashTest.java:52)
Everything works fine when
* the top entity is being created (with the middle and bottom entities) instead (calling persist instead of merge)
* when the middle entities are persisted before (or instead of) merging the top entity
* when the bottom entity has a generated id instead of a reference to the middle entity
See the attached files for the whole test.
( https://hibernate.atlassian.net/browse/HHH-16825#add-comment?atlOrigin=ey... ) Add Comment ( https://hibernate.atlassian.net/browse/HHH-16825#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#100227- sha1:8ffa416 )
1 year, 4 months
[JIRA] (HHH-16745) TransientObjectException when loading versioned entity from second-level cache with @OneToOne LAZY mapping
by Koen De Jaeger (JIRA)
Koen De Jaeger ( https://hibernate.atlassian.net/secure/ViewProfile.jspa?accountId=557058%... ) *commented* on HHH-16745 ( https://hibernate.atlassian.net/browse/HHH-16745?atlOrigin=eyJpIjoiMTllYT... )
Re: TransientObjectException when loading versioned entity from second-level cache with @OneToOne LAZY mapping ( https://hibernate.atlassian.net/browse/HHH-16745?atlOrigin=eyJpIjoiMTllYT... )
Hello, I have the same stacktrace when fetching data, after upgrading to spring boot 3.1.x. I tried now with spring boot 3.1.1-SNAPSHOT and override hibernate to version 6.2.5.Final but still go the same error. When I remove @Version , the stacktrace dissapears.
@Entity
@Table(name = "users")
@JsonIgnoreProperties(ignoreUnknown = true)
public class User implements Serializable {
@Id
@SequenceGenerator(name = "user_seq", sequenceName = "user_seq", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_seq")
@JsonIgnore
private Long id;
@Column(updatable = false, insertable = false, columnDefinition = "varchar")
@Generated(event = EventType.INSERT, sql = "uuid_generate_v4()")
@JsonProperty("id")
@JsonView({Views.UserListManagement.class})
private String uuid;
@Version
@JsonView({Views.UserListManagement.class})
private Date modified;
@JsonIgnore
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "saved_by")
private User savedBy;
...
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
@JsonView({Views.UserDetail.class})
public String getSavedByUsername() {
if (savedBy != null && savedBy.getUsername() != null) {
return savedBy.getUsername().replace("@vluchtelingenwerk.nl", "");
} else {
return null;
}
}
public void setSavedBy(User savedBy) {
this.savedBy = savedBy;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return id.equals(user.id);
}
}
( https://hibernate.atlassian.net/browse/HHH-16745#add-comment?atlOrigin=ey... ) Add Comment ( https://hibernate.atlassian.net/browse/HHH-16745#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#100227- sha1:8ffa416 )
1 year, 4 months