Skip to content

HHH-19559 add hibernate.multi_tenant.set_schema #10372

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
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