HHH-14822 Support custom tenant identifier type

This commit is contained in:
Christian Beikov 2023-10-25 15:58:28 +02:00
parent d3674186bf
commit 5ea40e255d
53 changed files with 418 additions and 288 deletions

View File

@ -112,9 +112,21 @@ public interface SessionBuilder {
* @param tenantIdentifier The tenant identifier.
*
* @return {@code this}, for method chaining
* @deprecated Use {@link #tenantIdentifier(Object)} instead
*/
@Deprecated(forRemoval = true)
SessionBuilder tenantIdentifier(String tenantIdentifier);
/**
* Define the tenant identifier to be associated with the opened session.
*
* @param tenantIdentifier The tenant identifier.
*
* @return {@code this}, for method chaining
* @since 6.4
*/
SessionBuilder tenantIdentifier(Object tenantIdentifier);
/**
* Add one or more {@link SessionEventListener} instances to the list of
* listeners for the new session to be built.

View File

@ -31,6 +31,14 @@ public interface SharedSessionContract extends QueryProducer, Closeable, Seriali
*/
String getTenantIdentifier();
/**
* Obtain the tenant identifier associated with this session.
*
* @return The tenant identifier associated with this session, or {@code null}
* @since 6.4
*/
Object getTenantIdentifierValue();
/**
* End the session by releasing the JDBC connection and cleaning up.
*

View File

@ -36,6 +36,18 @@ public interface StatelessSessionBuilder {
* @param tenantIdentifier The tenant identifier.
*
* @return {@code this}, for method chaining
* @deprecated Use {@link #tenantIdentifier(Object)} instead
*/
@Deprecated(forRemoval = true)
StatelessSessionBuilder tenantIdentifier(String tenantIdentifier);
/**
* Define the tenant identifier to be associated with the opened session.
*
* @param tenantIdentifier The tenant identifier.
*
* @return {@code this}, for method chaining
* @since 6.4
*/
StatelessSessionBuilder tenantIdentifier(Object tenantIdentifier);
}

View File

@ -55,23 +55,7 @@ public class TenantIdBinder implements AttributeBinder<TenantId> {
FILTER_NAME,
"",
singletonMap( PARAMETER_NAME, tenantIdType )
) {
// unfortunately the old APIs only accept String for a tenantId, so parse it
@Override
public Object processArgument(Object value) {
if (value==null) {
return null;
}
else if (value instanceof String) {
return getParameterJdbcMapping( PARAMETER_NAME )
.getJavaTypeDescriptor()
.fromString((String) value);
}
else {
return value;
}
}
}
)
);
}
else {
@ -89,27 +73,27 @@ public class TenantIdBinder implements AttributeBinder<TenantId> {
}
}
persistentClass.addFilter(
FILTER_NAME,
columnNameOrFormula(property)
+ " = :"
+ PARAMETER_NAME,
true,
emptyMap(),
emptyMap()
);
FILTER_NAME,
columnNameOrFormula( property )
+ " = :"
+ PARAMETER_NAME,
true,
emptyMap(),
emptyMap()
);
property.resetUpdateable(false);
property.resetOptional(false);
property.resetUpdateable( false );
property.resetOptional( false );
}
private String columnNameOrFormula(Property property) {
if ( property.getColumnSpan()!=1 ) {
throw new MappingException("@TenantId attribute must be mapped to a single column or formula");
if ( property.getColumnSpan() != 1 ) {
throw new MappingException( "@TenantId attribute must be mapped to a single column or formula" );
}
Selectable selectable = property.getSelectables().get(0);
Selectable selectable = property.getSelectables().get( 0 );
return selectable.isFormula()
? ((Formula) selectable).getFormula()
: ((Column) selectable).getName();
? ( (Formula) selectable ).getFormula()
: ( (Column) selectable ).getName();
}
}

View File

@ -377,7 +377,7 @@ public interface SessionFactoryBuilder {
*
* @see org.hibernate.cfg.AvailableSettings#MULTI_TENANT_IDENTIFIER_RESOLVER
*/
SessionFactoryBuilder applyCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver resolver);
SessionFactoryBuilder applyCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver<?> resolver);
/**
* If using the built-in JTA-based

View File

@ -262,7 +262,7 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
}
@Override
public SessionFactoryBuilder applyCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver resolver) {
public SessionFactoryBuilder applyCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver<?> resolver) {
this.optionsBuilder.applyCurrentTenantIdentifierResolver( resolver );
return this;
}

View File

@ -206,7 +206,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
// multi-tenancy
private boolean multiTenancyEnabled;
private CurrentTenantIdentifierResolver currentTenantIdentifierResolver;
private CurrentTenantIdentifierResolver<Object> currentTenantIdentifierResolver;
// Queries
private SqmFunctionRegistry sqmFunctionRegistry;
@ -1008,7 +1008,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
}
@Override
public CurrentTenantIdentifierResolver getCurrentTenantIdentifierResolver() {
public CurrentTenantIdentifierResolver<Object> getCurrentTenantIdentifierResolver() {
return currentTenantIdentifierResolver;
}
@ -1390,8 +1390,9 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
this.multiTenancyEnabled = enabled;
}
public void applyCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver resolver) {
this.currentTenantIdentifierResolver = resolver;
public void applyCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver<?> resolver) {
//noinspection unchecked
this.currentTenantIdentifierResolver = (CurrentTenantIdentifierResolver<Object>) resolver;
}
public void enableNamedQueryCheckingOnStartup(boolean enabled) {

View File

@ -210,7 +210,7 @@ public abstract class AbstractDelegatingSessionFactoryBuilder<T extends SessionF
}
@Override
public T applyCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver resolver) {
public T applyCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver<?> resolver) {
delegate.applyCurrentTenantIdentifierResolver( resolver );
return getThis();
}

View File

@ -35,6 +35,7 @@ import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
import org.hibernate.query.sqm.sql.SqmTranslatorFactory;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.hibernate.resource.jdbc.spi.StatementInspector;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.format.FormatMapper;
/**
@ -213,10 +214,15 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp
}
@Override
public CurrentTenantIdentifierResolver getCurrentTenantIdentifierResolver() {
public CurrentTenantIdentifierResolver<Object> getCurrentTenantIdentifierResolver() {
return delegate.getCurrentTenantIdentifierResolver();
}
@Override
public JavaType<Object> getDefaultTenantIdentifierJavaType() {
return delegate.getDefaultTenantIdentifierJavaType();
}
@Override
public boolean isJtaTrackByThread() {
return delegate.isJtaTrackByThread();

View File

@ -32,6 +32,8 @@ import org.hibernate.query.NullPrecedence;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.hibernate.resource.jdbc.spi.StatementInspector;
import org.hibernate.stat.Statistics;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.ObjectJavaType;
import org.hibernate.type.format.FormatMapper;
/**
@ -154,7 +156,7 @@ public interface SessionFactoryOptions extends QueryEngineOptions {
boolean isMultiTenancyEnabled();
CurrentTenantIdentifierResolver getCurrentTenantIdentifierResolver();
CurrentTenantIdentifierResolver<Object> getCurrentTenantIdentifierResolver();
boolean isJtaTrackByThread();
@ -341,4 +343,14 @@ public interface SessionFactoryOptions extends QueryEngineOptions {
*/
@Incubating
FormatMapper getXmlFormatMapper();
/**
* The default tenant identifier java type to use, in case no explicit tenant identifier property is defined.
*
* @since 6.4
*/
@Incubating
default JavaType<Object> getDefaultTenantIdentifierJavaType() {
return ObjectJavaType.INSTANCE;
}
}

View File

@ -73,12 +73,11 @@ public class DefaultCacheKeysFactory implements CacheKeysFactory {
Object naturalIdValues,
EntityPersister persister,
SharedSessionContractImplementor session) {
NaturalIdCacheKey.NaturalIdCacheKeyBuilder builder = new NaturalIdCacheKey.NaturalIdCacheKeyBuilder(
return NaturalIdCacheKey.from(
naturalIdValues,
persister,
session
);
return builder.build();
}
public static Object staticGetEntityId(Object cacheKey) {

View File

@ -6,18 +6,14 @@
*/
package org.hibernate.cache.internal;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.hibernate.Internal;
import org.hibernate.cache.MutableCacheKeyBuilder;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.ValueHolder;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.descriptor.java.JavaType;
/**
* Defines a key for caching natural identifier resolutions into the second level cache.
@ -33,88 +29,38 @@ public class NaturalIdCacheKey implements Serializable {
private final String entityName;
private final String tenantId;
private final int hashCode;
// "transient" is important here -- NaturalIdCacheKey needs to be Serializable
private transient ValueHolder<String> toString;
public static class NaturalIdCacheKeyBuilder implements MutableCacheKeyBuilder {
private final String entityName;
private final String tenantIdentifier;
private final List<Object> values;
private int hashCode;
public NaturalIdCacheKeyBuilder(
Object naturalIdValues,
EntityPersister persister,
String entityName,
SharedSessionContractImplementor session) {
this.entityName = entityName;
this.tenantIdentifier = session.getTenantIdentifier();
values = new ArrayList<>();
persister.getNaturalIdMapping().addToCacheKey( this, naturalIdValues, session );
}
public NaturalIdCacheKeyBuilder(
Object naturalIdValues,
EntityPersister persister,
SharedSessionContractImplementor session) {
this( naturalIdValues, persister, persister.getRootEntityName(), session );
}
@Override
public void addValue(Object value) {
values.add( value );
}
@Override
public void addHashCode(int hashCode) {
this.hashCode = 37 * this.hashCode + hashCode;
}
@Override
public NaturalIdCacheKey build() {
return new NaturalIdCacheKey(
values.toArray( new Object[0] ),
entityName,
tenantIdentifier,
hashCode
);
}
}
@Internal
public NaturalIdCacheKey(Object naturalIdValues, String entityName, String tenantId, int hashCode) {
private NaturalIdCacheKey(Object naturalIdValues, String entityName, String tenantId, int hashCode) {
this.naturalIdValues = naturalIdValues;
this.entityName = entityName;
this.tenantId = tenantId;
this.hashCode = hashCode;
initTransients();
}
private void initTransients() {
this.toString = new ValueHolder<>(
() -> {
//Complex toString is needed as naturalIds for entities are not simply based on a single value like primary keys
//the only same way to differentiate the keys is to include the disassembled values in the string.
final StringBuilder toStringBuilder = new StringBuilder().append( entityName ).append( "##NaturalId[" );
if ( naturalIdValues instanceof Object[] ) {
final Object[] values = (Object[]) naturalIdValues;
for ( int i = 0; i < values.length; i++ ) {
toStringBuilder.append( values[ i ] );
if ( i + 1 < values.length ) {
toStringBuilder.append( ", " );
}
}
}
else {
toStringBuilder.append( naturalIdValues );
}
return toStringBuilder.toString();
}
public static NaturalIdCacheKey from(
Object naturalIdValues,
EntityPersister persister,
String entityName,
SharedSessionContractImplementor session) {
final NaturalIdMapping naturalIdMapping = persister.getNaturalIdMapping();
final NaturalIdCacheKeyBuilder builder = new NaturalIdCacheKeyBuilder(
entityName,
session.getTenantIdentifier(),
naturalIdMapping.getJdbcTypeCount()
);
final JavaType<Object> tenantIdentifierJavaType = session.getFactory().getTenantIdentifierJavaType();
final Object tenantId = session.getTenantIdentifierValue();
// Add the tenant id to the hash code
builder.addHashCode( tenantId == null ? 0 : tenantIdentifierJavaType.extractHashCode( tenantId ) );
naturalIdMapping.addToCacheKey( builder, naturalIdValues, session );
return builder.build();
}
public static NaturalIdCacheKey from(
Object naturalIdValues,
EntityPersister persister,
SharedSessionContractImplementor session) {
return from( naturalIdValues, persister, persister.getRootEntityName(), session );
}
@SuppressWarnings( {"UnusedDeclaration"})
@ -132,11 +78,6 @@ public class NaturalIdCacheKey implements Serializable {
return naturalIdValues;
}
@Override
public String toString() {
return toString.getValue();
}
@Override
public int hashCode() {
return this.hashCode;
@ -162,9 +103,59 @@ public class NaturalIdCacheKey implements Serializable {
&& Objects.deepEquals( this.naturalIdValues, other.naturalIdValues );
}
private void readObject(ObjectInputStream ois)
throws ClassNotFoundException, IOException {
ois.defaultReadObject();
initTransients();
@Override
public String toString() {
//Complex toString is needed as naturalIds for entities are not simply based on a single value like primary keys
//the only same way to differentiate the keys is to include the disassembled values in the string.
final StringBuilder toStringBuilder = new StringBuilder().append( entityName ).append( "##NaturalId[" );
if ( naturalIdValues instanceof Object[] ) {
final Object[] values = (Object[]) naturalIdValues;
for ( int i = 0; i < values.length; i++ ) {
toStringBuilder.append( values[ i ] );
if ( i + 1 < values.length ) {
toStringBuilder.append( ", " );
}
}
}
else {
toStringBuilder.append( naturalIdValues );
}
return toStringBuilder.toString();
}
private static class NaturalIdCacheKeyBuilder implements MutableCacheKeyBuilder {
private final String entityName;
private final String tenantIdentifier;
private final Object[] naturalIdValues;
private int hashCode;
private int naturalIdValueIndex;
public NaturalIdCacheKeyBuilder(String entityName, String tenantIdentifier, int naturalIdValueCount) {
this.entityName = entityName;
this.tenantIdentifier = tenantIdentifier;
this.naturalIdValues = new Object[naturalIdValueCount];
}
@Override
public void addValue(Object value) {
naturalIdValues[naturalIdValueIndex++] = value;
}
@Override
public void addHashCode(int hashCode) {
this.hashCode = 37 * this.hashCode + hashCode;
}
@Override
public NaturalIdCacheKey build() {
return new NaturalIdCacheKey(
naturalIdValues,
entityName,
tenantIdentifier,
hashCode
);
}
}
}

View File

@ -37,13 +37,12 @@ public class SimpleCacheKeysFactory implements CacheKeysFactory {
EntityPersister persister,
SharedSessionContractImplementor session) {
// natural ids always need to be wrapped
NaturalIdCacheKey.NaturalIdCacheKeyBuilder builder = new NaturalIdCacheKey.NaturalIdCacheKeyBuilder(
return NaturalIdCacheKey.from(
naturalIdValues,
persister,
null,
session
);
return builder.build();
}
@Override

View File

@ -24,8 +24,7 @@ import org.hibernate.query.spi.QueryParameterBindings;
* Note that the fields of this object must contain every explicit and
* implicit setting and parameter argument that affects the result list
* of the query, including things like the {@link #maxRows limit} and
* {@link #firstRow offset}, {@link #tenantIdentifier current tenant id},
* and {@link #enabledFilterNames enabled filters}.
* {@link #firstRow offset} and {@link #enabledFilterNames enabled filters}.
*
* @author Gavin King
* @author Steve Ebersole
@ -41,7 +40,7 @@ public class QueryKey implements Serializable {
String sqlQueryString,
Limit limit,
QueryParameterBindings parameterBindings,
SharedSessionContractImplementor persistenceContext) {
SharedSessionContractImplementor session) {
// todo (6.0) : here is where we should centralize cacheable-or-not
// if this method returns null, the query should be considered un-cacheable
//
@ -52,11 +51,10 @@ public class QueryKey implements Serializable {
return new QueryKey(
sqlQueryString,
parameterBindings.generateQueryKeyMemento( persistenceContext ),
parameterBindings.generateQueryKeyMemento( session ),
limitToUse.getFirstRow(),
limitToUse.getMaxRows(),
persistenceContext.getTenantIdentifier(),
persistenceContext.getLoadQueryInfluencers().getEnabledFilterNames()
session.getLoadQueryInfluencers().getEnabledFilterNames()
);
}
@ -65,7 +63,6 @@ public class QueryKey implements Serializable {
private final ParameterBindingsMemento parameterBindingsMemento;
private final Integer firstRow;
private final Integer maxRows;
private final String tenantIdentifier;
private final String[] enabledFilterNames;
/**
@ -79,13 +76,11 @@ public class QueryKey implements Serializable {
ParameterBindingsMemento parameterBindingsMemento,
Integer firstRow,
Integer maxRows,
String tenantIdentifier,
Set<String> enabledFilterNames) {
this.sqlQueryString = sql;
this.parameterBindingsMemento = parameterBindingsMemento;
this.firstRow = firstRow;
this.maxRows = maxRows;
this.tenantIdentifier = tenantIdentifier;
this.enabledFilterNames = enabledFilterNames.toArray( String[]::new );
this.hashCode = generateHashCode();
}
@ -109,7 +104,6 @@ public class QueryKey implements Serializable {
// Don't include the firstRow and maxRows in the hash as these values are rarely useful for query caching
// result = 37 * result + ( firstRow==null ? 0 : firstRow );
// result = 37 * result + ( maxRows==null ? 0 : maxRows );
result = 37 * result + ( tenantIdentifier==null ? 0 : tenantIdentifier.hashCode() );
result = 37 * result + parameterBindingsMemento.hashCode();
result = 37 * result + Arrays.hashCode( enabledFilterNames );
return result;
@ -133,10 +127,6 @@ public class QueryKey implements Serializable {
return false;
}
if ( ! Objects.equals( tenantIdentifier, that.tenantIdentifier ) ) {
return false;
}
if ( ! Objects.equals( firstRow, that.firstRow )
|| ! Objects.equals( maxRows, that.maxRows ) ) {
return false;

View File

@ -155,7 +155,7 @@ public class Configuration {
private Interceptor interceptor;
private SessionFactoryObserver sessionFactoryObserver;
private StatementInspector statementInspector;
private CurrentTenantIdentifierResolver currentTenantIdentifierResolver;
private CurrentTenantIdentifierResolver<Object> currentTenantIdentifierResolver;
private CustomEntityDirtinessStrategy customEntityDirtinessStrategy;
private ColumnOrderingStrategy columnOrderingStrategy;
private Properties properties;
@ -792,7 +792,7 @@ public class Configuration {
/**
* The {@link CurrentTenantIdentifierResolver}, if any, that was added to this configuration.
*/
public CurrentTenantIdentifierResolver getCurrentTenantIdentifierResolver() {
public CurrentTenantIdentifierResolver<Object> getCurrentTenantIdentifierResolver() {
return currentTenantIdentifierResolver;
}
@ -801,7 +801,7 @@ public class Configuration {
*
* @return {@code this} for method chaining
*/
public Configuration setCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver currentTenantIdentifierResolver) {
public Configuration setCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver<Object> currentTenantIdentifierResolver) {
this.currentTenantIdentifierResolver = currentTenantIdentifierResolver;
return this;
}

View File

@ -6,13 +6,12 @@
*/
package org.hibernate.context.spi;
import java.util.Objects;
import org.hibernate.Session;
import org.hibernate.SessionBuilder;
import org.hibernate.context.TenantIdentifierMismatchException;
import org.hibernate.engine.spi.SessionBuilderImplementor;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.type.descriptor.java.JavaType;
/**
* Base support for {@link CurrentSessionContext} implementors.
@ -37,7 +36,7 @@ public abstract class AbstractCurrentSessionContext implements CurrentSessionCon
protected SessionBuilder baseSessionBuilder() {
final SessionBuilderImplementor builder = factory.withOptions();
final CurrentTenantIdentifierResolver resolver = factory.getCurrentTenantIdentifierResolver();
final CurrentTenantIdentifierResolver<Object> resolver = factory.getCurrentTenantIdentifierResolver();
if ( resolver != null ) {
builder.tenantIdentifier( resolver.resolveCurrentTenantIdentifier() );
}
@ -45,16 +44,17 @@ public abstract class AbstractCurrentSessionContext implements CurrentSessionCon
}
protected void validateExistingSession(Session existingSession) {
final CurrentTenantIdentifierResolver resolver = factory.getCurrentTenantIdentifierResolver();
final CurrentTenantIdentifierResolver<Object> resolver = factory.getCurrentTenantIdentifierResolver();
if ( resolver != null && resolver.validateExistingCurrentSessions() ) {
final String current = resolver.resolveCurrentTenantIdentifier();
if ( !Objects.equals( existingSession.getTenantIdentifier(), current ) ) {
final Object currentValue = resolver.resolveCurrentTenantIdentifier();
final JavaType<Object> tenantIdentifierJavaType = factory.getTenantIdentifierJavaType();
if ( !tenantIdentifierJavaType.areEqual( currentValue, existingSession.getTenantIdentifierValue() ) ) {
throw new TenantIdentifierMismatchException(
String.format(
"Reported current tenant identifier [%s] did not match tenant identifier from " +
"existing session [%s]",
current,
existingSession.getTenantIdentifier()
tenantIdentifierJavaType.toString( currentValue ),
tenantIdentifierJavaType.toString( existingSession.getTenantIdentifierValue() )
)
);
}

View File

@ -20,13 +20,13 @@ package org.hibernate.context.spi;
*
* @author Steve Ebersole
*/
public interface CurrentTenantIdentifierResolver {
public interface CurrentTenantIdentifierResolver<T> {
/**
* Resolve the current tenant identifier.
*
* @return The current tenant identifier
*/
String resolveCurrentTenantIdentifier();
T resolveCurrentTenantIdentifier();
/**
* Should we validate that the tenant identifier of a "current sessions" that
@ -47,7 +47,7 @@ public interface CurrentTenantIdentifierResolver {
*
* @return true is this is root tenant
*/
default boolean isRoot(String tenantId) {
default boolean isRoot(T tenantId) {
return false;
}
}

View File

@ -24,7 +24,7 @@ import org.jboss.logging.Logger;
*
* @author Steve Ebersole
*/
public class MultiTenantConnectionProviderInitiator implements StandardServiceInitiator<MultiTenantConnectionProvider> {
public class MultiTenantConnectionProviderInitiator implements StandardServiceInitiator<MultiTenantConnectionProvider<?>> {
private static final Logger log = Logger.getLogger( MultiTenantConnectionProviderInitiator.class );
/**
@ -33,12 +33,13 @@ public class MultiTenantConnectionProviderInitiator implements StandardServiceIn
public static final MultiTenantConnectionProviderInitiator INSTANCE = new MultiTenantConnectionProviderInitiator();
@Override
public Class<MultiTenantConnectionProvider> getServiceInitiated() {
return MultiTenantConnectionProvider.class;
public Class<MultiTenantConnectionProvider<?>> getServiceInitiated() {
//noinspection unchecked
return (Class<MultiTenantConnectionProvider<?>>) (Class<?>) MultiTenantConnectionProvider.class;
}
@Override
public MultiTenantConnectionProvider initiateService(Map<String, Object> configurationValues, ServiceRegistryImplementor registry) {
public MultiTenantConnectionProvider<?> initiateService(Map<String, Object> configurationValues, ServiceRegistryImplementor registry) {
if ( !configurationValues.containsKey( AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER ) ) {
// nothing to do, but given the separate hierarchies have to handle this here.
return null;
@ -50,20 +51,20 @@ public class MultiTenantConnectionProviderInitiator implements StandardServiceIn
// DataSourceBasedMultiTenantConnectionProviderImpl
final Object dataSourceConfigValue = configurationValues.get( AvailableSettings.DATASOURCE );
if ( dataSourceConfigValue instanceof String ) {
return new DataSourceBasedMultiTenantConnectionProviderImpl();
return new DataSourceBasedMultiTenantConnectionProviderImpl<>();
}
return null;
}
if ( configValue instanceof MultiTenantConnectionProvider ) {
return (MultiTenantConnectionProvider) configValue;
if ( configValue instanceof MultiTenantConnectionProvider<?> ) {
return (MultiTenantConnectionProvider<?>) configValue;
}
else {
final Class<MultiTenantConnectionProvider> implClass;
final Class<MultiTenantConnectionProvider<?>> implClass;
if ( configValue instanceof Class ) {
@SuppressWarnings("unchecked")
Class<MultiTenantConnectionProvider> clazz = (Class<MultiTenantConnectionProvider>) configValue;
Class<MultiTenantConnectionProvider<?>> clazz = (Class<MultiTenantConnectionProvider<?>>) configValue;
implClass = clazz;
}
else {

View File

@ -16,9 +16,9 @@ import org.hibernate.service.UnknownUnwrapTypeException;
* Basic support for implementations of {@link MultiTenantConnectionProvider} based on DataSources.
* @author Steve Ebersole
*/
public abstract class AbstractDataSourceBasedMultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
public abstract class AbstractDataSourceBasedMultiTenantConnectionProviderImpl<T> implements MultiTenantConnectionProvider<T> {
protected abstract DataSource selectAnyDataSource();
protected abstract DataSource selectDataSource(String tenantIdentifier);
protected abstract DataSource selectDataSource(T tenantIdentifier);
@Override
public Connection getAnyConnection() throws SQLException {
@ -31,12 +31,12 @@ public abstract class AbstractDataSourceBasedMultiTenantConnectionProviderImpl i
}
@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
public Connection getConnection(T tenantIdentifier) throws SQLException {
return selectDataSource( tenantIdentifier ).getConnection();
}
@Override
public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
public void releaseConnection(T tenantIdentifier, Connection connection) throws SQLException {
connection.close();
}

View File

@ -20,9 +20,9 @@ import org.hibernate.service.UnknownUnwrapTypeException;
*
* @author Steve Ebersole
*/
public abstract class AbstractMultiTenantConnectionProvider implements MultiTenantConnectionProvider {
public abstract class AbstractMultiTenantConnectionProvider<T> implements MultiTenantConnectionProvider<T> {
protected abstract ConnectionProvider getAnyConnectionProvider();
protected abstract ConnectionProvider selectConnectionProvider(String tenantIdentifier);
protected abstract ConnectionProvider selectConnectionProvider(T tenantIdentifier);
@Override
public Connection getAnyConnection() throws SQLException {
@ -35,12 +35,12 @@ public abstract class AbstractMultiTenantConnectionProvider implements MultiTena
}
@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
public Connection getConnection(T tenantIdentifier) throws SQLException {
return selectConnectionProvider( tenantIdentifier ).getConnection();
}
@Override
public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
public void releaseConnection(T tenantIdentifier, Connection connection) throws SQLException {
selectConnectionProvider( tenantIdentifier ).closeConnection( connection );
}

View File

@ -38,13 +38,13 @@ import static org.hibernate.cfg.MultiTenancySettings.TENANT_IDENTIFIER_TO_USE_FO
*
* @author Steve Ebersole
*/
public class DataSourceBasedMultiTenantConnectionProviderImpl
extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl
public class DataSourceBasedMultiTenantConnectionProviderImpl<T>
extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl<T>
implements ServiceRegistryAwareService, Stoppable {
private Map<String,DataSource> dataSourceMap;
private Map<T, DataSource> dataSourceMap;
private JndiService jndiService;
private String tenantIdentifierForAny;
private T tenantIdentifierForAny;
private String baseJndiNamespace;
@Override
@ -53,7 +53,7 @@ public class DataSourceBasedMultiTenantConnectionProviderImpl
}
@Override
protected DataSource selectDataSource(String tenantIdentifier) {
protected DataSource selectDataSource(T tenantIdentifier) {
DataSource dataSource = dataSourceMap().get( tenantIdentifier );
if ( dataSource == null ) {
dataSource = (DataSource) jndiService.locate( baseJndiNamespace + '/' + tenantIdentifier );
@ -62,7 +62,7 @@ public class DataSourceBasedMultiTenantConnectionProviderImpl
return dataSource;
}
private Map<String,DataSource> dataSourceMap() {
private Map<T, DataSource> dataSourceMap() {
if ( dataSourceMap == null ) {
dataSourceMap = new ConcurrentHashMap<>();
}
@ -92,12 +92,12 @@ public class DataSourceBasedMultiTenantConnectionProviderImpl
if ( namedObject instanceof DataSource ) {
final int loc = jndiName.lastIndexOf( '/' );
this.baseJndiNamespace = jndiName.substring( 0, loc );
this.tenantIdentifierForAny = jndiName.substring( loc + 1 );
this.tenantIdentifierForAny = (T) jndiName.substring( loc + 1 );
dataSourceMap().put( tenantIdentifierForAny, (DataSource) namedObject );
}
else if ( namedObject instanceof Context ) {
this.baseJndiNamespace = jndiName;
this.tenantIdentifierForAny = (String) serviceRegistry.getService( ConfigurationService.class )
this.tenantIdentifierForAny = (T) serviceRegistry.getService( ConfigurationService.class )
.getSettings()
.get( TENANT_IDENTIFIER_TO_USE_FOR_ANY_KEY );
if ( tenantIdentifierForAny == null ) {

View File

@ -22,12 +22,14 @@ import org.hibernate.service.spi.Wrapped;
* An application usually implements its own custom {@code MultiTenantConnectionProvider}
* by subclassing {@link AbstractMultiTenantConnectionProvider}.
*
* @param <T> The tenant identifier type
*
* @author Steve Ebersole
*
* @see AbstractMultiTenantConnectionProvider
* @see org.hibernate.cfg.AvailableSettings#MULTI_TENANT_CONNECTION_PROVIDER
*/
public interface MultiTenantConnectionProvider extends Service, Wrapped {
public interface MultiTenantConnectionProvider<T> extends Service, Wrapped {
/**
* Allows access to the database metadata of the underlying database(s) in situations
* where we do not have a tenant id (like startup processing, for example).
@ -57,7 +59,7 @@ public interface MultiTenantConnectionProvider extends Service, Wrapped {
* @throws SQLException Indicates a problem opening a connection
* @throws org.hibernate.HibernateException Indicates a problem otherwise obtaining a connection.
*/
Connection getConnection(String tenantIdentifier) throws SQLException;
Connection getConnection(T tenantIdentifier) throws SQLException;
/**
* Release a connection from Hibernate use.
@ -68,7 +70,7 @@ public interface MultiTenantConnectionProvider extends Service, Wrapped {
* @throws SQLException Indicates a problem closing the connection
* @throws org.hibernate.HibernateException Indicates a problem otherwise releasing a connection.
*/
void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException;
void releaseConnection(T tenantIdentifier, Connection connection) throws SQLException;
/**
* Does this connection provider support aggressive release of JDBC connections and later

View File

@ -392,7 +392,7 @@ public class JdbcEnvironmentInitiator implements StandardServiceInitiator<JdbcEn
return new ConnectionProviderJdbcConnectionAccess( connectionProvider );
}
else {
final MultiTenantConnectionProvider multiTenantConnectionProvider = registry.getService( MultiTenantConnectionProvider.class );
final MultiTenantConnectionProvider<?> multiTenantConnectionProvider = registry.getService( MultiTenantConnectionProvider.class );
return new MultiTenantConnectionProviderJdbcConnectionAccess( multiTenantConnectionProvider );
}
}
@ -403,7 +403,7 @@ public class JdbcEnvironmentInitiator implements StandardServiceInitiator<JdbcEn
return new ConnectionProviderJdbcConnectionAccess( connectionProvider );
}
else {
final MultiTenantConnectionProvider multiTenantConnectionProvider = registry.getService( MultiTenantConnectionProvider.class );
final MultiTenantConnectionProvider<?> multiTenantConnectionProvider = registry.getService( MultiTenantConnectionProvider.class );
return new MultiTenantConnectionProviderJdbcConnectionAccess( multiTenantConnectionProvider );
}
}
@ -436,13 +436,13 @@ public class JdbcEnvironmentInitiator implements StandardServiceInitiator<JdbcEn
}
public static class MultiTenantConnectionProviderJdbcConnectionAccess implements JdbcConnectionAccess {
private final MultiTenantConnectionProvider connectionProvider;
private final MultiTenantConnectionProvider<?> connectionProvider;
public MultiTenantConnectionProviderJdbcConnectionAccess(MultiTenantConnectionProvider connectionProvider) {
public MultiTenantConnectionProviderJdbcConnectionAccess(MultiTenantConnectionProvider<?> connectionProvider) {
this.connectionProvider = connectionProvider;
}
public MultiTenantConnectionProvider getConnectionProvider() {
public MultiTenantConnectionProvider<?> getConnectionProvider() {
return connectionProvider;
}

View File

@ -87,6 +87,12 @@ public abstract class AbstractDelegatingSessionBuilder implements SessionBuilder
return this;
}
@Override
public SessionBuilder tenantIdentifier(Object tenantIdentifier) {
delegate.tenantIdentifier( tenantIdentifier );
return this;
}
@Override
public SessionBuilder eventListeners(SessionEventListener... listeners) {
delegate.eventListeners( listeners );

View File

@ -123,6 +123,12 @@ public abstract class AbstractDelegatingSharedSessionBuilder implements SharedSe
return this;
}
@Override
public SharedSessionBuilder tenantIdentifier(Object tenantIdentifier) {
delegate.tenantIdentifier( tenantIdentifier );
return this;
}
@Override
public SharedSessionBuilder eventListeners(SessionEventListener... listeners) {
delegate.eventListeners( listeners );

View File

@ -110,6 +110,11 @@ public class SessionDelegatorBaseImpl implements SessionImplementor {
return delegate.getTenantIdentifier();
}
@Override
public Object getTenantIdentifierValue() {
return delegate.getTenantIdentifierValue();
}
@Override
public UUID getSessionIdentifier() {
return delegate.getSessionIdentifier();

View File

@ -49,6 +49,7 @@ import org.hibernate.stat.spi.StatisticsImplementor;
import org.hibernate.generator.Generator;
import org.hibernate.type.Type;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.spi.TypeConfiguration;
/**
@ -260,10 +261,15 @@ public class SessionFactoryDelegatingImpl implements SessionFactoryImplementor,
}
@Override
public CurrentTenantIdentifierResolver getCurrentTenantIdentifierResolver() {
public CurrentTenantIdentifierResolver<Object> getCurrentTenantIdentifierResolver() {
return delegate.getCurrentTenantIdentifierResolver();
}
@Override
public JavaType<Object> getTenantIdentifierJavaType() {
return delegate.getTenantIdentifierJavaType();
}
@Override
public FastSessionServices getFastSessionServices() {
return delegate.getFastSessionServices();

View File

@ -36,6 +36,7 @@ import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.stat.spi.StatisticsImplementor;
import org.hibernate.generator.Generator;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.spi.TypeConfiguration;
/**
@ -143,7 +144,14 @@ public interface SessionFactoryImplementor
CustomEntityDirtinessStrategy getCustomEntityDirtinessStrategy();
//todo make a Service ?
CurrentTenantIdentifierResolver getCurrentTenantIdentifierResolver();
CurrentTenantIdentifierResolver<Object> getCurrentTenantIdentifierResolver();
/**
* The java type to use for a tenant identifier.
*
* @since 6.4
*/
JavaType<Object> getTenantIdentifierJavaType();
/**
* @return the FastSessionServices instance associated with this SessionFactory

View File

@ -594,6 +594,11 @@ public class SessionLazyDelegator implements Session {
return this.lazySession.get().getTenantIdentifier();
}
@Override
public Object getTenantIdentifierValue() {
return this.lazySession.get().getTenantIdentifierValue();
}
@Override
public void close() throws HibernateException {
this.lazySession.get().close();

View File

@ -73,6 +73,11 @@ public class SharedSessionDelegatorBaseImpl implements SharedSessionContractImpl
return delegate.getTenantIdentifier();
}
@Override
public Object getTenantIdentifierValue() {
return delegate.getTenantIdentifierValue();
}
private QueryProducerImplementor queryDelegate() {
return delegate;
}

View File

@ -88,13 +88,15 @@ public class JfrEventManager {
public static void completeJdbcConnectionAcquisitionEvent(
JdbcConnectionAcquisitionEvent jdbcConnectionAcquisitionEvent,
SharedSessionContractImplementor session,
String tenantId) {
Object tenantId) {
if ( jdbcConnectionAcquisitionEvent.isEnabled() ) {
jdbcConnectionAcquisitionEvent.end();
if ( jdbcConnectionAcquisitionEvent.shouldCommit() ) {
jdbcConnectionAcquisitionEvent.executionTime = getExecutionTime( jdbcConnectionAcquisitionEvent.startedAt );
jdbcConnectionAcquisitionEvent.sessionIdentifier = getSessionIdentifier( session );
jdbcConnectionAcquisitionEvent.tenantIdentifier = tenantId;
jdbcConnectionAcquisitionEvent.tenantIdentifier = tenantId == null ? null : session.getFactory()
.getTenantIdentifierJavaType()
.toString( tenantId );
jdbcConnectionAcquisitionEvent.commit();
}
}
@ -112,13 +114,15 @@ public class JfrEventManager {
public static void completeJdbcConnectionReleaseEvent(
JdbcConnectionReleaseEvent jdbcConnectionReleaseEvent,
SharedSessionContractImplementor session,
String tenantId) {
Object tenantId) {
if ( jdbcConnectionReleaseEvent.isEnabled() ) {
jdbcConnectionReleaseEvent.end();
if ( jdbcConnectionReleaseEvent.shouldCommit() ) {
jdbcConnectionReleaseEvent.executionTime = getExecutionTime( jdbcConnectionReleaseEvent.startedAt );
jdbcConnectionReleaseEvent.sessionIdentifier = getSessionIdentifier( session );
jdbcConnectionReleaseEvent.tenantIdentifier = tenantId;
jdbcConnectionReleaseEvent.tenantIdentifier = tenantId == null ? null : session.getFactory()
.getTenantIdentifierJavaType()
.toString( tenantId );
jdbcConnectionReleaseEvent.commit();
}
}

View File

@ -6,23 +6,21 @@
*/
package org.hibernate.generator.internal;
import org.hibernate.MappingException;
import java.lang.reflect.Member;
import java.util.EnumSet;
import org.hibernate.PropertyValueException;
import org.hibernate.annotations.TenantId;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.BeforeExecutionGenerator;
import org.hibernate.generator.EventType;
import org.hibernate.generator.EventTypeSets;
import org.hibernate.generator.BeforeExecutionGenerator;
import org.hibernate.generator.GeneratorCreationContext;
import org.hibernate.type.descriptor.java.JavaType;
import java.lang.reflect.Member;
import java.util.EnumSet;
import static org.hibernate.generator.EventTypeSets.INSERT_ONLY;
import static org.hibernate.internal.util.ReflectHelper.getPropertyType;
/**
* A generator that produces the current tenant identifier
@ -34,14 +32,12 @@ public class TenantIdGeneration implements BeforeExecutionGenerator {
private final String entityName;
private final String propertyName;
private final Class<?> propertyType;
public TenantIdGeneration(TenantId annotation, Member member, GeneratorCreationContext context) {
entityName = context.getPersistentClass() == null
? member.getDeclaringClass().getName() //it's an attribute of an embeddable
: context.getPersistentClass().getEntityName();
propertyName = context.getProperty().getName();
propertyType = getPropertyType( member );
}
/**
@ -54,29 +50,27 @@ public class TenantIdGeneration implements BeforeExecutionGenerator {
@Override
public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue, EventType eventType) {
SessionFactoryImplementor sessionFactory = session.getSessionFactory();
JavaType<Object> descriptor = sessionFactory.getTypeConfiguration().getJavaTypeRegistry()
.findDescriptor(propertyType);
if ( descriptor==null ) {
throw new MappingException( "unsupported tenant id property type: " + propertyType.getName() );
}
final SessionFactoryImplementor sessionFactory = session.getSessionFactory();
final JavaType<Object> tenantIdentifierJavaType = sessionFactory.getTenantIdentifierJavaType();
String tenantId = session.getTenantIdentifier(); //unfortunately this is always a string in old APIs
final Object tenantId = session.getTenantIdentifierValue();
if ( currentValue != null ) {
CurrentTenantIdentifierResolver resolver = sessionFactory.getCurrentTenantIdentifierResolver();
if ( resolver!=null && resolver.isRoot(tenantId) ) {
final CurrentTenantIdentifierResolver<Object> resolver = sessionFactory.getCurrentTenantIdentifierResolver();
if ( resolver != null && resolver.isRoot( tenantId ) ) {
// the "root" tenant is allowed to set the tenant id explicitly
return currentValue;
}
String currentTenantId = descriptor.toString(currentValue);
if ( !currentTenantId.equals(tenantId) ) {
if ( !tenantIdentifierJavaType.areEqual( currentValue, tenantId ) ) {
throw new PropertyValueException(
"assigned tenant id differs from current tenant id: "
+ currentTenantId + "!=" + tenantId,
entityName, propertyName
"assigned tenant id differs from current tenant id: " +
tenantIdentifierJavaType.toString( currentValue ) +
"!=" +
tenantIdentifierJavaType.toString( tenantId ),
entityName,
propertyName
);
}
}
return tenantId == null ? null : descriptor.fromString(tenantId); //convert to the model type
return tenantId;
}
}

View File

@ -152,7 +152,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
private final Interceptor interceptor;
private final String tenantIdentifier;
private final Object tenantIdentifier;
private final TimeZone jdbcTimeZone;
// mutable state
@ -257,8 +257,8 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
);
}
private String getTenantId( SessionFactoryImpl factory, SessionCreationOptions options ) {
final String tenantIdentifier = options.getTenantIdentifier();
private Object getTenantId( SessionFactoryImpl factory, SessionCreationOptions options ) {
final Object tenantIdentifier = options.getTenantIdentifierValue();
if ( factory.getSessionFactoryOptions().isMultiTenancyEnabled() && tenantIdentifier == null ) {
throw new HibernateException( "SessionFactory configured for multi-tenancy, but no tenant identifier specified" );
}
@ -367,6 +367,14 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
@Override
public String getTenantIdentifier() {
if ( tenantIdentifier == null ) {
return null;
}
return factory.getTenantIdentifierJavaType().toString( tenantIdentifier );
}
@Override
public Object getTenantIdentifierValue() {
return tenantIdentifier;
}
@ -611,7 +619,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
}
else {
jdbcConnectionAccess = new ContextualJdbcConnectionAccess(
getTenantIdentifier(),
getTenantIdentifierValue(),
getEventListenerManager(),
fastSessionServices.multiTenantConnectionProvider,
this

View File

@ -23,16 +23,16 @@ import org.hibernate.event.jfr.JdbcConnectionReleaseEvent;
* @author Steve Ebersole
*/
public class ContextualJdbcConnectionAccess implements JdbcConnectionAccess, Serializable {
private final String tenantIdentifier;
private final Object tenantIdentifier;
private final SessionEventListener listener;
private final MultiTenantConnectionProvider connectionProvider;
private final MultiTenantConnectionProvider<Object> connectionProvider;
private final SharedSessionContractImplementor session;
public ContextualJdbcConnectionAccess(
String tenantIdentifier,
Object tenantIdentifier,
SessionEventListener listener,
MultiTenantConnectionProvider connectionProvider,
MultiTenantConnectionProvider<Object> connectionProvider,
SharedSessionContractImplementor session) {
this.tenantIdentifier = tenantIdentifier;
this.listener = listener;

View File

@ -158,7 +158,7 @@ public final class FastSessionServices {
final TimeZoneStorageStrategy defaultTimeZoneStorageStrategy;
final boolean requiresMultiTenantConnectionProvider;
final ConnectionProvider connectionProvider;
final MultiTenantConnectionProvider multiTenantConnectionProvider;
final MultiTenantConnectionProvider<Object> multiTenantConnectionProvider;
final ClassLoaderService classLoaderService;
final TransactionCoordinatorBuilder transactionCoordinatorBuilder;
public final JdbcServices jdbcServices;

View File

@ -46,6 +46,8 @@ public interface SessionCreationOptions {
String getTenantIdentifier();
Object getTenantIdentifierValue();
TimeZone getJdbcTimeZone();
/**

View File

@ -37,6 +37,7 @@ import org.hibernate.SessionFactoryObserver;
import org.hibernate.StatelessSession;
import org.hibernate.StatelessSessionBuilder;
import org.hibernate.UnknownFilterException;
import org.hibernate.binder.internal.TenantIdBinder;
import org.hibernate.boot.cfgxml.spi.CfgXmlAccessService;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl;
@ -78,6 +79,7 @@ import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.RootClass;
import org.hibernate.metadata.CollectionMetadata;
import org.hibernate.metamodel.internal.RuntimeMetamodelsImpl;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.model.domain.internal.MappingMetamodelImpl;
import org.hibernate.metamodel.model.domain.spi.JpaMetamodelImplementor;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
@ -111,6 +113,7 @@ import org.hibernate.service.spi.SessionFactoryServiceRegistryFactory;
import org.hibernate.stat.spi.StatisticsImplementor;
import org.hibernate.type.Type;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.spi.TypeConfiguration;
import org.jboss.logging.Logger;
@ -185,6 +188,7 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
private final transient Map<String, Generator> identifierGenerators;
private final transient Map<String, FilterDefinition> filters;
private final transient Map<String, FetchProfile> fetchProfiles;
private final transient JavaType<Object> tenantIdentifierJavaType;
private final transient FastSessionServices fastSessionServices;
private final transient WrapperOptions wrapperOptions;
@ -240,6 +244,17 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
filters = new HashMap<>( bootMetamodel.getFilterDefinitions() );
LOG.debugf( "Session factory constructed with filter configurations : %s", filters );
final FilterDefinition tenantFilter = filters.get( TenantIdBinder.FILTER_NAME );
if ( tenantFilter == null ) {
tenantIdentifierJavaType = options.getDefaultTenantIdentifierJavaType();
}
else {
final JdbcMapping jdbcMapping = tenantFilter.getParameterJdbcMapping( TenantIdBinder.PARAMETER_NAME );
assert jdbcMapping != null;
//noinspection unchecked
tenantIdentifierJavaType = jdbcMapping.getJavaTypeDescriptor();
}
entityNameResolver = new CoordinatingEntityNameResolver( this, getInterceptor() );
schemaManager = new SchemaManagerImpl( this, bootMetamodel );
@ -502,7 +517,7 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
}
private SessionBuilderImpl createDefaultSessionOpenOptionsIfPossible() {
final CurrentTenantIdentifierResolver currentTenantIdentifierResolver = getCurrentTenantIdentifierResolver();
final CurrentTenantIdentifierResolver<Object> currentTenantIdentifierResolver = getCurrentTenantIdentifierResolver();
if ( currentTenantIdentifierResolver == null ) {
return withOptions();
}
@ -707,7 +722,7 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
if ( map != null ) {
//noinspection SuspiciousMethodCalls
final String tenantIdHint = (String) map.get( HINT_TENANT_ID );
final Object tenantIdHint = map.get( HINT_TENANT_ID );
if ( tenantIdHint != null ) {
builder = (SessionBuilderImplementor) builder.tenantIdentifier( tenantIdHint );
}
@ -1198,7 +1213,7 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
private FlushMode flushMode;
private boolean autoClose;
private boolean autoClear;
private String tenantIdentifier;
private Object tenantIdentifier;
private TimeZone jdbcTimeZone;
private boolean explicitNoInterceptor;
private int defaultBatchFetchSize;
@ -1223,7 +1238,7 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
this.defaultBatchFetchSize = sessionFactoryOptions.getDefaultBatchFetchSize();
this.subselectFetchEnabled = sessionFactoryOptions.isSubselectFetchEnabled();
final CurrentTenantIdentifierResolver currentTenantIdentifierResolver = sessionFactory.getCurrentTenantIdentifierResolver();
final CurrentTenantIdentifierResolver<Object> currentTenantIdentifierResolver = sessionFactory.getCurrentTenantIdentifierResolver();
if ( currentTenantIdentifierResolver != null ) {
tenantIdentifier = currentTenantIdentifierResolver.resolveCurrentTenantIdentifier();
}
@ -1293,6 +1308,14 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
@Override
public String getTenantIdentifier() {
if ( tenantIdentifier == null ) {
return null;
}
return sessionFactory.getTenantIdentifierJavaType().toString( tenantIdentifier );
}
@Override
public Object getTenantIdentifierValue() {
return tenantIdentifier;
}
@ -1377,6 +1400,12 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
return this;
}
@Override
public SessionBuilderImpl tenantIdentifier(Object tenantIdentifier) {
this.tenantIdentifier = tenantIdentifier;
return this;
}
@Override
public SessionBuilderImpl eventListeners(SessionEventListener... listeners) {
if ( this.listeners == null ) {
@ -1410,12 +1439,12 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
public static class StatelessSessionBuilderImpl implements StatelessSessionBuilder, SessionCreationOptions {
private final SessionFactoryImpl sessionFactory;
private Connection connection;
private String tenantIdentifier;
private Object tenantIdentifier;
public StatelessSessionBuilderImpl(SessionFactoryImpl sessionFactory) {
this.sessionFactory = sessionFactory;
CurrentTenantIdentifierResolver tenantIdentifierResolver = sessionFactory.getCurrentTenantIdentifierResolver();
CurrentTenantIdentifierResolver<Object> tenantIdentifierResolver = sessionFactory.getCurrentTenantIdentifierResolver();
if ( tenantIdentifierResolver != null ) {
tenantIdentifier = tenantIdentifierResolver.resolveCurrentTenantIdentifier();
}
@ -1438,6 +1467,12 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
return this;
}
@Override
public StatelessSessionBuilder tenantIdentifier(Object tenantIdentifier) {
this.tenantIdentifier = tenantIdentifier;
return this;
}
@Override
public boolean shouldAutoJoinTransactions() {
return true;
@ -1491,6 +1526,14 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
@Override
public String getTenantIdentifier() {
if ( tenantIdentifier == null ) {
return null;
}
return sessionFactory.getTenantIdentifierJavaType().toString( tenantIdentifier );
}
@Override
public Object getTenantIdentifierValue() {
return tenantIdentifier;
}
@ -1516,10 +1559,15 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
}
@Override
public CurrentTenantIdentifierResolver getCurrentTenantIdentifierResolver() {
public CurrentTenantIdentifierResolver<Object> getCurrentTenantIdentifierResolver() {
return getSessionFactoryOptions().getCurrentTenantIdentifierResolver();
}
@Override
public JavaType<Object> getTenantIdentifierJavaType() {
return tenantIdentifierJavaType;
}
// Serialization handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -286,12 +286,12 @@ public class SessionImpl
private void setUpMultitenancy(SessionFactoryImplementor factory) {
if ( factory.getDefinedFilterNames().contains( TenantIdBinder.FILTER_NAME ) ) {
final String tenantIdentifier = getTenantIdentifier();
final Object tenantIdentifier = getTenantIdentifierValue();
if ( tenantIdentifier == null ) {
throw new HibernateException( "SessionFactory configured for multi-tenancy, but no tenant identifier specified" );
}
else {
final CurrentTenantIdentifierResolver resolver = factory.getCurrentTenantIdentifierResolver();
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
@ -2090,7 +2090,7 @@ public class SessionImpl
private SharedSessionBuilderImpl(SessionImpl session) {
super( (SessionFactoryImpl) session.getFactory() );
this.session = session;
super.tenantIdentifier( session.getTenantIdentifier() );
super.tenantIdentifier( session.getTenantIdentifierValue() );
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -2103,6 +2103,12 @@ public class SessionImpl
throw new SessionException( "Cannot redefine tenant identifier on child session" );
}
@Override
public SharedSessionBuilderImpl tenantIdentifier(Object tenantIdentifier) {
// todo : is this always true? Or just in the case of sharing JDBC resources?
throw new SessionException( "Cannot redefine tenant identifier on child session" );
}
@Override
public SharedSessionBuilderImpl interceptor() {
super.interceptor( session.getInterceptor() );

View File

@ -4146,8 +4146,7 @@ public abstract class AbstractEntityPersister
// check to see if it is in the second-level cache
if ( session.getCacheMode().isGetEnabled() && canReadFromCache() ) {
final EntityDataAccess cache = getCacheAccessStrategy();
final String tenantId = session.getTenantIdentifier();
final Object ck = cache.generateCacheKey( id, this, session.getFactory(), tenantId );
final Object ck = cache.generateCacheKey( id, this, session.getFactory(), session.getTenantIdentifier() );
final Object ce = CacheHelper.fromSharedCache( session, ck, this, getCacheAccessStrategy() );
if ( ce != null ) {
return false;

View File

@ -173,6 +173,10 @@ public class QueryParameterBindingsImpl implements QueryParameterBindings {
@Override
public QueryKey.ParameterBindingsMemento generateQueryKeyMemento(SharedSessionContractImplementor session) {
final MutableCacheKeyImpl mutableCacheKey = new MutableCacheKeyImpl( parameterBindingMap.size() );
final JavaType<Object> tenantIdentifierJavaType = session.getFactory().getTenantIdentifierJavaType();
final Object tenantId = session.getTenantIdentifierValue();
mutableCacheKey.addValue( tenantIdentifierJavaType.getMutabilityPlan().disassemble( tenantId, session ) );
mutableCacheKey.addHashCode( tenantId == null ? 0 : tenantIdentifierJavaType.extractHashCode( tenantId ) );
// We know that parameters are consumed in processing order, this ensures consistency of generated cache keys
parameterMetadata.visitParameters( queryParameter -> {
final QueryParameterBinding<?> binding = parameterBindingMap.get( queryParameter );

View File

@ -17,7 +17,7 @@ import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
*/
//tag::multitenacy-hibernate-ConfigurableMultiTenantConnectionProvider-example[]
public class ConfigurableMultiTenantConnectionProvider
extends AbstractMultiTenantConnectionProvider {
extends AbstractMultiTenantConnectionProvider<String> {
private final Map<String, ConnectionProvider> connectionProviderMap =
new HashMap<>();

View File

@ -15,7 +15,7 @@ import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
/**
* @author Steve Ebersole
*/
public class TestingConnectionProvider extends AbstractMultiTenantConnectionProvider {
public class TestingConnectionProvider extends AbstractMultiTenantConnectionProvider<String> {
private Map<String,ConnectionProvider> connectionProviderMap;
public TestingConnectionProvider(Map<String, ConnectionProvider> connectionProviderMap) {

View File

@ -16,10 +16,8 @@ import org.hibernate.Session;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.SessionFactoryBuilder;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Environment;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.mapping.PersistentClass;
@ -184,7 +182,7 @@ public class DiscriminatorMultiTenancyTest extends BaseUnitTestCase {
} );
}
private static class TestCurrentTenantIdentifierResolver implements CurrentTenantIdentifierResolver {
private static class TestCurrentTenantIdentifierResolver implements CurrentTenantIdentifierResolver<Object> {
private String currentTenantIdentifier;
private final AtomicBoolean postBoot = new AtomicBoolean(false);
@ -193,7 +191,7 @@ public class DiscriminatorMultiTenancyTest extends BaseUnitTestCase {
}
@Override
public String resolveCurrentTenantIdentifier() {
public Object resolveCurrentTenantIdentifier() {
if ( postBoot.get() == false ) {
//Check to prevent any optimisation which might want to cache the tenantId too early during bootstrap:
//it's a common use case to want to provide the tenantId, for example, via a ThreadLocal.

View File

@ -13,7 +13,6 @@ import org.hibernate.SessionBuilder;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.SessionFactoryBuilder;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Environment;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
@ -46,7 +45,7 @@ import static org.hibernate.testing.transaction.TransactionUtil.doInHibernateSes
/**
* @author Steve Ebersole
*/
public abstract class AbstractSchemaBasedMultiTenancyTest<T extends MultiTenantConnectionProvider, C extends ConnectionProvider> extends BaseUnitTestCase {
public abstract class AbstractSchemaBasedMultiTenancyTest<T extends MultiTenantConnectionProvider<String>, C extends ConnectionProvider> extends BaseUnitTestCase {
protected C acmeProvider;
protected C jbossProvider;
@ -271,7 +270,7 @@ public abstract class AbstractSchemaBasedMultiTenancyTest<T extends MultiTenantC
protected SessionBuilder newSession(String tenant) {
return sessionFactory
.withOptions()
.tenantIdentifier( tenant );
.tenantIdentifier( (Object) tenant );
}
private SessionBuilder jboss() {

View File

@ -45,7 +45,7 @@ public class CurrentTenantResolverMultiTenancyTest extends SchemaBasedMultiTenan
return sessionBuilder;
}
private static class TestCurrentTenantIdentifierResolver implements CurrentTenantIdentifierResolver {
private static class TestCurrentTenantIdentifierResolver implements CurrentTenantIdentifierResolver<Object> {
private String currentTenantIdentifier;
@Override
@ -54,7 +54,7 @@ public class CurrentTenantResolverMultiTenancyTest extends SchemaBasedMultiTenan
}
@Override
public String resolveCurrentTenantIdentifier() {
public Object resolveCurrentTenantIdentifier() {
return currentTenantIdentifier;
}
}

View File

@ -9,7 +9,6 @@ package org.hibernate.orm.test.multitenancy.schema;
import javax.sql.DataSource;
import org.hibernate.HibernateException;
import org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl;
import org.hibernate.engine.jdbc.connections.spi.AbstractDataSourceBasedMultiTenantConnectionProviderImpl;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
@ -28,12 +27,12 @@ import static org.junit.Assert.assertThat;
*/
@RequiresDialectFeature( value = ConnectionProviderBuilder.class )
public class SchemaBasedDataSourceMultiTenancyTest extends AbstractSchemaBasedMultiTenancyTest<
AbstractDataSourceBasedMultiTenantConnectionProviderImpl, ConnectionProvider> {
AbstractDataSourceBasedMultiTenantConnectionProviderImpl<String>, ConnectionProvider> {
protected AbstractDataSourceBasedMultiTenantConnectionProviderImpl buildMultiTenantConnectionProvider() {
protected AbstractDataSourceBasedMultiTenantConnectionProviderImpl<String> buildMultiTenantConnectionProvider() {
acmeProvider = ConnectionProviderBuilder.buildDataSourceConnectionProvider( "acme" );
jbossProvider = ConnectionProviderBuilder.buildDataSourceConnectionProvider( "jboss" );
return new AbstractDataSourceBasedMultiTenantConnectionProviderImpl() {
return new AbstractDataSourceBasedMultiTenantConnectionProviderImpl<>() {
@Override
protected DataSource selectAnyDataSource() {
return acmeProvider.unwrap( DataSource.class );
@ -55,7 +54,7 @@ public class SchemaBasedDataSourceMultiTenancyTest extends AbstractSchemaBasedM
@Test
@TestForIssue( jiraKey = "HHH-11651")
public void testUnwrappingConnectionProvider() {
final MultiTenantConnectionProvider multiTenantConnectionProvider = serviceRegistry.getService(
final MultiTenantConnectionProvider<String> multiTenantConnectionProvider = serviceRegistry.getService(
MultiTenantConnectionProvider.class );
final DataSource dataSource = multiTenantConnectionProvider.unwrap( DataSource.class );
assertThat( dataSource, is( notNullValue() ) );
@ -64,7 +63,7 @@ public class SchemaBasedDataSourceMultiTenancyTest extends AbstractSchemaBasedM
@Test
@TestForIssue(jiraKey = "HHH-11651")
public void testUnwrappingAbstractMultiTenantConnectionProvider() {
final MultiTenantConnectionProvider multiTenantConnectionProvider = serviceRegistry.getService(
final MultiTenantConnectionProvider<String> multiTenantConnectionProvider = serviceRegistry.getService(
MultiTenantConnectionProvider.class );
final AbstractDataSourceBasedMultiTenantConnectionProviderImpl dataSourceBasedMultiTenantConnectionProvider = multiTenantConnectionProvider.unwrap(
AbstractDataSourceBasedMultiTenantConnectionProviderImpl.class );
@ -74,9 +73,9 @@ public class SchemaBasedDataSourceMultiTenancyTest extends AbstractSchemaBasedM
@Test
@TestForIssue(jiraKey = "HHH-11651")
public void testUnwrappingMultiTenantConnectionProvider() {
final MultiTenantConnectionProvider multiTenantConnectionProvider = serviceRegistry.getService(
final MultiTenantConnectionProvider<String> multiTenantConnectionProvider = serviceRegistry.getService(
MultiTenantConnectionProvider.class );
final MultiTenantConnectionProvider connectionProvider = multiTenantConnectionProvider.unwrap(
final MultiTenantConnectionProvider<String> connectionProvider = multiTenantConnectionProvider.unwrap(
MultiTenantConnectionProvider.class );
assertThat( connectionProvider, is( notNullValue() ) );
}

View File

@ -7,7 +7,6 @@
package org.hibernate.orm.test.multitenancy.schema;
import org.hibernate.HibernateException;
import org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl;
import org.hibernate.engine.jdbc.connections.spi.AbstractMultiTenantConnectionProvider;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
@ -26,12 +25,12 @@ import static org.junit.Assert.assertThat;
*/
@RequiresDialectFeature( value = ConnectionProviderBuilder.class )
public class SchemaBasedMultiTenancyTest extends AbstractSchemaBasedMultiTenancyTest<
AbstractMultiTenantConnectionProvider, ConnectionProvider> {
AbstractMultiTenantConnectionProvider<String>, ConnectionProvider> {
protected AbstractMultiTenantConnectionProvider buildMultiTenantConnectionProvider() {
protected AbstractMultiTenantConnectionProvider<String> buildMultiTenantConnectionProvider() {
acmeProvider = ConnectionProviderBuilder.buildConnectionProvider( "acme" );
jbossProvider = ConnectionProviderBuilder.buildConnectionProvider( "jboss" );
return new AbstractMultiTenantConnectionProvider() {
return new AbstractMultiTenantConnectionProvider<>() {
@Override
protected ConnectionProvider getAnyConnectionProvider() {
return acmeProvider;
@ -53,7 +52,7 @@ public class SchemaBasedMultiTenancyTest extends AbstractSchemaBasedMultiTenancy
@Test
@TestForIssue( jiraKey = "HHH-11651")
public void testUnwrappingConnectionProvider() {
final MultiTenantConnectionProvider multiTenantConnectionProvider = serviceRegistry.getService(
final MultiTenantConnectionProvider<String> multiTenantConnectionProvider = serviceRegistry.getService(
MultiTenantConnectionProvider.class );
final ConnectionProvider connectionProvider = multiTenantConnectionProvider.unwrap( ConnectionProvider.class );
assertThat( connectionProvider, is( notNullValue() ) );
@ -62,9 +61,9 @@ public class SchemaBasedMultiTenancyTest extends AbstractSchemaBasedMultiTenancy
@Test
@TestForIssue(jiraKey = "HHH-11651")
public void testUnwrappingAbstractMultiTenantConnectionProvider() {
final MultiTenantConnectionProvider multiTenantConnectionProvider = serviceRegistry.getService(
final MultiTenantConnectionProvider<String> multiTenantConnectionProvider = serviceRegistry.getService(
MultiTenantConnectionProvider.class );
final AbstractMultiTenantConnectionProvider connectionProvider = multiTenantConnectionProvider.unwrap(
final AbstractMultiTenantConnectionProvider<?> connectionProvider = multiTenantConnectionProvider.unwrap(
AbstractMultiTenantConnectionProvider.class );
assertThat( connectionProvider, is( notNullValue() ) );
}
@ -72,9 +71,9 @@ public class SchemaBasedMultiTenancyTest extends AbstractSchemaBasedMultiTenancy
@Test
@TestForIssue(jiraKey = "HHH-11651")
public void testUnwrappingMultiTenantConnectionProvider() {
final MultiTenantConnectionProvider multiTenantConnectionProvider = serviceRegistry.getService(
final MultiTenantConnectionProvider<String> multiTenantConnectionProvider = serviceRegistry.getService(
MultiTenantConnectionProvider.class );
final MultiTenantConnectionProvider connectionProvider = multiTenantConnectionProvider.unwrap(
final MultiTenantConnectionProvider<String> connectionProvider = multiTenantConnectionProvider.unwrap(
MultiTenantConnectionProvider.class );
assertThat( connectionProvider, is( notNullValue() ) );
}

View File

@ -34,7 +34,7 @@ public class TenantResolverConfigurationTest extends BaseCoreFunctionalTestCase
assertSame(currentTenantResolver, sessionFactory().getCurrentTenantIdentifierResolver());
}
private static class TestCurrentTenantIdentifierResolver implements CurrentTenantIdentifierResolver {
private static class TestCurrentTenantIdentifierResolver implements CurrentTenantIdentifierResolver<Object> {
private String currentTenantIdentifier;
@Override
@ -43,7 +43,7 @@ public class TenantResolverConfigurationTest extends BaseCoreFunctionalTestCase
}
@Override
public String resolveCurrentTenantIdentifier() {
public Object resolveCurrentTenantIdentifier() {
return currentTenantIdentifier;
}
}

View File

@ -8,7 +8,6 @@ package org.hibernate.orm.test.tenantid;
import org.hibernate.HibernateError;
import org.hibernate.PropertyValueException;
import org.hibernate.Session;
import org.hibernate.boot.SessionFactoryBuilder;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
@ -21,8 +20,6 @@ import org.hibernate.testing.orm.junit.SessionFactoryProducer;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.hibernate.binder.internal.TenantIdBinder;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.type.descriptor.DateTimeUtils;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.AfterEach;
@ -62,7 +59,7 @@ public class TenantIdTest implements SessionFactoryProducer {
@Override
public SessionFactoryImplementor produceSessionFactory(MetadataImplementor model) {
final SessionFactoryBuilder sessionFactoryBuilder = model.getSessionFactoryBuilder();
sessionFactoryBuilder.applyCurrentTenantIdentifierResolver( new CurrentTenantIdentifierResolver() {
sessionFactoryBuilder.applyCurrentTenantIdentifierResolver( new CurrentTenantIdentifierResolver<String>() {
@Override
public String resolveCurrentTenantIdentifier() {
return currentTenant;

View File

@ -49,10 +49,10 @@ public class TenantLongIdTest implements SessionFactoryProducer {
@Override
public SessionFactoryImplementor produceSessionFactory(MetadataImplementor model) {
final SessionFactoryBuilder sessionFactoryBuilder = model.getSessionFactoryBuilder();
sessionFactoryBuilder.applyCurrentTenantIdentifierResolver( new CurrentTenantIdentifierResolver() {
sessionFactoryBuilder.applyCurrentTenantIdentifierResolver( new CurrentTenantIdentifierResolver<Long>() {
@Override
public String resolveCurrentTenantIdentifier() {
return currentTenant.toString();
public Long resolveCurrentTenantIdentifier() {
return currentTenant;
}
@Override
public boolean validateExistingCurrentSessions() {

View File

@ -56,10 +56,10 @@ public class TenantUuidTest implements SessionFactoryProducer {
@Override
public SessionFactoryImplementor produceSessionFactory(MetadataImplementor model) {
final SessionFactoryBuilder sessionFactoryBuilder = model.getSessionFactoryBuilder();
sessionFactoryBuilder.applyCurrentTenantIdentifierResolver( new CurrentTenantIdentifierResolver() {
sessionFactoryBuilder.applyCurrentTenantIdentifierResolver( new CurrentTenantIdentifierResolver<UUID>() {
@Override
public String resolveCurrentTenantIdentifier() {
return currentTenant.toString();
public UUID resolveCurrentTenantIdentifier() {
return currentTenant;
}
@Override
public boolean validateExistingCurrentSessions() {

View File

@ -32,3 +32,13 @@ class Account {
----
See the link:{userGuideBase}#soft-delete[User Guide] for details.
[[custom-tenant-identifier-type]]
== Custom tenant identifier type
The `CurrentTenantIdentifierResolver` and `MultiTenantConnectionProvider` SPIs were generified to support custom tenant identifier types.
Both types now accept a type parameter that represents the tenant identifier type.
Methods that were accepting or returning a tenant identifier value of type `String` were changed to use the type variable.
Applications can migrate by specifying the type parameter `<String>` when extending `CurrentTenantIdentifierResolver`
or one of the `MultiTenantConnectionProvider` subtypes.

View File

@ -384,7 +384,12 @@ public abstract class MockSessionFactory
}
@Override
public CurrentTenantIdentifierResolver getCurrentTenantIdentifierResolver() {
public CurrentTenantIdentifierResolver<Object> getCurrentTenantIdentifierResolver() {
return null;
}
@Override
public JavaType<Object> getTenantIdentifierJavaType() {
return null;
}