HHH-17619 Add the multitenancy filter in a stateless session

This commit is contained in:
marko-bekhta 2024-01-04 15:58:13 +01:00 committed by Christian Beikov
parent f5800a0388
commit 733b555e86
4 changed files with 59 additions and 21 deletions

View File

@ -29,7 +29,9 @@ import org.hibernate.SessionEventListener;
import org.hibernate.SessionException; import org.hibernate.SessionException;
import org.hibernate.Transaction; import org.hibernate.Transaction;
import org.hibernate.UnknownEntityTypeException; import org.hibernate.UnknownEntityTypeException;
import org.hibernate.binder.internal.TenantIdBinder;
import org.hibernate.cache.spi.CacheTransactionSynchronization; import org.hibernate.cache.spi.CacheTransactionSynchronization;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.hibernate.engine.internal.SessionEventListenerManagerImpl; import org.hibernate.engine.internal.SessionEventListenerManagerImpl;
import org.hibernate.engine.jdbc.LobCreator; import org.hibernate.engine.jdbc.LobCreator;
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
@ -38,6 +40,7 @@ import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.ExceptionConverter; import org.hibernate.engine.spi.ExceptionConverter;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionEventListenerManager; import org.hibernate.engine.spi.SessionEventListenerManager;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
@ -224,6 +227,24 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
&& ( (SharedSessionCreationOptions) options ).isTransactionCoordinatorShared(); && ( (SharedSessionCreationOptions) options ).isTransactionCoordinatorShared();
} }
protected final void setUpMultitenancy(SessionFactoryImplementor factory, LoadQueryInfluencers loadQueryInfluencers) {
if ( factory.getDefinedFilterNames().contains( TenantIdBinder.FILTER_NAME ) ) {
final Object tenantIdentifier = getTenantIdentifierValue();
if ( tenantIdentifier == null ) {
throw new HibernateException( "SessionFactory configured for multi-tenancy, but no tenant identifier specified" );
}
else {
final CurrentTenantIdentifierResolver<Object> resolver = factory.getCurrentTenantIdentifierResolver();
if ( resolver==null || !resolver.isRoot( tenantIdentifier ) ) {
// turn on the filter, unless this is the "root" tenant with access to all partitions
loadQueryInfluencers
.enableFilter( TenantIdBinder.FILTER_NAME )
.setParameter( TenantIdBinder.PARAMETER_NAME, tenantIdentifier );
}
}
}
}
private void logInconsistentOptions(SharedSessionCreationOptions sharedOptions) { private void logInconsistentOptions(SharedSessionCreationOptions sharedOptions) {
if ( sharedOptions.shouldAutoJoinTransactions() ) { if ( sharedOptions.shouldAutoJoinTransactions() ) {
log.debug( log.debug(

View File

@ -49,9 +49,7 @@ import org.hibernate.TransientObjectException;
import org.hibernate.TypeMismatchException; import org.hibernate.TypeMismatchException;
import org.hibernate.UnknownProfileException; import org.hibernate.UnknownProfileException;
import org.hibernate.UnresolvableObjectException; import org.hibernate.UnresolvableObjectException;
import org.hibernate.binder.internal.TenantIdBinder;
import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.hibernate.engine.internal.StatefulPersistenceContext; import org.hibernate.engine.internal.StatefulPersistenceContext;
import org.hibernate.engine.jdbc.LobCreator; import org.hibernate.engine.jdbc.LobCreator;
import org.hibernate.engine.jdbc.NonContextualLobCreator; import org.hibernate.engine.jdbc.NonContextualLobCreator;
@ -268,7 +266,7 @@ public class SessionImpl
setHibernateFlushMode( getInitialFlushMode() ); setHibernateFlushMode( getInitialFlushMode() );
} }
setUpMultitenancy( factory ); setUpMultitenancy( factory, loadQueryInfluencers );
if ( log.isTraceEnabled() ) { if ( log.isTraceEnabled() ) {
log.tracef( "Opened Session [%s] at timestamp: %s", getSessionIdentifier(), currentTimeMillis() ); log.tracef( "Opened Session [%s] at timestamp: %s", getSessionIdentifier(), currentTimeMillis() );
@ -283,24 +281,6 @@ public class SessionImpl
: ConfigurationHelper.getFlushMode( getSessionProperty( HINT_FLUSH_MODE ), FlushMode.AUTO ); : ConfigurationHelper.getFlushMode( getSessionProperty( HINT_FLUSH_MODE ), FlushMode.AUTO );
} }
private void setUpMultitenancy(SessionFactoryImplementor factory) {
if ( factory.getDefinedFilterNames().contains( TenantIdBinder.FILTER_NAME ) ) {
final Object tenantIdentifier = getTenantIdentifierValue();
if ( tenantIdentifier == null ) {
throw new HibernateException( "SessionFactory configured for multi-tenancy, but no tenant identifier specified" );
}
else {
final CurrentTenantIdentifierResolver<Object> resolver = factory.getCurrentTenantIdentifierResolver();
if ( resolver==null || !resolver.isRoot( tenantIdentifier ) ) {
// turn on the filter, unless this is the "root" tenant with access to all partitions
loadQueryInfluencers
.enableFilter( TenantIdBinder.FILTER_NAME )
.setParameter( TenantIdBinder.PARAMETER_NAME, tenantIdentifier );
}
}
}
}
protected StatefulPersistenceContext createPersistenceContext() { protected StatefulPersistenceContext createPersistenceContext() {
return new StatefulPersistenceContext( this ); return new StatefulPersistenceContext( this );
} }

View File

@ -78,6 +78,7 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen
connectionProvided = options.getConnection() != null; connectionProvided = options.getConnection() != null;
temporaryPersistenceContext = new StatefulPersistenceContext( this ); temporaryPersistenceContext = new StatefulPersistenceContext( this );
influencers = new LoadQueryInfluencers( getFactory() ); influencers = new LoadQueryInfluencers( getFactory() );
setUpMultitenancy( factory, influencers );
} }
@Override @Override

View File

@ -8,6 +8,7 @@ package org.hibernate.orm.test.tenantid;
import org.hibernate.HibernateError; import org.hibernate.HibernateError;
import org.hibernate.PropertyValueException; import org.hibernate.PropertyValueException;
import org.hibernate.StatelessSession;
import org.hibernate.boot.SessionFactoryBuilder; import org.hibernate.boot.SessionFactoryBuilder;
import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
@ -20,6 +21,9 @@ import org.hibernate.testing.orm.junit.SessionFactoryProducer;
import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting; import org.hibernate.testing.orm.junit.Setting;
import org.hibernate.binder.internal.TenantIdBinder; import org.hibernate.binder.internal.TenantIdBinder;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaRoot;
import org.hibernate.testing.orm.junit.SkipForDialect; import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -28,6 +32,7 @@ import org.junit.jupiter.api.Test;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory; import jakarta.persistence.EntityManagerFactory;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.cfg.AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION; import static org.hibernate.cfg.AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION;
import static org.hibernate.internal.util.collections.CollectionHelper.toMap; import static org.hibernate.internal.util.collections.CollectionHelper.toMap;
import static org.hibernate.jpa.HibernateHints.HINT_TENANT_ID; import static org.hibernate.jpa.HibernateHints.HINT_TENANT_ID;
@ -37,6 +42,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
import java.util.List;
@SessionFactory @SessionFactory
@DomainModel(annotatedClasses = { Account.class, Client.class, Record.class }) @DomainModel(annotatedClasses = { Account.class, Client.class, Record.class })
@ServiceRegistry( @ServiceRegistry(
@ -53,6 +60,7 @@ public class TenantIdTest implements SessionFactoryProducer {
scope.inTransaction( session -> { scope.inTransaction( session -> {
session.createQuery("delete from Account").executeUpdate(); session.createQuery("delete from Account").executeUpdate();
session.createQuery("delete from Client").executeUpdate(); session.createQuery("delete from Client").executeUpdate();
session.createQuery("delete from Record").executeUpdate();
}); });
} }
@ -217,6 +225,34 @@ public class TenantIdTest implements SessionFactoryProducer {
} }
} }
@Test
public void tenantFilterWithStatelessSession(SessionFactoryScope scope) {
currentTenant = "mine";
Record myRecord1 = new Record();
Record myRecord2 = new Record();
scope.inTransaction( session -> {
session.persist(myRecord1);
session.persist(myRecord2);
} );
scope.inStatelessTransaction( session -> {
assertThat( listAllRecordsForTenant( session ) ).hasSize( 2 );
} );
currentTenant = "yours";
scope.inStatelessTransaction( session -> {
assertThat( listAllRecordsForTenant( session ) ).isEmpty();
} );
}
private static List<Record> listAllRecordsForTenant(StatelessSession session) {
HibernateCriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
JpaCriteriaQuery<Record> criteriaQuery = criteriaBuilder.createQuery( Record.class );
JpaRoot<Record> from = criteriaQuery.from( Record.class );
return session.createQuery( criteriaQuery ).getResultList();
}
private static void waitALittle() { private static void waitALittle() {
try { try {
Thread.sleep( 10 ); Thread.sleep( 10 );