Skip to content

Conversation

sebersole
Copy link
Member

@sebersole sebersole commented Aug 13, 2025

HHH-19602 - Adjust JdbcOperation to allow more-than-one statement


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license
and can be relicensed under the terms of the LGPL v2.1 license in the future at the maintainers' discretion.
For more information on licensing, please check here.


https://hibernate.atlassian.net/browse/HHH-19602

* @param jdbcConnection The JDBC Connection.
* @param executionContext Access to contextual information useful while executing.
*/
void performPostAction(StatementAccess jdbcStatementAccess, Connection jdbcConnection, ExecutionContext executionContext);

Check notice

Code scanning / CodeQL

Useless parameter Note

The parameter 'jdbcStatementAccess' is never used.
* @param jdbcConnection The JDBC Connection.
* @param executionContext Access to contextual information useful while executing.
*/
void performPreAction(StatementAccess jdbcStatementAccess, Connection jdbcConnection, ExecutionContext executionContext);

Check notice

Code scanning / CodeQL

Useless parameter Note

The parameter 'jdbcStatementAccess' is never used.
@beikov
Copy link
Member

beikov commented Aug 25, 2025

LGTM

@sebersole
Copy link
Member Author

sebersole commented Sep 10, 2025

In this last commit I have a generally working solution for simple cases, where I think the approach is one that is feasible for a first pass. It has some limitations currently -

  • it makes some assumptions about the type of query wrt the things to lock
  • does not yet work for locking entity tables with composite keys
  • does not yet lock actual collection tables. in case of collections it will (currently) only lock the associations if requested (EXTENDED)

It also requires some changes to how JdbcSelectExecutor and especically JdbcValuesSourceProcessingState operate. We could mitigate this by instead putting the new LoadedValuesCollector reference on ExecutionContext (similar to Callback/AfterLoadAction), but that has always felt fugly.

Anyway, see FollowOnLockingParadigmTests#testFollowOnLockingFlow for the flow.

Anyway, what happens is that we issue the first selection (without locking) and load results as mormal. As part of that loading, LoadedValuesCollector is used to collect things that need to be locked. A post-action then is used to leverage the LoadedValuesCollector values and issue locking selects against each table, ultimately refreshing the state of the entity (and its loaded-state). So for example, from the test we have the primary operation, the select without locking -

    select
        t1_0.id,
        m1_0.team_fk,
        m1_0.id,
        m1_0.name,
        t1_0.name 
    from
        teams t1_0 
    left join
        persons m1_0 
            on t1_0.id=m1_0.team_fk 
    where
        t1_0.id=?

Which yields:

Loaded root entities:
    org.hibernate.orm.test.sql.exec.op.Team#1
Loaded non-root entities:
    org.hibernate.orm.test.sql.exec.op.Person#1
    org.hibernate.orm.test.sql.exec.op.Person#2
    org.hibernate.orm.test.sql.exec.op.Person#3
Loaded collections:
    org.hibernate.orm.test.sql.exec.op.Team.members#1

And then 2 additional queries -

    select
        tbl.id,
        tbl.name 
    from
        teams tbl 
    where
        tbl.id in (?) 
    for
        update

    select
        tbl.id,
        tbl.name 
    from
        persons tbl 
    where
        tbl.id in (?, ?, ?) 
    for
        update

As the results of these locking queries are processed, we update the "loaded state" and instance state for the entities.

BiConsumer<Integer, PreparedStatement> expectationCheck,
ExecutionContext executionContext) {
final JdbcMutationExecutor jdbcMutationExecutor = executionContext.getSession().getJdbcServices().getJdbcMutationExecutor();
return jdbcMutationExecutor.execute(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing performPreActions() and performPostActions()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just focusing on selections for now. And in fact, I'm folding all this into JdbcOperation itself locally. These DatabaseOPeration contracts are going away. (from the Zulip discussion)

But larger picture, implementing this generically for mutations is going to be a pain because of the difference in how mutations work between queries and model mutuations.

@sebersole
Copy link
Member Author

A few notes:

  1. I changed how locking is applied to native queries.
  2. These last commits do add support for allowing post-actions to run after unsuccessful primary operations.
  3. These last commits also include refactoring to org.hibernate.sql.exec which I mentioned on Zulip.

@sebersole sebersole marked this pull request as draft September 16, 2025 19:02
}

default RootGraphImplementor createRootGraph(SharedSessionContractImplementor session) {
if ( getRepresentationStrategy() instanceof EntityRepresentationStrategyMap mapRep ) {

Check notice

Code scanning / CodeQL

Unread local variable Note

Variable 'EntityRepresentationStrategyMap mapRep' is never read.
final Map<Object, EntityDetails> entityDetailsMap = resolveEntityKeys( entityKeys, executionContext );

entityMappingType.forEachAttributeMapping( (index, attributeMapping) -> {
if ( attributeMapping instanceof PluralAttributeMapping pluralAttributeMapping ) {

Check notice

Code scanning / CodeQL

Unread local variable Note

Variable 'PluralAttributeMapping pluralAttributeMapping' is never read.
@sebersole
Copy link
Member Author

One thing I do still want to look into is:

// todo (JdbcOperation) : Consider leveraging approach based on Dialect#useArrayForMultiValuedParameters

Also need to be sensitive to the number of keys to load and the number of supported parameters when using an in-list predicate

Comment on lines +235 to +240
List<Product> products = session.createQuery( "select p from Product p left join p.vehicle v on v.id is null", Product.class )
.setHibernateLockMode( PESSIMISTIC_WRITE )
.setFollowOnStrategy( Locking.FollowOn.DISALLOW )
.setFirstResult( 40 )
.setMaxResults( 10 )
.getResultList();

Check notice

Code scanning / CodeQL

Unread local variable Note test

Variable 'List products' is never read.
Comment on lines +517 to +520
List<Vehicle> vehicles = session.createQuery( "select v from Vehicle v", Vehicle.class )
.setHibernateLockMode( PESSIMISTIC_WRITE )
.setFollowOnStrategy( Locking.FollowOn.DISALLOW )
.getResultList();

Check notice

Code scanning / CodeQL

Unread local variable Note test

Variable 'List vehicles' is never read.
# Conflicts:
#	hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DeferredResultSetAccess.java

# Conflicts:
#	hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DeferredResultSetAccess.java
HHH-19782 - Oracle support for locking across joins
HHH-19782 - Oracle support for locking across joins
@sebersole
Copy link
Member Author

Rebased and applied manually

@sebersole sebersole closed this Sep 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants