Skip to content
Closed
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.hibernate.annotations.CacheLayout;
import org.hibernate.cache.spi.TimestampsCacheFactory;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.hibernate.context.spi.TenantSchemaMapper;
import org.hibernate.jpa.spi.JpaCompliance;
import org.hibernate.proxy.EntityNotFoundDelegate;
import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
Expand Down Expand Up @@ -384,6 +385,21 @@ public interface SessionFactoryBuilder {
*/
SessionFactoryBuilder applyCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver<?> resolver);

/**
* Specifies a {@link TenantSchemaMapper} that is responsible for
* mapping the current tenant identifier to the name of a database
* schema.
*
* @param mapper The mapping strategy to use.
*
* @return {@code this}, for method chaining
*
* @see org.hibernate.cfg.AvailableSettings#MULTI_TENANT_SCHEMA_MAPPER
*
* @since 7.1
*/
SessionFactoryBuilder applyTenantSchemaMapper(TenantSchemaMapper<?> mapper);

/**
* If using the built-in JTA-based
* {@link org.hibernate.resource.transaction.spi.TransactionCoordinator} or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.hibernate.bytecode.spi.BytecodeProvider;
import org.hibernate.cache.spi.TimestampsCacheFactory;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.hibernate.context.spi.TenantSchemaMapper;
import org.hibernate.internal.SessionFactoryImpl;
import org.hibernate.proxy.EntityNotFoundDelegate;
import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
Expand Down Expand Up @@ -252,6 +253,12 @@ public SessionFactoryBuilder applyCurrentTenantIdentifierResolver(CurrentTenantI
return this;
}

@Override
public SessionFactoryBuilder applyTenantSchemaMapper(TenantSchemaMapper<?> mapper) {
this.optionsBuilder.applyTenantSchemaMapper( mapper );
return this;
}

@Override
public SessionFactoryBuilder applyNamedQueryCheckingOnStartup(boolean enabled) {
this.optionsBuilder.enableNamedQueryCheckingOnStartup( enabled );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.hibernate.LockOptions;
import org.hibernate.SessionEventListener;
import org.hibernate.SessionFactoryObserver;
import org.hibernate.context.spi.TenantSchemaMapper;
import org.hibernate.type.TimeZoneStorageStrategy;
import org.hibernate.annotations.CacheLayout;
import org.hibernate.boot.SchemaAutoTooling;
Expand Down Expand Up @@ -187,6 +188,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
// multi-tenancy
private boolean multiTenancyEnabled;
private CurrentTenantIdentifierResolver<Object> currentTenantIdentifierResolver;
private TenantSchemaMapper<Object> tenantSchemaMapper;

// Queries
private SqmFunctionRegistry sqmFunctionRegistry;
Expand Down Expand Up @@ -371,6 +373,9 @@ public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, Boo
null
);
}
tenantSchemaMapper =
strategySelector.resolveStrategy( TenantSchemaMapper.class,
settings.get( MULTI_TENANT_SCHEMA_MAPPER ) );

delayBatchFetchLoaderCreations =
configurationService.getSetting( DELAY_ENTITY_LOADER_CREATIONS, BOOLEAN, true );
Expand Down Expand Up @@ -1003,6 +1008,11 @@ public boolean isMultiTenancyEnabled() {
return multiTenancyEnabled;
}

@Override
public TenantSchemaMapper<Object> getTenantSchemaMapper() {
return tenantSchemaMapper;
}

@Override
public CurrentTenantIdentifierResolver<Object> getCurrentTenantIdentifierResolver() {
return currentTenantIdentifierResolver;
Expand Down Expand Up @@ -1450,6 +1460,11 @@ public void applyCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver
this.currentTenantIdentifierResolver = (CurrentTenantIdentifierResolver<Object>) resolver;
}

public void applyTenantSchemaMapper(TenantSchemaMapper<?> mapper) {
//noinspection unchecked
this.tenantSchemaMapper = (TenantSchemaMapper<Object>) mapper;
}

public void enableNamedQueryCheckingOnStartup(boolean enabled) {
this.namedQueryStartupCheckingEnabled = enabled;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.hibernate.boot.TempTableDdlTransactionHandling;
import org.hibernate.cache.spi.TimestampsCacheFactory;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.hibernate.context.spi.TenantSchemaMapper;
import org.hibernate.proxy.EntityNotFoundDelegate;
import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
Expand Down Expand Up @@ -208,6 +209,12 @@ public T applyCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver<?>
return getThis();
}

@Override
public SessionFactoryBuilder applyTenantSchemaMapper(TenantSchemaMapper<?> mapper) {
delegate.applyTenantSchemaMapper( mapper );
return getThis();
}

@Override
public T applyJtaTrackingByThread(boolean enabled) {
delegate.applyJtaTrackingByThread( enabled );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.hibernate.Interceptor;
import org.hibernate.LockOptions;
import org.hibernate.SessionFactoryObserver;
import org.hibernate.context.spi.TenantSchemaMapper;
import org.hibernate.type.TimeZoneStorageStrategy;
import org.hibernate.annotations.CacheLayout;
import org.hibernate.boot.SchemaAutoTooling;
Expand Down Expand Up @@ -224,6 +225,11 @@ public CurrentTenantIdentifierResolver<Object> getCurrentTenantIdentifierResolve
return delegate.getCurrentTenantIdentifierResolver();
}

@Override
public TenantSchemaMapper<Object> getTenantSchemaMapper() {
return delegate.getTenantSchemaMapper();
}

@Override
public JavaType<Object> getDefaultTenantIdentifierJavaType() {
return delegate.getDefaultTenantIdentifierJavaType();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.hibernate.LockOptions;
import org.hibernate.SessionEventListener;
import org.hibernate.SessionFactoryObserver;
import org.hibernate.context.spi.TenantSchemaMapper;
import org.hibernate.type.TimeZoneStorageStrategy;
import org.hibernate.annotations.CacheLayout;
import org.hibernate.boot.SchemaAutoTooling;
Expand Down Expand Up @@ -313,6 +314,18 @@ default SessionEventListener[] buildSessionEventListeners() {
*/
CurrentTenantIdentifierResolver<Object> getCurrentTenantIdentifierResolver();

/**
* Obtain a reference to the current {@linkplain TenantSchemaMapper tenant schema mapper},
* which is used to {@linkplain java.sql.Connection#setSchema set the schema} to the
* {@linkplain TenantSchemaMapper#schemaName schema belonging to the current tenant}
* each time a connection is obtained.
*
* @see org.hibernate.cfg.MultiTenancySettings#MULTI_TENANT_SCHEMA_MAPPER
*
* @since 7.1
*/
TenantSchemaMapper<Object> getTenantSchemaMapper();

/**
* @see org.hibernate.cfg.TransactionSettings#JTA_TRACK_BY_THREAD
*/
Expand Down
33 changes: 30 additions & 3 deletions hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.spi.XmlMappingBinderAccess;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.hibernate.context.spi.TenantSchemaMapper;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.EmptyInterceptor;
Expand Down Expand Up @@ -173,7 +174,8 @@ public class Configuration {
private EntityNotFoundDelegate entityNotFoundDelegate;
private SessionFactoryObserver sessionFactoryObserver;
private StatementInspector statementInspector;
private CurrentTenantIdentifierResolver<Object> currentTenantIdentifierResolver;
private CurrentTenantIdentifierResolver<?> currentTenantIdentifierResolver;
private TenantSchemaMapper<?> tenantSchemaMapper;
private CustomEntityDirtinessStrategy customEntityDirtinessStrategy;
private ColumnOrderingStrategy columnOrderingStrategy;
private SharedCacheMode sharedCacheMode;
Expand Down Expand Up @@ -939,7 +941,7 @@ public Configuration setStatementInspector(StatementInspector statementInspector
/**
* The {@link CurrentTenantIdentifierResolver}, if any, that was added to this configuration.
*/
public CurrentTenantIdentifierResolver<Object> getCurrentTenantIdentifierResolver() {
public CurrentTenantIdentifierResolver<?> getCurrentTenantIdentifierResolver() {
return currentTenantIdentifierResolver;
}

Expand All @@ -948,11 +950,32 @@ public CurrentTenantIdentifierResolver<Object> getCurrentTenantIdentifierResolve
*
* @return {@code this} for method chaining
*/
public Configuration setCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver<Object> currentTenantIdentifierResolver) {
public Configuration setCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver<?> currentTenantIdentifierResolver) {
this.currentTenantIdentifierResolver = currentTenantIdentifierResolver;
return this;
}

/**
* The {@link TenantSchemaMapper}, if any, that was added to this configuration.
*
* @since 7.1
*/
public TenantSchemaMapper<?> getTenantSchemaMapper() {
return tenantSchemaMapper;
}

/**
* Specify a {@link TenantSchemaMapper} to be added to this configuration.
*
* @return {@code this} for method chaining
*
* @since 7.1
*/
public Configuration setTenantSchemaMapper(TenantSchemaMapper<?> tenantSchemaMapper) {
this.tenantSchemaMapper = tenantSchemaMapper;
return this;
}

/**
* The {@link CustomEntityDirtinessStrategy}, if any, that was added to this configuration.
*/
Expand Down Expand Up @@ -1082,6 +1105,10 @@ public SessionFactory buildSessionFactory(ServiceRegistry serviceRegistry) throw
sessionFactoryBuilder.applyCurrentTenantIdentifierResolver( currentTenantIdentifierResolver );
}

if ( tenantSchemaMapper != null ) {
sessionFactoryBuilder.applyTenantSchemaMapper( tenantSchemaMapper );
}

if ( customEntityDirtinessStrategy != null ) {
sessionFactoryBuilder.applyCustomEntityDirtinessStrategy( customEntityDirtinessStrategy );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,44 @@ public interface MultiTenancySettings {
String MULTI_TENANT_CONNECTION_PROVIDER = "hibernate.multi_tenant_connection_provider";

/**
* Specifies a {@link CurrentTenantIdentifierResolver} to use,
* either:
* Specifies a {@link CurrentTenantIdentifierResolver} to use, either:
* <ul>
* <li>an instance of {@code CurrentTenantIdentifierResolver},
* <li>a {@link Class} representing an class that implements {@code CurrentTenantIdentifierResolver}, or
* <li>a {@link Class} representing a class that implements {@code CurrentTenantIdentifierResolver}, or
* <li>the name of a class that implements {@code CurrentTenantIdentifierResolver}.
* </ul>
*
* @see org.hibernate.boot.SessionFactoryBuilder#applyCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver)
* @see CurrentTenantIdentifierResolver
* @see org.hibernate.boot.SessionFactoryBuilder#applyCurrentTenantIdentifierResolver
*
* @since 4.1
*/
String MULTI_TENANT_IDENTIFIER_RESOLVER = "hibernate.tenant_identifier_resolver";

/**
* During bootstrap, Hibernate needs access to any Connection for access to {@link java.sql.DatabaseMetaData}.
* <p/>
* This setting configures the name of the DataSource to use for this access
* During bootstrap, Hibernate needs access to a {@code Connection} for access
* to the {@link java.sql.DatabaseMetaData}. This setting configures the tenant id
* to use when obtaining the {@link javax.sql.DataSource} to use for this access.
*/
String TENANT_IDENTIFIER_TO_USE_FOR_ANY_KEY = "hibernate.multi_tenant.datasource.identifier_for_any";

/**
* Specifies a {@link org.hibernate.context.spi.TenantSchemaMapper} to use, either:
* <ul>
* <li>an instance of {@code TenantSchemaMapper},
* <li>a {@link Class} representing a class that implements {@code TenantSchemaMapper}, or
* <li>the name of a class that implements {@code TenantSchemaMapper}.
* </ul>
* When a tenant schema mapper is set, {@link java.sql.Connection#setSchema(String)}}
* is called on newly acquired JDBC connections with the schema name returned by
* {@link org.hibernate.context.spi.TenantSchemaMapper#schemaName}.
* <p>
* By default, there is no tenant schema mapper.
*
* @see org.hibernate.context.spi.TenantSchemaMapper
* @see org.hibernate.boot.SessionFactoryBuilder#applyTenantSchemaMapper
*
* @since 7.1
*/
String MULTI_TENANT_SCHEMA_MAPPER = "hibernate.multi_tenant.schema_mapper";
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ public SessionFactoryImplementor factory() {

protected SessionBuilder baseSessionBuilder() {
final SessionBuilderImplementor builder = factory.withOptions();
final CurrentTenantIdentifierResolver<Object> resolver = factory.getCurrentTenantIdentifierResolver();
final var resolver = factory.getCurrentTenantIdentifierResolver();
if ( resolver != null ) {
builder.tenantIdentifier( resolver.resolveCurrentTenantIdentifier() );
}
return builder;
}

protected void validateExistingSession(Session existingSession) {
final CurrentTenantIdentifierResolver<Object> resolver = factory.getCurrentTenantIdentifierResolver();
final var resolver = factory.getCurrentTenantIdentifierResolver();
if ( resolver != null && resolver.validateExistingCurrentSessions() ) {
final Object currentValue = resolver.resolveCurrentTenantIdentifier();
final JavaType<Object> tenantIdentifierJavaType = factory.getTenantIdentifierJavaType();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/
package org.hibernate.context.spi;


/**
* A callback registered with the {@link org.hibernate.SessionFactory} that is
* responsible for resolving the current tenant identifier.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.context.spi;

import org.checkerframework.checker.nullness.qual.NonNull;
import org.hibernate.Incubating;

/**
* Obtains the name of a database schema for a given tenant identifier when
* {@linkplain org.hibernate.cfg.MultiTenancySettings#MULTI_TENANT_SCHEMA_MAPPER
* schema-based multitenancy} is enabled.
*
* @param <T> The type of the tenant id
*
* @since 7.1
*
* @author Gavin King
*/
@Incubating
public interface TenantSchemaMapper<T> {
/**
* The name of the database schema for data belonging to the tenant with the
* given identifier.
* <p>
* Called when {@value org.hibernate.cfg.MultiTenancySettings#MULTI_TENANT_SCHEMA_MAPPER}
* is enabled.
*
* @param tenantIdentifier The tenant identifier
* @return The name of the database schema belonging to that tenant
*
* @see org.hibernate.cfg.MultiTenancySettings#MULTI_TENANT_SCHEMA_MAPPER
*/
@NonNull String schemaName(@NonNull T tenantIdentifier);
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,14 @@ public void injectServices(ServiceRegistryImplementor serviceRegistry) {
throw new HibernateException( "JNDI name [" + this.jndiName + "] could not be resolved" );
}
else if ( namedObject instanceof DataSource datasource ) {
final int loc = this.jndiName.lastIndexOf( '/' );
baseJndiNamespace = this.jndiName.substring( 0, loc );
final String prefix = this.jndiName.substring( loc + 1);
final int loc = jndiName.lastIndexOf( '/' );
baseJndiNamespace = jndiName.substring( 0, loc );
final String prefix = jndiName.substring( loc + 1);
tenantIdentifierForAny = (T) prefix;
dataSourceMap().put( tenantIdentifierForAny, datasource );
}
else if ( namedObject instanceof Context ) {
baseJndiNamespace = this.jndiName;
baseJndiNamespace = jndiName;
final Object configuredTenantId =
configurationService.getSettings().get( TENANT_IDENTIFIER_TO_USE_FOR_ANY_KEY );
tenantIdentifierForAny = (T) configuredTenantId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -806,5 +806,13 @@ public boolean isActive() {
public SqlExceptionHelper getSqlExceptionHelper() {
return sqlExceptionHelper;
}

@Override
public void afterObtainConnection(Connection connection) {
}

@Override
public void beforeReleaseConnection(Connection connection) {
}
}
}
Loading
Loading