Skip to content

Commit 7a6918a

Browse files
committed
HHH-19559 introduce TenantSchemaMapper
configuring a TenantSchemaMapper turns on schema-based multitenancy
1 parent 351d922 commit 7a6918a

13 files changed

+150
-64
lines changed

hibernate-core/src/main/java/org/hibernate/boot/SessionFactoryBuilder.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.hibernate.annotations.CacheLayout;
1616
import org.hibernate.cache.spi.TimestampsCacheFactory;
1717
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
18+
import org.hibernate.context.spi.TenantSchemaMapper;
1819
import org.hibernate.jpa.spi.JpaCompliance;
1920
import org.hibernate.proxy.EntityNotFoundDelegate;
2021
import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
@@ -384,6 +385,21 @@ public interface SessionFactoryBuilder {
384385
*/
385386
SessionFactoryBuilder applyCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver<?> resolver);
386387

388+
/**
389+
* Specifies a {@link TenantSchemaMapper} that is responsible for
390+
* mapping the current tenant identifier to the name of a database
391+
* schema.
392+
*
393+
* @param mapper The mapping strategy to use.
394+
*
395+
* @return {@code this}, for method chaining
396+
*
397+
* @see org.hibernate.cfg.AvailableSettings#MULTI_TENANT_SCHEMA_MAPPER
398+
*
399+
* @since 7.1
400+
*/
401+
SessionFactoryBuilder applyTenantSchemaMapper(TenantSchemaMapper<?> mapper);
402+
387403
/**
388404
* If using the built-in JTA-based
389405
* {@link org.hibernate.resource.transaction.spi.TransactionCoordinator} or

hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.hibernate.bytecode.spi.BytecodeProvider;
2424
import org.hibernate.cache.spi.TimestampsCacheFactory;
2525
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
26+
import org.hibernate.context.spi.TenantSchemaMapper;
2627
import org.hibernate.internal.SessionFactoryImpl;
2728
import org.hibernate.proxy.EntityNotFoundDelegate;
2829
import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
@@ -252,6 +253,12 @@ public SessionFactoryBuilder applyCurrentTenantIdentifierResolver(CurrentTenantI
252253
return this;
253254
}
254255

256+
@Override
257+
public SessionFactoryBuilder applyTenantSchemaMapper(TenantSchemaMapper<?> mapper) {
258+
this.optionsBuilder.applyTenantSchemaMapper( mapper );
259+
return this;
260+
}
261+
255262
@Override
256263
public SessionFactoryBuilder applyNamedQueryCheckingOnStartup(boolean enabled) {
257264
this.optionsBuilder.enableNamedQueryCheckingOnStartup( enabled );

hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.hibernate.LockOptions;
2929
import org.hibernate.SessionEventListener;
3030
import org.hibernate.SessionFactoryObserver;
31+
import org.hibernate.context.spi.TenantSchemaMapper;
3132
import org.hibernate.type.TimeZoneStorageStrategy;
3233
import org.hibernate.annotations.CacheLayout;
3334
import org.hibernate.boot.SchemaAutoTooling;
@@ -187,7 +188,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
187188
// multi-tenancy
188189
private boolean multiTenancyEnabled;
189190
private CurrentTenantIdentifierResolver<Object> currentTenantIdentifierResolver;
190-
private boolean setTenantSchemaEnabled;
191+
private TenantSchemaMapper<Object> tenantSchemaMapper;
191192

192193
// Queries
193194
private SqmFunctionRegistry sqmFunctionRegistry;
@@ -372,7 +373,9 @@ public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, Boo
372373
null
373374
);
374375
}
375-
setTenantSchemaEnabled = configurationService.getSetting( SET_TENANT_SCHEMA, BOOLEAN, false );
376+
tenantSchemaMapper =
377+
strategySelector.resolveStrategy( TenantSchemaMapper.class,
378+
settings.get( MULTI_TENANT_SCHEMA_MAPPER ) );
376379

377380
delayBatchFetchLoaderCreations =
378381
configurationService.getSetting( DELAY_ENTITY_LOADER_CREATIONS, BOOLEAN, true );
@@ -1006,8 +1009,8 @@ public boolean isMultiTenancyEnabled() {
10061009
}
10071010

10081011
@Override
1009-
public boolean isSetTenantSchemaEnabled() {
1010-
return setTenantSchemaEnabled;
1012+
public TenantSchemaMapper<Object> getTenantSchemaMapper() {
1013+
return tenantSchemaMapper;
10111014
}
10121015

10131016
@Override
@@ -1457,8 +1460,9 @@ public void applyCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver
14571460
this.currentTenantIdentifierResolver = (CurrentTenantIdentifierResolver<Object>) resolver;
14581461
}
14591462

1460-
public void applySetTenantSchema(boolean enabled) {
1461-
this.setTenantSchemaEnabled = enabled;
1463+
public void applyTenantSchemaMapper(TenantSchemaMapper<?> mapper) {
1464+
//noinspection unchecked
1465+
this.tenantSchemaMapper = (TenantSchemaMapper<Object>) mapper;
14621466
}
14631467

14641468
public void enableNamedQueryCheckingOnStartup(boolean enabled) {

hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryBuilder.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.hibernate.boot.TempTableDdlTransactionHandling;
1717
import org.hibernate.cache.spi.TimestampsCacheFactory;
1818
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
19+
import org.hibernate.context.spi.TenantSchemaMapper;
1920
import org.hibernate.proxy.EntityNotFoundDelegate;
2021
import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
2122
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
@@ -208,6 +209,12 @@ public T applyCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver<?>
208209
return getThis();
209210
}
210211

212+
@Override
213+
public SessionFactoryBuilder applyTenantSchemaMapper(TenantSchemaMapper<?> mapper) {
214+
delegate.applyTenantSchemaMapper( mapper );
215+
return getThis();
216+
}
217+
211218
@Override
212219
public T applyJtaTrackingByThread(boolean enabled) {
213220
delegate.applyJtaTrackingByThread( enabled );

hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.hibernate.Interceptor;
1818
import org.hibernate.LockOptions;
1919
import org.hibernate.SessionFactoryObserver;
20+
import org.hibernate.context.spi.TenantSchemaMapper;
2021
import org.hibernate.type.TimeZoneStorageStrategy;
2122
import org.hibernate.annotations.CacheLayout;
2223
import org.hibernate.boot.SchemaAutoTooling;
@@ -220,13 +221,13 @@ public boolean isMultiTenancyEnabled() {
220221
}
221222

222223
@Override
223-
public boolean isSetTenantSchemaEnabled() {
224-
return delegate.isSetTenantSchemaEnabled();
224+
public CurrentTenantIdentifierResolver<Object> getCurrentTenantIdentifierResolver() {
225+
return delegate.getCurrentTenantIdentifierResolver();
225226
}
226227

227228
@Override
228-
public CurrentTenantIdentifierResolver<Object> getCurrentTenantIdentifierResolver() {
229-
return delegate.getCurrentTenantIdentifierResolver();
229+
public TenantSchemaMapper<Object> getTenantSchemaMapper() {
230+
return delegate.getTenantSchemaMapper();
230231
}
231232

232233
@Override

hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.hibernate.LockOptions;
2121
import org.hibernate.SessionEventListener;
2222
import org.hibernate.SessionFactoryObserver;
23+
import org.hibernate.context.spi.TenantSchemaMapper;
2324
import org.hibernate.type.TimeZoneStorageStrategy;
2425
import org.hibernate.annotations.CacheLayout;
2526
import org.hibernate.boot.SchemaAutoTooling;
@@ -304,17 +305,6 @@ default SessionEventListener[] buildSessionEventListeners() {
304305
*/
305306
boolean isMultiTenancyEnabled();
306307

307-
/**
308-
* Should the schema be {@linkplain java.sql.Connection#setSchema set} to the
309-
* {@linkplain CurrentTenantIdentifierResolver#schemaName schema belonging to
310-
* the current tenant} each time a connection is obtained?
311-
*
312-
* @see org.hibernate.cfg.MultiTenancySettings#SET_TENANT_SCHEMA
313-
*
314-
* @since 7.1
315-
*/
316-
boolean isSetTenantSchemaEnabled();
317-
318308
/**
319309
* Obtain a reference to the
320310
* {@linkplain CurrentTenantIdentifierResolver current tenant identifier resolver},
@@ -324,6 +314,18 @@ default SessionEventListener[] buildSessionEventListeners() {
324314
*/
325315
CurrentTenantIdentifierResolver<Object> getCurrentTenantIdentifierResolver();
326316

317+
/**
318+
* Obtain a reference to the current {@linkplain TenantSchemaMapper tenant schema mapper},
319+
* which is used to {@linkplain java.sql.Connection#setSchema set the schema} to the
320+
* {@linkplain TenantSchemaMapper#schemaName schema belonging to the current tenant}
321+
* each time a connection is obtained.
322+
*
323+
* @see org.hibernate.cfg.MultiTenancySettings#MULTI_TENANT_SCHEMA_MAPPER
324+
*
325+
* @since 7.1
326+
*/
327+
TenantSchemaMapper<Object> getTenantSchemaMapper();
328+
327329
/**
328330
* @see org.hibernate.cfg.TransactionSettings#JTA_TRACK_BY_THREAD
329331
*/

hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
4949
import org.hibernate.boot.spi.XmlMappingBinderAccess;
5050
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
51+
import org.hibernate.context.spi.TenantSchemaMapper;
5152
import org.hibernate.internal.CoreLogging;
5253
import org.hibernate.internal.CoreMessageLogger;
5354
import org.hibernate.internal.EmptyInterceptor;
@@ -173,7 +174,8 @@ public class Configuration {
173174
private EntityNotFoundDelegate entityNotFoundDelegate;
174175
private SessionFactoryObserver sessionFactoryObserver;
175176
private StatementInspector statementInspector;
176-
private CurrentTenantIdentifierResolver<Object> currentTenantIdentifierResolver;
177+
private CurrentTenantIdentifierResolver<?> currentTenantIdentifierResolver;
178+
private TenantSchemaMapper<?> tenantSchemaMapper;
177179
private CustomEntityDirtinessStrategy customEntityDirtinessStrategy;
178180
private ColumnOrderingStrategy columnOrderingStrategy;
179181
private SharedCacheMode sharedCacheMode;
@@ -939,7 +941,7 @@ public Configuration setStatementInspector(StatementInspector statementInspector
939941
/**
940942
* The {@link CurrentTenantIdentifierResolver}, if any, that was added to this configuration.
941943
*/
942-
public CurrentTenantIdentifierResolver<Object> getCurrentTenantIdentifierResolver() {
944+
public CurrentTenantIdentifierResolver<?> getCurrentTenantIdentifierResolver() {
943945
return currentTenantIdentifierResolver;
944946
}
945947

@@ -948,11 +950,32 @@ public CurrentTenantIdentifierResolver<Object> getCurrentTenantIdentifierResolve
948950
*
949951
* @return {@code this} for method chaining
950952
*/
951-
public Configuration setCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver<Object> currentTenantIdentifierResolver) {
953+
public Configuration setCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver<?> currentTenantIdentifierResolver) {
952954
this.currentTenantIdentifierResolver = currentTenantIdentifierResolver;
953955
return this;
954956
}
955957

958+
/**
959+
* The {@link TenantSchemaMapper}, if any, that was added to this configuration.
960+
*
961+
* @since 7.1
962+
*/
963+
public TenantSchemaMapper<?> getTenantSchemaMapper() {
964+
return tenantSchemaMapper;
965+
}
966+
967+
/**
968+
* Specify a {@link TenantSchemaMapper} to be added to this configuration.
969+
*
970+
* @return {@code this} for method chaining
971+
*
972+
* @since 7.1
973+
*/
974+
public Configuration setTenantSchemaMapper(TenantSchemaMapper<?> tenantSchemaMapper) {
975+
this.tenantSchemaMapper = tenantSchemaMapper;
976+
return this;
977+
}
978+
956979
/**
957980
* The {@link CustomEntityDirtinessStrategy}, if any, that was added to this configuration.
958981
*/
@@ -1082,6 +1105,10 @@ public SessionFactory buildSessionFactory(ServiceRegistry serviceRegistry) throw
10821105
sessionFactoryBuilder.applyCurrentTenantIdentifierResolver( currentTenantIdentifierResolver );
10831106
}
10841107

1108+
if ( tenantSchemaMapper != null ) {
1109+
sessionFactoryBuilder.applyTenantSchemaMapper( tenantSchemaMapper );
1110+
}
1111+
10851112
if ( customEntityDirtinessStrategy != null ) {
10861113
sessionFactoryBuilder.applyCustomEntityDirtinessStrategy( customEntityDirtinessStrategy );
10871114
}

hibernate-core/src/main/java/org/hibernate/cfg/MultiTenancySettings.java

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,37 +22,44 @@ public interface MultiTenancySettings {
2222
String MULTI_TENANT_CONNECTION_PROVIDER = "hibernate.multi_tenant_connection_provider";
2323

2424
/**
25-
* Specifies a {@link CurrentTenantIdentifierResolver} to use,
26-
* either:
25+
* Specifies a {@link CurrentTenantIdentifierResolver} to use, either:
2726
* <ul>
2827
* <li>an instance of {@code CurrentTenantIdentifierResolver},
2928
* <li>a {@link Class} representing a class that implements {@code CurrentTenantIdentifierResolver}, or
3029
* <li>the name of a class that implements {@code CurrentTenantIdentifierResolver}.
3130
* </ul>
3231
*
33-
* @see org.hibernate.boot.SessionFactoryBuilder#applyCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver)
32+
* @see CurrentTenantIdentifierResolver
33+
* @see org.hibernate.boot.SessionFactoryBuilder#applyCurrentTenantIdentifierResolver
3434
*
3535
* @since 4.1
3636
*/
3737
String MULTI_TENANT_IDENTIFIER_RESOLVER = "hibernate.tenant_identifier_resolver";
3838

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

4646
/**
47-
* Specifies that {@link java.sql.Connection#setSchema(String)}}
48-
* should be called with the current tenant id, or with the schema name
49-
* returned by {@link CurrentTenantIdentifierResolver#schemaName}.
50-
*
51-
* @settingDefault false
47+
* Specifies a {@link org.hibernate.context.spi.TenantSchemaMapper} to use, either:
48+
* <ul>
49+
* <li>an instance of {@code TenantSchemaMapper},
50+
* <li>a {@link Class} representing a class that implements {@code TenantSchemaMapper}, or
51+
* <li>the name of a class that implements {@code TenantSchemaMapper}.
52+
* </ul>
53+
* When a tenant schema mapper is set, {@link java.sql.Connection#setSchema(String)}}
54+
* is called on newly acquired JDBC connections with the schema name returned by
55+
* {@link org.hibernate.context.spi.TenantSchemaMapper#schemaName}.
56+
* <p>
57+
* By default, there is no tenant schema mapper.
5258
*
53-
* @see CurrentTenantIdentifierResolver#schemaName
59+
* @see org.hibernate.context.spi.TenantSchemaMapper
60+
* @see org.hibernate.boot.SessionFactoryBuilder#applyTenantSchemaMapper
5461
*
5562
* @since 7.1
5663
*/
57-
String SET_TENANT_SCHEMA = "hibernate.multi_tenant.set_schema";
64+
String MULTI_TENANT_SCHEMA_MAPPER = "hibernate.multi_tenant.schema_mapper";
5865
}

hibernate-core/src/main/java/org/hibernate/context/spi/CurrentTenantIdentifierResolver.java

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*/
55
package org.hibernate.context.spi;
66

7-
import org.checkerframework.checker.nullness.qual.NonNull;
87

98
/**
109
* A callback registered with the {@link org.hibernate.SessionFactory} that is
@@ -50,20 +49,4 @@ public interface CurrentTenantIdentifierResolver<T> {
5049
default boolean isRoot(T tenantId) {
5150
return false;
5251
}
53-
54-
/**
55-
* The name of the database schema for data belonging to the tenant with the
56-
* given identifier.
57-
* <p>
58-
* Called when {@value org.hibernate.cfg.MultiTenancySettings#SET_TENANT_SCHEMA}
59-
* is enabled.
60-
*
61-
* @param tenantIdentifier The tenant identifier
62-
* @return The name of the database schema belonging to that tenant
63-
*
64-
* @see org.hibernate.cfg.MultiTenancySettings#SET_TENANT_SCHEMA
65-
*/
66-
default @NonNull String schemaName(@NonNull T tenantIdentifier) {
67-
return tenantIdentifier.toString();
68-
}
6952
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.context.spi;
6+
7+
import org.checkerframework.checker.nullness.qual.NonNull;
8+
9+
/**
10+
* Obtains the name of a database schema for a given tenant identifier when
11+
* {@linkplain org.hibernate.cfg.MultiTenancySettings#MULTI_TENANT_SCHEMA_MAPPER
12+
* schema-based multitenancy} is enabled.
13+
*
14+
* @param <T> The type of the tenant id
15+
*
16+
* @since 7.1
17+
*
18+
* @author Gavin King
19+
*/
20+
public interface TenantSchemaMapper<T> {
21+
/**
22+
* The name of the database schema for data belonging to the tenant with the
23+
* given identifier.
24+
* <p>
25+
* Called when {@value org.hibernate.cfg.MultiTenancySettings#MULTI_TENANT_SCHEMA_MAPPER}
26+
* is enabled.
27+
*
28+
* @param tenantIdentifier The tenant identifier
29+
* @return The name of the database schema belonging to that tenant
30+
*
31+
* @see org.hibernate.cfg.MultiTenancySettings#MULTI_TENANT_SCHEMA_MAPPER
32+
*/
33+
@NonNull String schemaName(@NonNull T tenantIdentifier);
34+
}

0 commit comments

Comments
 (0)