-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Changes from 1 commit
67363e9
c99e2ee
af6bfbf
ac01280
949873b
8b8d4bb
755ea99
19828e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,8 @@ | |
| */ | ||
| package org.hibernate.context.spi; | ||
|
|
||
| import org.checkerframework.checker.nullness.qual.NonNull; | ||
|
|
||
| /** | ||
| * A callback registered with the {@link org.hibernate.SessionFactory} that is | ||
| * responsible for resolving the current tenant identifier. | ||
|
|
@@ -48,4 +50,20 @@ public interface CurrentTenantIdentifierResolver<T> { | |
| default boolean isRoot(T tenantId) { | ||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * The name of the database schema for data belonging to the tenant with the | ||
| * given identifier. | ||
| * <p> | ||
| * Called when {@value org.hibernate.cfg.MultiTenancySettings#SET_TENANT_SCHEMA} | ||
| * is enabled. | ||
| * | ||
| * @param tenantIdentifier The tenant identifier | ||
| * @return The name of the database schema belonging to that tenant | ||
| * | ||
| * @see org.hibernate.cfg.MultiTenancySettings#SET_TENANT_SCHEMA | ||
| */ | ||
| default @NonNull String schemaName(@NonNull T tenantIdentifier) { | ||
| return tenantIdentifier.toString(); | ||
| } | ||
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,7 +27,6 @@ | |
| import org.hibernate.binder.internal.TenantIdBinder; | ||
| import org.hibernate.boot.spi.SessionFactoryOptions; | ||
| import org.hibernate.cache.spi.CacheTransactionSynchronization; | ||
| import org.hibernate.context.spi.CurrentTenantIdentifierResolver; | ||
| import org.hibernate.dialect.Dialect; | ||
| import org.hibernate.engine.internal.SessionEventListenerManagerImpl; | ||
| import org.hibernate.engine.jdbc.LobCreator; | ||
|
|
@@ -107,6 +106,7 @@ | |
| import java.io.ObjectInputStream; | ||
| import java.io.ObjectOutputStream; | ||
| import java.io.Serial; | ||
| import java.sql.Connection; | ||
| import java.sql.SQLException; | ||
| import java.util.List; | ||
| import java.util.Locale; | ||
|
|
@@ -243,7 +243,7 @@ protected final void setUpMultitenancy(SessionFactoryImplementor factory, LoadQu | |
| throw new HibernateException( "SessionFactory configured for multi-tenancy, but no tenant identifier specified" ); | ||
| } | ||
| else { | ||
| final CurrentTenantIdentifierResolver<Object> resolver = factory.getCurrentTenantIdentifierResolver(); | ||
| final var resolver = factory.getCurrentTenantIdentifierResolver(); | ||
| if ( resolver==null || !resolver.isRoot( tenantIdentifier ) ) { | ||
| // turn on the filter, unless this is the "root" tenant with access to all partitions | ||
| loadQueryInfluencers.enableFilter( TenantIdBinder.FILTER_NAME ) | ||
|
|
@@ -676,16 +676,18 @@ public boolean isConnected() { | |
|
|
||
| @Override | ||
| public JdbcConnectionAccess getJdbcConnectionAccess() { | ||
| // See class-level JavaDocs for a discussion of the concurrent-access safety of this method | ||
| // See class-level Javadoc for a discussion of the concurrent-access safety of this method | ||
| if ( jdbcConnectionAccess == null ) { | ||
| if ( !factoryOptions.isMultiTenancyEnabled() ) { | ||
| // we might still be using schema-based multitenancy | ||
|
Comment on lines
681
to
+682
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm sorry, what? This might warrant a renaming of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yoann, that's the whole point of this. The design that we inherited here was that "Mutitenancy" = you have a The problem is that Hibernate has support for all sorts of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the old There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know. Sorry, perhaps not the best time for humor. More seriously, renaming There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Sure, but that method's defined by an SPI so it would have to be done via the deprecation process. And note that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I disagree with this. And you even disagreed with it above. There are a few situations here -
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @sebersole The specific method There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure where I said otherwise, but yep. |
||
| jdbcConnectionAccess = new NonContextualJdbcConnectionAccess( | ||
| sessionEventsManager, | ||
| factory.connectionProvider, | ||
| this | ||
| ); | ||
| } | ||
| else { | ||
| // we're using datasource-based multitenancy | ||
| jdbcConnectionAccess = new ContextualJdbcConnectionAccess( | ||
| tenantIdentifier, | ||
| sessionEventsManager, | ||
|
|
@@ -697,6 +699,32 @@ public JdbcConnectionAccess getJdbcConnectionAccess() { | |
| return jdbcConnectionAccess; | ||
| } | ||
|
|
||
| private boolean useSchemaBasedMultiTenancy() { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here we are again :) |
||
| return tenantIdentifier != null | ||
| && factory.getSessionFactoryOptions().isSetTenantSchemaEnabled(); | ||
| } | ||
|
|
||
| private String tenantSchema() { | ||
| final var tenantIdResolver = factory.getCurrentTenantIdentifierResolver(); | ||
| return tenantIdResolver == null | ||
| ? (String) tenantIdentifier | ||
| : tenantIdResolver.schemaName( tenantIdentifier ); | ||
| } | ||
|
|
||
| @Override | ||
| public void afterObtainConnection(Connection connection) throws SQLException { | ||
|
||
| if ( useSchemaBasedMultiTenancy() ) { | ||
| connection.setSchema( tenantSchema() ); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void beforeReleaseConnection(Connection connection) throws SQLException { | ||
| if ( useSchemaBasedMultiTenancy() ) { | ||
| connection.setSchema( null ); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public EntityKey generateEntityKey(Object id, EntityPersister persister) { | ||
| return new EntityKey( id, persister ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ | |
| import org.hibernate.engine.spi.SharedSessionContractImplementor; | ||
| import org.hibernate.event.monitor.spi.EventMonitor; | ||
| import org.hibernate.event.monitor.spi.DiagnosticEvent; | ||
| import org.jboss.logging.Logger; | ||
|
|
||
| /** | ||
| * @author Steve Ebersole | ||
|
|
@@ -24,6 +25,8 @@ public class NonContextualJdbcConnectionAccess implements JdbcConnectionAccess, | |
| private final ConnectionProvider connectionProvider; | ||
| private final SharedSessionContractImplementor session; | ||
|
|
||
| private static final Logger log = Logger.getLogger( NonContextualJdbcConnectionAccess.class ); | ||
|
|
||
| public NonContextualJdbcConnectionAccess( | ||
| SessionEventListener listener, | ||
| ConnectionProvider connectionProvider, | ||
|
|
@@ -39,9 +42,10 @@ public NonContextualJdbcConnectionAccess( | |
| public Connection obtainConnection() throws SQLException { | ||
| final EventMonitor eventMonitor = session.getEventMonitor(); | ||
| final DiagnosticEvent jdbcConnectionAcquisitionEvent = eventMonitor.beginJdbcConnectionAcquisitionEvent(); | ||
| final Connection connection; | ||
| try { | ||
| listener.jdbcConnectionAcquisitionStart(); | ||
| return connectionProvider.getConnection(); | ||
| connection = connectionProvider.getConnection(); | ||
| } | ||
| finally { | ||
| eventMonitor.completeJdbcConnectionAcquisitionEvent( | ||
|
|
@@ -51,10 +55,31 @@ public Connection obtainConnection() throws SQLException { | |
| ); | ||
| listener.jdbcConnectionAcquisitionEnd(); | ||
| } | ||
|
|
||
| try { | ||
| session.afterObtainConnection( connection ); | ||
| } | ||
| catch (SQLException e) { | ||
| try { | ||
| releaseConnection( connection ); | ||
| } | ||
| catch (SQLException re) { | ||
| e.addSuppressed( re ); | ||
| } | ||
| throw e; | ||
| } | ||
| return connection; | ||
| } | ||
|
|
||
| @Override | ||
| public void releaseConnection(Connection connection) throws SQLException { | ||
| try { | ||
| session.beforeReleaseConnection( connection ); | ||
| } | ||
| catch (SQLException e) { | ||
| log.warn( "Error before releasing JDBC connection", e ); | ||
| } | ||
|
||
|
|
||
| final EventMonitor eventMonitor = session.getEventMonitor(); | ||
| final DiagnosticEvent jdbcConnectionReleaseEvent = eventMonitor.beginJdbcConnectionReleaseEvent(); | ||
| try { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't love the naming here at all; unfortunately the existing properties have irregular naming.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that was an unfortunate reality of a singular AvailableSettings with a zagillabillion settings. Related things weren't always "geographically" close to one another and naming tended to get disjointed. It was the main reason I started breaking them out.
But we can always "shadow deprecate" them - aka create new ones that replace the old while still recognizing the old. See DeprecationLogger#deprecatedSetting(java.lang.String, java.lang.String)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that would be reasonable, I think.