I am upgrading a Spring Boot project from 2.7 to 3.1.5, which involves upgrading Hibernate 5 to 6.2. This has totally broken my schema-based multitenancy setup. I am using Postgres 12 along with Spring Data JPA, with various entity classes and JpaRepository interfaces. I test with Testcontainers.
My schema-based multitenancy setup uses implementations of MultiTenantConnectionProvider
and CurrentTenantIdentifierResolver
, which are @Component
Beans. After the upgrade, I am only able to get tests involving a repository and Testcontainers to pass if I do not register the connection provider with Hibernate properties (when running in isolation); however, that makes it impossible for the project to build (with Maven).
In Spring Boot 2.7, I had to make both my multitenancy components implement HibernatePropertiesCustomizer
and register themselves with Hibernate properties through AvailableSettings.MULTI_TENANT_ ...
(and I'm aware that the MULTI_TENANT
type property itself was removed in Hibernate 6). This was the only way I was able to get the app to recognise the components for multitenancy. In Spring Boot 3.1.5 with Hibernate 6.2, the TenantIdentifierResolver
registers itself fine - it's only the connection provider that causes tests to fail. But if I don't register it, my app can't build at all, and I have no multitenancy setup.
The error when I register the connection provider:
org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction
at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:466)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.startTransaction(AbstractPlatformTransactionManager.java:400)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:373)
at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:601)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:385)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:164)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:244)
at jdk.proxy2/jdk.proxy2.$Proxy228.save(Unknown Source)
at org.fake.pckge.name.TestContainerTests.saveEntityToRepository(TestContainerTests.java:36)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: org.hibernate.TransactionException: JDBC begin transaction failed:
at org.hibernate.resource.jdbc.internal.AbstractLogicalConnectionImplementor.begin(AbstractLogicalConnectionImplementor.java:78)
at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.begin(LogicalConnectionManagedImpl.java:282)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.begin(JdbcResourceLocalTransactionCoordinatorImpl.java:232)
at org.hibernate.engine.transaction.internal.TransactionImpl.begin(TransactionImpl.java:83)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.beginTransaction(HibernateJpaDialect.java:164)
at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:420)
... 85 more
Caused by: java.sql.SQLException: Connection is closed
I have tried registering the connection provider in other ways, such as in a HibernateConfig
Bean, or in MultiTenancyJpaConfiguration
as in the suggestion from this post, as well as in application properties. Wherever I register it, I have the same issue. I am wondering if it’s a chicken-and-egg situation, where the instantiation of the Beans is too slow for the connection to be established with the right schema. One other piece of the puzzle is that my TenantBasedConnectionProvider
relies on a custom @Configuration
Bean which loads some properties from the application YAML, such as the names of schemata to use. However, like I say, this worked fine in Spring Boot 2.7 and should be possible, given Hibernate properties are also set in the application YAML.
What can I do?
I have solved it. It was a red herring. While debugging I had introduced the @Container
annotation in my PostgresIntegrationTest
interface that all Testcontainer tests implement. This annotation closes the Testcontainer after the first test runs. You cannot use @Container
or @ServiceConnection
- you have to use @DynamicPropertyRegistry
and call container.start()
within the method
Another issue was that in MultiTenantConnectionProvider.getConnection
I had put the connection inside a try block’s parenthesis, which meant the connection would get closed automatically. You have to leave it open and allow Hibernate to manage it