Release mode After Statment with deferred result set access

Hello,

We are working in release mode = AFTER STATEMENT.
The intention is that after each statement the connection is released, and can be reused later within the same thread / transaction by directly accessing a datasource.
It worked this way with Hibernate 5.

However, as with Hibernate 6, it goes to DeferredResultSetAccess.executeQuery()
And the code there is written as such:

                try {
                        ...
			logicalConnection.getResourceRegistry().register( resultSet, preparedStatement );

		}
		catch (SQLException e) {...}
		finally {
			logicalConnection.afterStatement();
		}

But due to register(), the further afterStatement() does not release the connection:

        @Override
        public void afterStatement() {
                super.afterStatement();
 
                if ( connectionHandlingMode.getReleaseMode() == AFTER_STATEMENT ) {
                        if ( getResourceRegistry().hasRegisteredResources() ) {
                                log.debug( "Skipping aggressive release of JDBC Connection after-statement due to held resources" );
                        }
                        else {
                                log.debug( "Initiating JDBC connection release from afterStatement" );
                                releaseConnection();
                        }
                }
        }

How is this supposed to work then? Am i missing anything?
Many thanks for any assistence.

(Meanwhile I found a workaround, by hacking the connection out of the Hibernate session; but this is indeed ugly).

Hi there, thanks for reporting this problem. Could you please try to create a reproducer for this problem with our test case template and if you are able to reproduce the issue, create a bug ticket in our issue tracker and attach that reproducer.

The reproducer is attached.
The idea of the test is as follows: Since both connection requests come from the same thread, Atomikos is expected to bring the same connection, unless it is currently in use (regardless of being in transaction). Closing a connection does not close the transaction, it only returns the connection to the pool. Connection pool max size is set to 1, this way we ensure we consume only one connection. As long as a connection is closed after statement, it works OK. The problematic parts is find(), since it does not close the connection. Thus, the next connection request fails, and the test fails. The test works OK on Hibernate 5.

Please check this is wirtten in a proper form, and you can proceed with this (before the formal call is open). Thanks.

package hibernateTest;
 
import static org.junit.Assert.assertEquals;
 
import java.sql.Connection;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
 
import org.h2.jdbcx.JdbcDataSource;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
 
import com.atomikos.jdbc.AtomikosDataSourceBean;
 
import jakarta.persistence.Entity;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Id;
import jakarta.persistence.Persistence;
import jakarta.persistence.Table;
 
/**
* This template demonstrates how to develop a test case for Hibernate ORM, using the Java Persistence API.
*/
public class HibernateTest {
 
       private EntityManagerFactory entityManagerFactory;
       private AtomikosDataSourceBean dataSource;
 
       @Before
       public void init() {
              
              JdbcDataSource ds = new JdbcDataSource();
              ds.setURL("jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1");
              ds.setUser("sa");
              dataSource = new AtomikosDataSourceBean();
              dataSource.setXaDataSource(ds);
              dataSource.setDefaultIsolationLevel(Connection.TRANSACTION_READ_COMMITTED);
              dataSource.setUniqueResourceName("resourceName");
              dataSource.setMaxPoolSize(1);
              
              Map<String, Object> properties = new HashMap<>(); 
              properties.put(AvailableSettings.DATASOURCE, dataSource);
              properties.put(AvailableSettings.CONNECTION_HANDLING, PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT);
              entityManagerFactory = Persistence.createEntityManagerFactory( "templatePU", properties);
       }
 
       @After
       public void destroy() {
              entityManagerFactory.close();
       }
 
       // Entities are auto-discovered, so just add them anywhere on class-path
       // Add your tests, using standard JUnit.
       @Test
       public void hhh123Test() throws Exception {
              EntityManager entityManager = entityManagerFactory.createEntityManager();
              entityManager.getTransaction().begin();
              A entity = new A();
              entity.setX(0);
              entity.setY(0);
              entityManager.persist(entity);
              entityManager.flush();
              entityManager.find(A.class, 1);
              
              try (Connection connection = dataSource.getConnection();
                           Statement statement = connection.createStatement();
                           ) {
                     statement.execute("UPDATE A SET Y = 1 WHERE X = 0");
                     assertEquals(1, statement.getUpdateCount());
              }
              
              
              entityManager.getTransaction().commit();
              entityManager.close();
       }
       
       @Entity
       @Table(name = "A")
       public static class A {
              @Id
              private Integer x;
              private Integer y;
 
              public Integer getX() {
                     return x;
              }
 
              public void setX(Integer x) {
                     this.x = x;
              }
 
              public Integer getY() {
                     return y;
              }
 
              public void setY(Integer y) {
                     this.y = y;
              }
              
       }
       
       
 
 
       
}
 

Any news on this? Should I open a formal call?

Yes, please create a Jira issue in the issue tracker and we will look into this as soon as we find time.

https://hibernate.atlassian.net/browse/HHH-17887
Done.

1 Like

I managed to create a simpler test, without use of Atomikos for testing:

package hibernateTest;
 
import static org.junit.Assert.assertFalse;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
 
import javax.sql.DataSource;
 
import org.h2.jdbcx.JdbcDataSource;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
 
import jakarta.persistence.Entity;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Id;
import jakarta.persistence.Persistence;
import jakarta.persistence.Table;
 
/**
* This template demonstrates how to develop a test case for Hibernate ORM, using the Java Persistence API.
*/
public class HibernateTest {
 
       private EntityManagerFactory entityManagerFactory;
       private DataSource dataSource;
       
       private boolean connectionInUse = false;
       
       private void testAndSet() {
              assertFalse(connectionInUse);
              connectionInUse = true;
       }
 
       @Before
       public void init() {
              
              JdbcDataSource ds = new JdbcDataSource();
              ds.setURL("jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1");
              ds.setUser("sa");
              
              dataSource = (DataSource) Proxy.newProxyInstance(DataSource.class.getClassLoader(), 
                           new Class<?>[] {DataSource.class}, new InvocationHandler() {
                                  @Override
                                  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                                         Object result = method.invoke(ds, args);
                                         if ("getConnection".equals(method.getName())) {
                                                testAndSet();
                                                Connection connection = (Connection)result;
                                                return Proxy.newProxyInstance(DataSource.class.getClassLoader(), 
                                                              new Class<?>[] {Connection.class}, new InvocationHandler() {
                                                                     @Override
                                                                     public Object invoke(Object proxy, Method method, Object[] args)
                                                                                  throws Throwable {
                                                                           if ("close".equals(method.getName())) {
                                                                                  connectionInUse = false;
                                                                           }
                                                                           return method.invoke(connection, args);
                                                                     }
                                                                     
                                                              });
                                         }
                                         return result;
                                  }
                                  
                           });
 
              Map<String, Object> properties = new HashMap<>(); 
              properties.put(AvailableSettings.DATASOURCE, dataSource);
              properties.put(AvailableSettings.CONNECTION_HANDLING, PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT);
              entityManagerFactory = Persistence.createEntityManagerFactory( "templatePU", properties);
       }
 
       @After
       public void destroy() {
              entityManagerFactory.close();
       }
 
       // Entities are auto-discovered, so just add them anywhere on class-path
       // Add your tests, using standard JUnit.
       @Test
       public void hhh123Test() throws Exception {
              EntityManager entityManager = entityManagerFactory.createEntityManager();
              entityManager.getTransaction().begin();
              entityManager.find(A.class, 1);
              
              try (Connection connection = dataSource.getConnection();
                           Statement statement = connection.createStatement();
                           ) {
                     statement.execute("UPDATE A SET Y = 1 WHERE X = 0");
              }
              
              
              entityManager.getTransaction().commit();
              entityManager.close();
       }
       
       @Entity
       @Table(name = "A")
       public static class A {
              @Id
              private Integer x;
              private Integer y;
 
              public Integer getX() {
                     return x;
              }
 
              public void setX(Integer x) {
                     this.x = x;
              }
 
              public Integer getY() {
                     return y;
              }
 
              public void setY(Integer y) {
                     this.y = y;
              }
              
       }
       
}
 

Any news on this?
Any updates?

If anyone has time to work on this, you will receive a notification from Jira. Since you created the issue, you are registered as watcher, so any changes that happen will cause e.g. an email notification.

I see it was fixed, many thanks.
When is the version expected to be released?
Is there anyway to receive any version (not necessary the official release) to check it resolves our problem?
Thanks.

You can use the OSS Sonatype snapshots repository to try out 6.4.5-SNAPSHOT.

<repositories>
    <repository>
        <id>oss.sonatype.org-snapshot</id>
        <url>http://oss.sonatype.org/content/repositories/snapshots</url>
        <releases><enabled>false</enabled></releases>
        <snapshots><enabled>true</enabled></snapshots>
    </repository>
</repositories>

Thanks.
When is the fix expected to be available in a formal version?
Thanks.

Probably around next week.

Thank you very much for your handling of the case.

It indeed solved the original problem.

However, version 6.5.0.CR2 where it was fixed introduced many other critical problems.

I understand the version is not an official release.

However, the gap between 6.2.13 that we currently have and this version is very large.

Could you please apply the fix to version 6.2.x?

Thanks so much.

@avraham.nissimov version 6.2 is only receiving limited support, the issue was fixed in version 6.4.5 as well so I suggest upgrading as soon as possible.

version 6.5 .0.CR2 where it was fixed introduced many other critical problems

Could you please expand on this? If you found problems with the new version please also report them with an attached reproducer in our issue tracker and we’ll look into them.