-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Description
I have configured Hibernate's native multitenancy in the attached persistence-tenant.txt.
As you can see:
<property name="hibernate.multiTenancy" value="DATABASE" /> <property name="hibernate.multi_tenant_connection_provider" value="mu.maccs.ffa.dao.spi.MultiTenantConnectionProviderImpl" /> <property name="hibernate.tenant_identifier_resolver" value="mu.maccs.ffa.dao.spi.MultiTenantIdentifierResolverImpl" />
I am using a simple JPARepository class as attached.
The problem is that on bootstrapping, org.springframework.data.jpa.repository.support.JpaRepositoryFactory's constructor makes the following call:
this.extractor = PersistenceProvider.fromEntityManager(entityManager);
which actually creates a hibernate sessionImpl to determine the PersistenceProvider to use in the JPARepository.
This is wrong because since my application is not executing any database call on boot, a Hibernate session creation is FORCED (which in turns creates a connection with the underlying datasource for that particular tenant). This line is the culprit:
Class<?> entityManagerType = em.getDelegate().getClass();
Because of this, I am forced to set a default tenantIdentifier in MultiTenantIdentifierResolverImpl which isn't right. There should be NO tenant resolution on creation of JpaRepositoryFactory.
persistence-tenant.txt
UserRepository.txt
MultiTenantConnectionProviderImpl.txt
MultiTenantIdentifierResolverImpl.txt
Log Trace:
2024-04-13 00:19:20,870 [main] TRACE org.springframework.beans.factory.support.DefaultListableBeanFactory.invokeInitMethods:1831 - Invoking afterPropertiesSet() on bean with name 'userRepository'
2024-04-13 00:19:20,884 [main] TRACE org.springframework.core.io.support.SpringFactoriesLoader.load:202 - Loaded [org.springframework.data.util.CustomCollectionRegistrar] names: [org.springframework.data.util.CustomCollections.VavrCollections, org.springframework.data.util.CustomCollections.EclipseCollections]
2024-04-13 00:19:37,009 [main] DEBUG org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke:310 - Creating new EntityManager for shared EntityManager invocation
2024-04-13 00:19:37,183 [main] TRACE org.hibernate.internal.SessionFactoryImpl$SessionBuilderImpl.openSession:1363 - Opening Hibernate Session. tenant=root
2024-04-13 00:19:39,034 [main] TRACE org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService:234 - Initializing service [role=org.hibernate.stat.spi.StatisticsImplementor]
2024-04-13 00:19:39,446 [main] TRACE org.hibernate.internal.SessionImpl.<init>:272 - Opened Session [23989d6b-324a-4a3d-b4f2-3a358f5ff536] at timestamp: 1712953179446
2024-04-13 00:19:41,112 [main] TRACE org.hibernate.internal.SessionImpl.closeWithoutOpenChecks:396 - Closing session [23989d6b-324a-4a3d-b4f2-3a358f5ff536]
2024-04-13 00:19:41,117 [main] TRACE org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.close:152 - Closing JDBC container [org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl@67c020c8]
2024-04-13 00:19:41,123 [main] TRACE org.hibernate.resource.jdbc.internal.ResourceRegistryStandardImpl.releaseResources:329 - Releasing JDBC resources
2024-04-13 00:19:41,133 [main] TRACE org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.close:256 - Closing logical connection
2024-04-13 00:19:41,139 [main] TRACE org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.close:263 - Logical connection closed
2024-04-13 00:19:41,146 [main] DEBUG org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke:310 - Creating new EntityManager for shared EntityManager invocation
2024-04-13 00:19:41,151 [main] TRACE org.hibernate.internal.SessionFactoryImpl$SessionBuilderImpl.openSession:1363 - Opening Hibernate Session. tenant=root
2024-04-13 00:19:41,160 [main] TRACE org.hibernate.internal.SessionImpl.<init>:272 - Opened Session [52517467-b3ab-49aa-8efb-832f41f21f60] at timestamp: 1712953181160
2024-04-13 00:19:41,166 [main] TRACE org.hibernate.internal.SessionImpl.closeWithoutOpenChecks:396 - Closing session [52517467-b3ab-49aa-8efb-832f41f21f60]
2024-04-13 00:19:41,172 [main] TRACE org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.close:152 - Closing JDBC container [org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl@66464f27]
2024-04-13 00:19:41,177 [main] TRACE org.hibernate.resource.jdbc.internal.ResourceRegistryStandardImpl.releaseResources:329 - Releasing JDBC resources
2024-04-13 00:19:41,182 [main] TRACE org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.close:256 - Closing logical connection
2024-04-13 00:19:41,189 [main] TRACE org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.close:263 - Logical connection closed
Activity
[-]Using natirce Hibernate multinenancy with Spring Data JPA breaks on application boot.[/-][+]Using native Hibernate multitenancy with Spring Data JPA breaks on application boot.[/+]mp911de commentedon Apr 15, 2024
JpaRepositoryFactory
isn't tied to multi-tenancy identifiers. Upon application startup we need to obtain theEntityManager
for various initialization tasks, verification of queries and introspection of managed types to properly start up the repository infrastructure.At some point, this has to happen. Since repositories aren't tied to tenant information, what would be an alternative?
mnnayeck commentedon Apr 15, 2024
I understand and agree with your point. I just think there must be another way to retrieve the extractor
The line causing problem at his point is
Class<?> entityManagerType = em.getDelegate().getClass();
which actually makes a connection to the database (which forces the developer to specify a tenant identifier for the connection to be successful to the datasource).
mp911de commentedon Apr 15, 2024
Since you're already looking into the issue, care to investigate an approach that would work for you?
odrotbohm commentedon Apr 17, 2024
I briefly thought about switching to checking the EMF for its type rather than the delegate, but it turns out both Hibernate's implementation of
….getEntityManagerFactory()
and….getDelegate()
callcheckOpen()
. I am assuming that that call is triggering the opening of a connection, asgetDelegate()
simply returnsthis
.To me there are two aspects to this:
EntityManager
instance, it's gonna be hard for us to fix the problem. We could inspect Spring configuration setup, but that would couple our detection logic to more to expecting developers to set up their JPA infrastructure through it. While that's probably given for most of our user base, it might not be the case for all.EntityManager
in the constructor of the repository factory, would that really help? As Mark pointed out, at some point we will have to interact with theEntityManager
to validate queries. If any of those steps requires a connection to be established (I don't know whether that's true or not) you'd still have to answer the question of how to obtain a tenant identifier from the initialization process in general. How would you like to provide that?We could still give the EMF inspection a try though as Spring wraps the EM into a proxy that delegates calls to
getEntityManagerFactory()
to return the Spring-managed EMF directly.mnnayeck commentedon Apr 19, 2024
Hello Olivier,
Regarding the second point, I don't think you can the call to the EntityManager. This means that you would need to delay it until the first call to the persistence layer done by the application.
Regarding your first point, the problem is getDelegate() which is implementation specific. I checked for Hibernate and EclipseLink - both call checkOpen() to simply open a connection before returning 'this'.
Hibernate:
@Override public Object getDelegate() { checkOpen(); return this; }
EclipseLink:
@Override public Object getDelegate() { try { verifyOpen(); return this; } catch (RuntimeException e) { setRollbackOnly(); throw e; } }
Honestly, I think we need to update the JPA spec to add em.getDelegateClass() method. This would be much much cleaner.
mnnayeck commentedon Apr 19, 2024
I briefly tried to use em.getMetaModel() to try resolve the issue. but those also make a call to checkOpen() - both in EclipseLink as well as Hibernate. That's a dead end.
31 remaining items