HHH-5697 - Support for multi-tenancy

This commit is contained in:
Steve Ebersole 2011-03-25 18:31:04 -05:00
parent 98877a3b28
commit 3ff0288da5
34 changed files with 1036 additions and 293 deletions

View File

@ -0,0 +1,81 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate;
import java.util.Map;
import org.hibernate.cfg.Environment;
/**
* Describes the methods for multi-tenancy understood by Hibernate.
*
* @author Steve Ebersole
*/
public enum MultiTenancyStrategy {
/**
* Multi-tenancy implemented by use of discriminator columns.
*/
DISCRIMINATOR,
/**
* Multi-tenancy implemented as separate schemas.
*/
SCHEMA,
/**
* Multi-tenancy implemented as separate databases.
*/
DATABASE,
/**
* No multi-tenancy
*/
NONE;
public static MultiTenancyStrategy determineMultiTenancyStrategy(Map properties) {
final Object strategy = properties.get( Environment.MULTI_TENANT );
if ( strategy == null ) {
return MultiTenancyStrategy.NONE;
}
if ( MultiTenancyStrategy.class.isInstance( strategy ) ) {
return (MultiTenancyStrategy) strategy;
}
final String strategyName = strategy.toString();
if ( MultiTenancyStrategy.DISCRIMINATOR.name().equals( strategyName ) ) {
return MultiTenancyStrategy.DISCRIMINATOR;
}
else if ( MultiTenancyStrategy.SCHEMA.name().equals( strategyName ) ) {
return MultiTenancyStrategy.SCHEMA;
}
else if ( MultiTenancyStrategy.DATABASE.name().equals( strategyName ) ) {
return MultiTenancyStrategy.DATABASE;
}
else if ( MultiTenancyStrategy.NONE.name().equals( strategyName ) ) {
return MultiTenancyStrategy.NONE;
}
else {
// todo log?
return MultiTenancyStrategy.NONE;
}
}
}

View File

@ -31,6 +31,24 @@ import java.io.Serializable;
* @author Steve Ebersole
*/
public interface SharedSessionContract extends Serializable {
/**
* Obtain the tenant identifier associated with this session.
*
* @return The tenant identifier associated with this session, or {@code null}
*/
public String getTenantIdentifier();
/**
* Should be set only once for the session. Would rather this be supplied to opening the session, as
* being discussed for HHH-2860
*
* @param identifier The tenant identifier.
*
* @deprecated HHH-2860
*/
@Deprecated
public void setTenantIdentifier(String identifier);
/**
* Begin a unit of work and return the associated {@link Transaction} object. If a new underlying transaction is
* required, begin the transaction. Otherwise continue the new work in the context of the existing underlying

View File

@ -551,6 +551,11 @@ public final class Environment {
*/
public static final String NON_CONTEXTUAL_LOB_CREATION = "hibernate.jdbc.lob.non_contextual_creation";
/**
* Strategy for multi-tenancy.
* @see org.hibernate.MultiTenancyStrategy
*/
public static final String MULTI_TENANT = "hibernate.multiTenancy";
private static final BytecodeProvider BYTECODE_PROVIDER_INSTANCE;
private static final boolean ENABLE_BINARY_STREAMS;

View File

@ -26,6 +26,7 @@ package org.hibernate.cfg;
import java.util.Map;
import org.hibernate.ConnectionReleaseMode;
import org.hibernate.EntityMode;
import org.hibernate.MultiTenancyStrategy;
import org.hibernate.cache.QueryCacheFactory;
import org.hibernate.cache.RegionFactory;
import org.hibernate.hql.QueryTranslatorFactory;
@ -81,6 +82,7 @@ public final class Settings {
// private ComponentTuplizerFactory componentTuplizerFactory; todo : HHH-3517 and HHH-1907
// private BytecodeProvider bytecodeProvider;
private String importFiles;
private MultiTenancyStrategy multiTenancyStrategy;
private JtaPlatform jtaPlatform;
@ -438,4 +440,12 @@ public final class Settings {
void setJtaPlatform(JtaPlatform jtaPlatform) {
this.jtaPlatform = jtaPlatform;
}
public MultiTenancyStrategy getMultiTenancyStrategy() {
return multiTenancyStrategy;
}
void setMultiTenancyStrategy(MultiTenancyStrategy multiTenancyStrategy) {
this.multiTenancyStrategy = multiTenancyStrategy;
}
}

View File

@ -30,6 +30,7 @@ import org.hibernate.ConnectionReleaseMode;
import org.hibernate.EntityMode;
import org.hibernate.HibernateException;
import org.hibernate.HibernateLogger;
import org.hibernate.MultiTenancyStrategy;
import org.hibernate.cache.QueryCacheFactory;
import org.hibernate.cache.RegionFactory;
import org.hibernate.cache.impl.NoCachingRegionFactory;
@ -241,6 +242,9 @@ public class SettingsFactory implements Serializable {
LOG.checkNullability(enabledDisabled(checkNullability));
settings.setCheckNullability(checkNullability);
MultiTenancyStrategy multiTenancyStrategy = MultiTenancyStrategy.determineMultiTenancyStrategy( properties );
LOG.debug( "multi-tenancy strategy : " + multiTenancyStrategy );
settings.setMultiTenancyStrategy( multiTenancyStrategy );
// String provider = properties.getProperty( Environment.BYTECODE_PROVIDER );
// log.info( "Bytecode provider name : " + provider );
@ -292,7 +296,7 @@ public class SettingsFactory implements Serializable {
if ( regionFactoryClassName == null ) {
regionFactoryClassName = DEF_CACHE_REG_FACTORY;
}
LOG.cacheRegionFactory(regionFactoryClassName);
LOG.cacheRegionFactory( regionFactoryClassName );
try {
try {
return (RegionFactory) ReflectHelper.classForName( regionFactoryClassName )
@ -314,7 +318,7 @@ public class SettingsFactory implements Serializable {
String className = ConfigurationHelper.getString(
Environment.QUERY_TRANSLATOR, properties, "org.hibernate.hql.ast.ASTQueryTranslatorFactory"
);
LOG.queryTranslator(className);
LOG.queryTranslator( className );
try {
return (QueryTranslatorFactory) ReflectHelper.classForName(className).newInstance();
}

View File

@ -32,6 +32,7 @@ import org.hibernate.ConnectionReleaseMode;
import org.hibernate.HibernateException;
import org.hibernate.Interceptor;
import org.hibernate.MappingException;
import org.hibernate.MultiTenancyStrategy;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.SessionFactoryObserver;
@ -52,6 +53,7 @@ import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.proxy.EntityNotFoundDelegate;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.hibernate.stat.spi.StatisticsImplementor;
import org.hibernate.type.Type;
import org.hibernate.type.TypeResolver;
@ -255,7 +257,7 @@ public interface SessionFactoryImplementor extends Mapping, SessionFactory {
*/
public FetchProfile getFetchProfile(String name);
public ServiceRegistry getServiceRegistry();
public ServiceRegistryImplementor getServiceRegistry();
public void addObserver(SessionFactoryObserver observer);
}

View File

@ -1,10 +1,10 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
* Copyright (c) 2008-2011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
@ -20,7 +20,6 @@
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*
*/
package org.hibernate.engine;
@ -39,6 +38,7 @@ import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.collection.PersistentCollection;
import org.hibernate.engine.jdbc.LobCreationContext;
import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess;
import org.hibernate.engine.query.sql.NativeSQLQuerySpecification;
import org.hibernate.engine.transaction.spi.TransactionCoordinator;
import org.hibernate.event.EventListeners;
@ -47,16 +47,21 @@ import org.hibernate.loader.custom.CustomQuery;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.Type;
/**
* Defines the internal contract between the <tt>Session</tt> and other parts of
* Hibernate such as implementors of <tt>Type</tt> or <tt>EntityPersister</tt>.
* Defines the internal contract between {@link org.hibernate.Session} / {@link org.hibernate.StatelessSession} and
* other parts of Hibernate such as {@link Type}, {@link EntityPersister} and
* {@link org.hibernate.persister.collection.CollectionPersister} implementors
*
* @see org.hibernate.Session the interface to the application
* @see org.hibernate.impl.SessionImpl the actual implementation
* @author Gavin King
* @author Steve Ebersole
*/
public interface SessionImplementor extends Serializable, LobCreationContext {
/**
* Provides access to JDBC connections
*
* @return The contract for accessing JDBC connections.
*/
public JdbcConnectionAccess getJdbcConnectionAccess();
/**
* Retrieves the interceptor currently in use by this event source.

View File

@ -71,7 +71,8 @@ public class JdbcCoordinatorImpl implements JdbcCoordinator {
this.logicalConnection = new LogicalConnectionImpl(
userSuppliedConnection,
transactionCoordinator.getTransactionContext().getConnectionReleaseMode(),
transactionCoordinator.getTransactionContext().getTransactionEnvironment().getJdbcServices()
transactionCoordinator.getTransactionContext().getTransactionEnvironment().getJdbcServices(),
transactionCoordinator.getTransactionContext().getJdbcConnectionAccess()
);
}

View File

@ -22,21 +22,28 @@
* Boston, MA 02110-1301 USA
*/
package org.hibernate.engine.jdbc.internal;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.jboss.logging.Logger;
import org.hibernate.HibernateLogger;
import org.hibernate.MultiTenancyStrategy;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.LobCreationContext;
import org.hibernate.engine.jdbc.LobCreator;
import org.hibernate.engine.jdbc.spi.ExtractedDatabaseMetaData;
import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.jdbc.spi.ResultSetWrapper;
import org.hibernate.engine.jdbc.spi.SchemaNameResolver;
@ -45,41 +52,39 @@ import org.hibernate.engine.jdbc.spi.SqlStatementLogger;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.service.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.hibernate.service.jdbc.dialect.spi.DialectFactory;
import org.hibernate.service.spi.Configurable;
import org.hibernate.service.spi.InjectService;
import org.jboss.logging.Logger;
import org.hibernate.service.spi.ServiceRegistryAwareService;
import org.hibernate.service.spi.ServiceRegistryImplementor;
/**
* Standard implementation of the {@link JdbcServices} contract
*
* @author Steve Ebersole
*/
public class JdbcServicesImpl implements JdbcServices, Configurable {
public class JdbcServicesImpl implements JdbcServices, ServiceRegistryAwareService, Configurable {
private static final HibernateLogger LOG = Logger.getMessageLogger(HibernateLogger.class, JdbcServicesImpl.class.getName());
private ConnectionProvider connectionProvider;
@InjectService
public void setConnectionProvider(ConnectionProvider connectionProvider) {
this.connectionProvider = connectionProvider;
}
private DialectFactory dialectFactory;
@InjectService
public void setDialectFactory(DialectFactory dialectFactory) {
this.dialectFactory = dialectFactory;
}
private ServiceRegistryImplementor serviceRegistry;
private Dialect dialect;
private ConnectionProvider connectionProvider;
private SqlStatementLogger sqlStatementLogger;
private SqlExceptionHelper sqlExceptionHelper;
private ExtractedDatabaseMetaData extractedMetaDataSupport;
private LobCreatorBuilder lobCreatorBuilder;
@Override
public void injectServices(ServiceRegistryImplementor serviceRegistry) {
this.serviceRegistry = serviceRegistry;
}
@Override
public void configure(Map configValues) {
final JdbcConnectionAccess jdbcConnectionAccess = buildJdbcConnectionAccess( configValues );
final DialectFactory dialectFactory = serviceRegistry.getService( DialectFactory.class );
Dialect dialect = null;
LobCreatorBuilder lobCreatorBuilder = null;
@ -105,9 +110,9 @@ public class JdbcServicesImpl implements JdbcServices, Configurable {
boolean useJdbcMetadata = ConfigurationHelper.getBoolean( "hibernate.temp.use_jdbc_metadata_defaults", configValues, true );
if ( useJdbcMetadata ) {
try {
Connection conn = connectionProvider.getConnection();
Connection connection = jdbcConnectionAccess.obtainConnection();
try {
DatabaseMetaData meta = conn.getMetaData();
DatabaseMetaData meta = connection.getMetaData();
LOG.database(meta.getDatabaseProductName(),
meta.getDatabaseProductVersion(),
meta.getDatabaseMajorVersion(),
@ -128,25 +133,25 @@ public class JdbcServicesImpl implements JdbcServices, Configurable {
lobLocatorUpdateCopy = meta.locatorsUpdateCopy();
typeInfoSet.addAll( TypeInfoExtracter.extractTypeInfo( meta ) );
dialect = dialectFactory.buildDialect( configValues, conn );
dialect = dialectFactory.buildDialect( configValues, connection );
catalogName = conn.getCatalog();
catalogName = connection.getCatalog();
SchemaNameResolver schemaNameResolver = determineExplicitSchemaNameResolver( configValues );
if ( schemaNameResolver == null ) {
// todo : add dialect method
// schemaNameResolver = dialect.getSchemaNameResolver();
}
if ( schemaNameResolver != null ) {
schemaName = schemaNameResolver.resolveSchemaName( conn );
schemaName = schemaNameResolver.resolveSchemaName( connection );
}
lobCreatorBuilder = new LobCreatorBuilder( configValues, conn );
lobCreatorBuilder = new LobCreatorBuilder( configValues, connection );
}
catch ( SQLException sqle ) {
LOG.unableToObtainConnectionMetadata(sqle.getMessage());
}
finally {
if ( conn != null ) {
connectionProvider.closeConnection( conn );
if ( connection != null ) {
jdbcConnectionAccess.releaseConnection( connection );
}
}
}
@ -190,6 +195,57 @@ public class JdbcServicesImpl implements JdbcServices, Configurable {
);
}
private JdbcConnectionAccess buildJdbcConnectionAccess(Map configValues) {
final MultiTenancyStrategy multiTenancyStrategy = MultiTenancyStrategy.determineMultiTenancyStrategy( configValues );
if ( MultiTenancyStrategy.NONE == multiTenancyStrategy ) {
connectionProvider = serviceRegistry.getService( ConnectionProvider.class );
return new ConnectionProviderJdbcConnectionAccess( connectionProvider );
}
else {
connectionProvider = null;
final MultiTenantConnectionProvider multiTenantConnectionProvider = serviceRegistry.getService( MultiTenantConnectionProvider.class );
return new MultiTenantConnectionProviderJdbcConnectionAccess( multiTenantConnectionProvider );
}
}
private static class ConnectionProviderJdbcConnectionAccess implements JdbcConnectionAccess {
private final ConnectionProvider connectionProvider;
public ConnectionProviderJdbcConnectionAccess(ConnectionProvider connectionProvider) {
this.connectionProvider = connectionProvider;
}
@Override
public Connection obtainConnection() throws SQLException {
return connectionProvider.getConnection();
}
@Override
public void releaseConnection(Connection connection) throws SQLException {
connection.close();
}
}
private static class MultiTenantConnectionProviderJdbcConnectionAccess implements JdbcConnectionAccess {
private final MultiTenantConnectionProvider connectionProvider;
public MultiTenantConnectionProviderJdbcConnectionAccess(MultiTenantConnectionProvider connectionProvider) {
this.connectionProvider = connectionProvider;
}
@Override
public Connection obtainConnection() throws SQLException {
return connectionProvider.getAnyConnection();
}
@Override
public void releaseConnection(Connection connection) throws SQLException {
connection.close();
}
}
// todo : add to Environment
public static final String SCHEMA_NAME_RESOLVER = "hibernate.schema_name_resolver";
@ -220,9 +276,7 @@ public class JdbcServicesImpl implements JdbcServices, Configurable {
private Set<String> parseKeywords(String extraKeywordsString) {
Set<String> keywordSet = new HashSet<String>();
for ( String keyword : extraKeywordsString.split( "," ) ) {
keywordSet.add( keyword );
}
keywordSet.addAll( Arrays.asList( extraKeywordsString.split( "," ) ) );
return keywordSet;
}
@ -278,81 +332,93 @@ public class JdbcServicesImpl implements JdbcServices, Configurable {
this.typeInfoSet = typeInfoSet;
}
@Override
public boolean supportsScrollableResults() {
return supportsScrollableResults;
}
@Override
public boolean supportsGetGeneratedKeys() {
return supportsGetGeneratedKeys;
}
@Override
public boolean supportsBatchUpdates() {
return supportsBatchUpdates;
}
@Override
public boolean supportsDataDefinitionInTransaction() {
return supportsDataDefinitionInTransaction;
}
@Override
public boolean doesDataDefinitionCauseTransactionCommit() {
return doesDataDefinitionCauseTransactionCommit;
}
@Override
public Set<String> getExtraKeywords() {
return extraKeywords;
}
@Override
public SQLStateType getSqlStateType() {
return sqlStateType;
}
@Override
public boolean doesLobLocatorUpdateCopy() {
return lobLocatorUpdateCopy;
}
@Override
public String getConnectionSchemaName() {
return connectionSchemaName;
}
@Override
public String getConnectionCatalogName() {
return connectionCatalogName;
}
@Override
public LinkedHashSet<TypeInfo> getTypeInfoSet() {
return typeInfoSet;
}
}
@Override
public ConnectionProvider getConnectionProvider() {
return connectionProvider;
}
@Override
public SqlStatementLogger getSqlStatementLogger() {
return sqlStatementLogger;
}
@Override
public SqlExceptionHelper getSqlExceptionHelper() {
return sqlExceptionHelper;
}
@Override
public Dialect getDialect() {
return dialect;
}
@Override
public ExtractedDatabaseMetaData getExtractedMetaDataSupport() {
return extractedMetaDataSupport;
}
/**
* {@inheritDoc}
*/
@Override
public LobCreator getLobCreator(LobCreationContext lobCreationContext) {
return lobCreatorBuilder.buildLobCreator( lobCreationContext );
}
/**
* {@inheritDoc}
*/
@Override
public ResultSetWrapper getResultSetWrapper() {
return ResultSetWrapperImpl.INSTANCE;
}

View File

@ -37,6 +37,7 @@ import org.hibernate.HibernateLogger;
import org.hibernate.JDBCException;
import org.hibernate.engine.jdbc.internal.proxy.ProxyBuilder;
import org.hibernate.engine.jdbc.spi.ConnectionObserver;
import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess;
import org.hibernate.engine.jdbc.spi.JdbcResourceRegistry;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.jdbc.spi.LogicalConnectionImplementor;
@ -61,6 +62,7 @@ public class LogicalConnectionImpl implements LogicalConnectionImplementor {
private final transient ConnectionReleaseMode connectionReleaseMode;
private final transient JdbcServices jdbcServices;
private final transient JdbcConnectionAccess jdbcConnectionAccess;
private final transient JdbcResourceRegistry jdbcResourceRegistry;
private final transient List<ConnectionObserver> observers;
@ -73,10 +75,12 @@ public class LogicalConnectionImpl implements LogicalConnectionImplementor {
public LogicalConnectionImpl(
Connection userSuppliedConnection,
ConnectionReleaseMode connectionReleaseMode,
JdbcServices jdbcServices) {
JdbcServices jdbcServices,
JdbcConnectionAccess jdbcConnectionAccess) {
this(
connectionReleaseMode,
jdbcServices,
jdbcConnectionAccess,
(userSuppliedConnection != null),
false,
new ArrayList<ConnectionObserver>()
@ -87,6 +91,7 @@ public class LogicalConnectionImpl implements LogicalConnectionImplementor {
private LogicalConnectionImpl(
ConnectionReleaseMode connectionReleaseMode,
JdbcServices jdbcServices,
JdbcConnectionAccess jdbcConnectionAccess,
boolean isUserSuppliedConnection,
boolean isClosed,
List<ConnectionObserver> observers) {
@ -94,6 +99,7 @@ public class LogicalConnectionImpl implements LogicalConnectionImplementor {
jdbcServices, isUserSuppliedConnection, connectionReleaseMode
);
this.jdbcServices = jdbcServices;
this.jdbcConnectionAccess = jdbcConnectionAccess;
this.jdbcResourceRegistry = new JdbcResourceRegistryImpl( getJdbcServices().getSqlExceptionHelper() );
this.observers = observers;
@ -286,7 +292,7 @@ public class LogicalConnectionImpl implements LogicalConnectionImplementor {
private void obtainConnection() throws JDBCException {
LOG.debugf("Obtaining JDBC connection");
try {
physicalConnection = getJdbcServices().getConnectionProvider().getConnection();
physicalConnection = jdbcConnectionAccess.obtainConnection();
for ( ConnectionObserver observer : observers ) {
observer.physicalConnectionObtained( physicalConnection );
}
@ -304,13 +310,19 @@ public class LogicalConnectionImpl implements LogicalConnectionImplementor {
*/
private void releaseConnection() throws JDBCException {
LOG.debugf("Releasing JDBC connection");
if ( physicalConnection == null ) return;
try {
if (!physicalConnection.isClosed()) getJdbcServices().getSqlExceptionHelper().logAndClearWarnings(physicalConnection);
if (!isUserSuppliedConnection) getJdbcServices().getConnectionProvider().closeConnection(physicalConnection);
if ( physicalConnection == null ) {
return;
}
catch (SQLException sqle) {
throw getJdbcServices().getSqlExceptionHelper().convert( sqle, "Could not close connection" );
try {
if ( ! physicalConnection.isClosed() ) {
getJdbcServices().getSqlExceptionHelper().logAndClearWarnings( physicalConnection );
}
if ( ! isUserSuppliedConnection ) {
jdbcConnectionAccess.releaseConnection( physicalConnection );
}
}
catch (SQLException e) {
throw getJdbcServices().getSqlExceptionHelper().convert( e, "Could not close connection" );
}
finally {
physicalConnection = null;
@ -424,6 +436,7 @@ public class LogicalConnectionImpl implements LogicalConnectionImplementor {
return new LogicalConnectionImpl(
transactionContext.getConnectionReleaseMode(),
transactionContext.getTransactionEnvironment().getJdbcServices(),
transactionContext.getJdbcConnectionAccess(),
isUserSuppliedConnection,
isClosed,
observers

View File

@ -1,210 +0,0 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2010, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.engine.jdbc.spi;
import java.io.Serializable;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import org.hibernate.HibernateException;
import org.hibernate.ScrollMode;
import org.hibernate.jdbc.Expectation;
/**
* Encapsulates JDBC Connection management SPI.
* <p/>
* The lifecycle is intended to span a logical series of interactions with the
* database. Internally, this means the the lifecycle of the Session.
*
* @author Gail Badner
*/
public interface ConnectionManager extends Serializable {
/**
* Retrieves the connection currently managed by this ConnectionManager.
* <p/>
* Note, that we may need to obtain a connection to return here if a
* connection has either not yet been obtained (non-UserSuppliedConnectionProvider)
* or has previously been aggressively released (if supported in this environment).
*
* @return The current Connection.
*
* @throws HibernateException Indicates a connection is currently not
* available (we are currently manually disconnected).
*/
Connection getConnection();
// TODO: should this be removd from the SPI?
boolean hasBorrowedConnection();
// TODO: should this be removd from the SPI?
void releaseBorrowedConnection();
/**
* Is this ConnectionManager instance "logically" connected. Meaning
* do we either have a cached connection available or do we have the
* ability to obtain a connection on demand.
*
* @return True if logically connected; false otherwise.
*/
boolean isCurrentlyConnected();
/**
* To be called after execution of each JDBC statement. Used to
* conditionally release the JDBC connection aggressively if
* the configured release mode indicates.
*/
void afterStatement();
/**
* Sets the transaction timeout.
* @param seconds - number of seconds until the the transaction times out.
*/
void setTransactionTimeout(int seconds);
/**
* To be called after Session completion. Used to release the JDBC
* connection.
*
* @return The connection mantained here at time of close. Null if
* there was no connection cached internally.
*/
Connection close();
/**
* Manually disconnect the underlying JDBC Connection. The assumption here
* is that the manager will be reconnected at a later point in time.
*
* @return The connection mantained here at time of disconnect. Null if
* there was no connection cached internally.
*/
Connection manualDisconnect();
/**
* Manually reconnect the underlying JDBC Connection. Should be called at
* some point after manualDisconnect().
* <p/>
* This form is used for ConnectionProvider-supplied connections.
*/
void manualReconnect();
/**
* Manually reconnect the underlying JDBC Connection. Should be called at
* some point after manualDisconnect().
* <p/>
* This form is used for user-supplied connections.
*/
void manualReconnect(Connection suppliedConnection);
/**
* Callback to let us know that a flush is beginning. We use this fact
* to temporarily circumvent aggressive connection releasing until after
* the flush cycle is complete {@link #flushEnding()}
*/
void flushBeginning();
/**
* Callback to let us know that a flush is ending. We use this fact to
* stop circumventing aggressive releasing connections.
*/
void flushEnding();
/**
* Get a non-batchable prepared statement to use for inserting / deleting / updating,
* using JDBC3 getGeneratedKeys ({@link java.sql.Connection#prepareStatement(String, int)}).
* <p/>
* If not explicitly closed via {@link java.sql.PreparedStatement#close()}, it will be
* released when the session is closed or disconnected.
*/
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys);
/**
* Get a non-batchable prepared statement to use for inserting / deleting / updating.
* using JDBC3 getGeneratedKeys ({@link java.sql.Connection#prepareStatement(String, String[])}).
* <p/>
* If not explicitly closed via {@link java.sql.PreparedStatement#close()}, it will be
* released when the session is closed or disconnected.
*/
public PreparedStatement prepareStatement(String sql, String[] columnNames);
/**
* Get a non-batchable prepared statement to use for selecting. Does not
* result in execution of the current batch.
* <p/>
* If not explicitly closed via {@link java.sql.PreparedStatement#close()},
* it will be released when the session is closed or disconnected.
*/
public PreparedStatement prepareSelectStatement(String sql);
/**
* Get a non-batchable prepared statement to use for inserting / deleting / updating.
* <p/>
* If not explicitly closed via {@link java.sql.PreparedStatement#close()}, it will be
* released when the session is closed or disconnected.
*/
public PreparedStatement prepareStatement(String sql, boolean isCallable);
/**
* Get a non-batchable callable statement to use for inserting / deleting / updating.
* <p/>
* If not explicitly closed via {@link java.sql.PreparedStatement#close()}, it will be
* released when the session is closed or disconnected.
*/
public CallableStatement prepareCallableStatement(String sql);
/**
* Get a batchable prepared statement to use for inserting / deleting / updating
* (might be called many times before a single call to <tt>executeBatch()</tt>).
* After setting parameters, call <tt>addToBatch</tt> - do not execute the
* statement explicitly.
* @see org.hibernate.engine.jdbc.batch.spi.Batch#addToBatch
* <p/>
* If not explicitly closed via {@link java.sql.PreparedStatement#close()}, it will be
* released when the session is closed or disconnected.
*/
public PreparedStatement prepareBatchStatement(Object key, String sql, boolean isCallable);
/**
* Get a prepared statement for use in loading / querying. Does not
* result in execution of the current batch.
* <p/>
* If not explicitly closed via {@link java.sql.PreparedStatement#close()},
* it will be released when the session is closed or disconnected.
*/
public PreparedStatement prepareQueryStatement(
String sql,
boolean isScrollable,
ScrollMode scrollMode,
boolean isCallable);
/**
* Cancel the current query statement
*/
public void cancelLastQuery();
public void abortBatch();
public void addToBatch(Object batchKey, String sql, Expectation expectation);
public void executeBatch();
}

View File

@ -0,0 +1,39 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.engine.jdbc.spi;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.SQLException;
/**
* Provides centralized access to JDBC connections. Centralized to hide the complexity of accounting for contextual
* (multi-tenant) versus non-contextual access.
*
* @author Steve Ebersole
*/
public interface JdbcConnectionAccess extends Serializable {
public Connection obtainConnection() throws SQLException;
public void releaseConnection(Connection connection) throws SQLException;
}

View File

@ -24,6 +24,7 @@
package org.hibernate.engine.transaction.spi;
import org.hibernate.ConnectionReleaseMode;
import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess;
import java.io.Serializable;
@ -110,4 +111,6 @@ public interface TransactionContext extends Serializable {
public void beforeTransactionCompletion(TransactionImplementor hibernateTransaction);
public void afterTransactionCompletion(TransactionImplementor hibernateTransaction, boolean successful);
public JdbcConnectionAccess getJdbcConnectionAccess();
}

View File

@ -30,11 +30,13 @@ import java.sql.SQLException;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.MultiTenancyStrategy;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.ScrollableResults;
import org.hibernate.SessionException;
import org.hibernate.SharedSessionContract;
import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess;
import org.hibernate.engine.NamedQueryDefinition;
import org.hibernate.engine.NamedSQLQueryDefinition;
import org.hibernate.engine.QueryParameters;
@ -48,6 +50,8 @@ import org.hibernate.engine.transaction.spi.TransactionContext;
import org.hibernate.engine.transaction.spi.TransactionEnvironment;
import org.hibernate.jdbc.WorkExecutor;
import org.hibernate.jdbc.WorkExecutorVisitable;
import org.hibernate.service.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider;
/**
* Functionality common to stateless and stateful sessions
@ -57,6 +61,7 @@ import org.hibernate.jdbc.WorkExecutorVisitable;
public abstract class AbstractSessionImpl implements Serializable, SharedSessionContract, SessionImplementor, TransactionContext {
protected transient SessionFactoryImpl factory;
private String tenantIdentifier;
private boolean closed = false;
protected AbstractSessionImpl(SessionFactoryImpl factory) {
@ -203,4 +208,77 @@ public abstract class AbstractSessionImpl implements Serializable, SharedSession
return scrollCustomQuery( getNativeSQLQueryPlan( spec ).getCustomQuery(), queryParameters );
}
@Override
public String getTenantIdentifier() {
return tenantIdentifier;
}
@Override
public void setTenantIdentifier(String identifier) {
if ( MultiTenancyStrategy.NONE == factory.getSettings().getMultiTenancyStrategy() ) {
throw new HibernateException( "SessionFactory was not configured for multi-tenancy" );
}
this.tenantIdentifier = identifier;
}
private transient JdbcConnectionAccess jdbcConnectionAccess;
@Override
public JdbcConnectionAccess getJdbcConnectionAccess() {
if ( jdbcConnectionAccess == null ) {
if ( MultiTenancyStrategy.NONE == factory.getSettings().getMultiTenancyStrategy() ) {
jdbcConnectionAccess = new NonContextualJdbcConnectionAccess(
factory.getServiceRegistry().getService( ConnectionProvider.class )
);
}
else {
jdbcConnectionAccess = new ContextualJdbcConnectionAccess(
factory.getServiceRegistry().getService( MultiTenantConnectionProvider.class )
);
}
}
return jdbcConnectionAccess;
}
private static class NonContextualJdbcConnectionAccess implements JdbcConnectionAccess, Serializable {
private final ConnectionProvider connectionProvider;
private NonContextualJdbcConnectionAccess(ConnectionProvider connectionProvider) {
this.connectionProvider = connectionProvider;
}
@Override
public Connection obtainConnection() throws SQLException {
return connectionProvider.getConnection();
}
@Override
public void releaseConnection(Connection connection) throws SQLException {
connectionProvider.closeConnection( connection );
}
}
private class ContextualJdbcConnectionAccess implements JdbcConnectionAccess, Serializable {
private final MultiTenantConnectionProvider connectionProvider;
private ContextualJdbcConnectionAccess(MultiTenantConnectionProvider connectionProvider) {
this.connectionProvider = connectionProvider;
}
@Override
public Connection obtainConnection() throws SQLException {
if ( tenantIdentifier == null ) {
throw new HibernateException( "Tenant identifier required!" );
}
return connectionProvider.getConnection( tenantIdentifier );
}
@Override
public void releaseConnection(Connection connection) throws SQLException {
if ( tenantIdentifier == null ) {
throw new HibernateException( "Tenant identifier required!" );
}
connectionProvider.releaseConnection( tenantIdentifier, connection );
}
}
}

View File

@ -53,6 +53,7 @@ import org.hibernate.HibernateException;
import org.hibernate.HibernateLogger;
import org.hibernate.Interceptor;
import org.hibernate.MappingException;
import org.hibernate.MultiTenancyStrategy;
import org.hibernate.ObjectNotFoundException;
import org.hibernate.QueryException;
import org.hibernate.Session;
@ -212,6 +213,7 @@ public final class SessionFactoryImpl
this.properties = new Properties();
this.properties.putAll( cfg.getProperties() );
this.interceptor = cfg.getInterceptor();
this.serviceRegistry = serviceRegistry.getService( SessionFactoryServiceRegistryFactory.class ).buildServiceRegistry(
this,
cfg
@ -1260,10 +1262,11 @@ public final class SessionFactoryImpl
}
@Override
public ServiceRegistry getServiceRegistry() {
public ServiceRegistryImplementor getServiceRegistry() {
return serviceRegistry;
}
@Override
public EntityNotFoundDelegate getEntityNotFoundDelegate() {
return entityNotFoundDelegate;
}

View File

@ -38,6 +38,7 @@ import org.hibernate.persister.spi.PersisterClassResolver;
import org.hibernate.persister.spi.PersisterFactory;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.spi.ServiceRegistryAwareService;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
@ -75,10 +76,10 @@ public final class PersisterFactoryImpl implements PersisterFactory, ServiceRegi
SessionFactoryImplementor.class
};
private ServiceRegistry serviceRegistry;
private ServiceRegistryImplementor serviceRegistry;
@Override
public void injectServices(ServiceRegistry serviceRegistry) {
public void injectServices(ServiceRegistryImplementor serviceRegistry) {
this.serviceRegistry = serviceRegistry;
}

View File

@ -31,6 +31,7 @@ import org.hibernate.persister.internal.PersisterFactoryInitiator;
import org.hibernate.service.classloading.internal.ClassLoaderServiceInitiator;
import org.hibernate.service.internal.SessionFactoryServiceRegistryFactoryInitiator;
import org.hibernate.service.jdbc.connections.internal.ConnectionProviderInitiator;
import org.hibernate.service.jdbc.connections.internal.MultiTenantConnectionProviderInitiator;
import org.hibernate.service.jdbc.dialect.internal.DialectFactoryInitiator;
import org.hibernate.service.jdbc.dialect.internal.DialectResolverInitiator;
import org.hibernate.service.jmx.internal.JmxServiceInitiator;
@ -58,6 +59,7 @@ public class StandardServiceInitiators {
serviceInitiators.add( PersisterFactoryInitiator.INSTANCE );
serviceInitiators.add( ConnectionProviderInitiator.INSTANCE );
serviceInitiators.add( MultiTenantConnectionProviderInitiator.INSTANCE );
serviceInitiators.add( DialectResolverInitiator.INSTANCE );
serviceInitiators.add( DialectFactoryInitiator.INSTANCE );
serviceInitiators.add( BatchBuilderInitiator.INSTANCE );

View File

@ -1,7 +1,7 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2010, Red Hat Inc. or third-party contributors as
* Copyright (c) 2011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
@ -125,12 +125,12 @@ public class BasicServiceRegistryImpl extends AbstractServiceRegistryImpl implem
protected <T extends Service> void configureService(T service) {
applyInjections( service );
if ( Configurable.class.isInstance( service ) ) {
( (Configurable) service ).configure( configurationValues );
}
if ( ServiceRegistryAwareService.class.isInstance( service ) ) {
( (ServiceRegistryAwareService) service ).injectServices( this );
}
if ( Configurable.class.isInstance( service ) ) {
( (Configurable) service ).configure( configurationValues );
}
}
}

View File

@ -36,6 +36,7 @@ import org.jboss.logging.Logger;
import org.hibernate.HibernateException;
import org.hibernate.HibernateLogger;
import org.hibernate.MultiTenancyStrategy;
import org.hibernate.cfg.Environment;
import org.hibernate.internal.util.beans.BeanInfoHelper;
import org.hibernate.service.classloading.spi.ClassLoaderService;
@ -100,6 +101,10 @@ public class ConnectionProviderInitiator implements BasicServiceInitiator<Connec
@Override
public ConnectionProvider initiateService(Map configurationValues, ServiceRegistryImplementor registry) {
if ( MultiTenancyStrategy.determineMultiTenancyStrategy( configurationValues ) != MultiTenancyStrategy.NONE ) {
// nothing to do, but given the separate hierarchies have to handle this here.
}
final ClassLoaderService classLoaderService = registry.getService( ClassLoaderService.class );
ConnectionProvider connectionProvider = null;

View File

@ -0,0 +1,53 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.service.jdbc.connections.internal;
import java.util.Map;
import org.hibernate.MultiTenancyStrategy;
import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.hibernate.service.spi.BasicServiceInitiator;
import org.hibernate.service.spi.ServiceRegistryImplementor;
/**
* @author Steve Ebersole
*/
public class MultiTenantConnectionProviderInitiator implements BasicServiceInitiator<MultiTenantConnectionProvider> {
public static final MultiTenantConnectionProviderInitiator INSTANCE = new MultiTenantConnectionProviderInitiator();
@Override
public Class<MultiTenantConnectionProvider> getServiceInitiated() {
return MultiTenantConnectionProvider.class;
}
@Override
public MultiTenantConnectionProvider initiateService(Map configurationValues, ServiceRegistryImplementor registry) {
if ( MultiTenancyStrategy.determineMultiTenancyStrategy( configurationValues ) == MultiTenancyStrategy.NONE ) {
// nothing to do, but given the separate hierarchies have to handle this here.
}
// for now...
return null;
}
}

View File

@ -0,0 +1,80 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.service.jdbc.connections.spi;
import java.sql.Connection;
import java.sql.SQLException;
import org.hibernate.service.UnknownUnwrapTypeException;
/**
* @author Steve Ebersole
*/
public abstract class AbstractMultiTenantConnectionProvider implements MultiTenantConnectionProvider {
protected abstract ConnectionProvider getAnyConnectionProvider();
protected abstract ConnectionProvider selectConnectionProvider(String tenantIdentifier);
@Override
public Connection getAnyConnection() throws SQLException {
return getAnyConnectionProvider().getConnection();
}
@Override
public void releaseAnyConnection(Connection connection) throws SQLException {
getAnyConnectionProvider().closeConnection( connection );
}
@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
return selectConnectionProvider( tenantIdentifier ).getConnection();
}
@Override
public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
selectConnectionProvider( tenantIdentifier ).getConnection();
}
@Override
public boolean supportsAggressiveRelease() {
return getAnyConnectionProvider().supportsAggressiveRelease();
}
@Override
public boolean isUnwrappableAs(Class unwrapType) {
return ConnectionProvider.class.equals( unwrapType ) ||
MultiTenantConnectionProvider.class.equals( unwrapType ) ||
AbstractMultiTenantConnectionProvider.class.isAssignableFrom( unwrapType );
}
@Override
@SuppressWarnings( {"unchecked"})
public <T> T unwrap(Class<T> unwrapType) {
if ( isUnwrappableAs( unwrapType ) ) {
return (T) this;
}
else {
throw new UnknownUnwrapTypeException( unwrapType );
}
}
}

View File

@ -0,0 +1,95 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.service.jdbc.connections.spi;
import java.sql.Connection;
import java.sql.SQLException;
import org.hibernate.service.Service;
import org.hibernate.service.spi.Wrapped;
/**
* @author Steve Ebersole
*/
public interface MultiTenantConnectionProvider 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).
*
* @return The database metadata.
*
* @throws SQLException Indicates a problem opening a connection
*/
public Connection getAnyConnection() throws SQLException;
/**
* Release a connection obtained from {@link #getAnyConnection}
*
* @param connection The JDBC connection to release
*
* @throws SQLException Indicates a problem closing the connection
*/
public void releaseAnyConnection(Connection connection) throws SQLException;
/**
* Obtains a connection for Hibernate use according to the underlying strategy of this provider.
*
* @param tenantIdentifier The identifier of the tenant for which to get a connection
*
* @return The obtained JDBC connection
*
* @throws SQLException Indicates a problem opening a connection
* @throws org.hibernate.HibernateException Indicates a problem otherwise obtaining a connection.
*/
public Connection getConnection(String tenantIdentifier) throws SQLException;
/**
* Release a connection from Hibernate use.
*
* @param connection The JDBC connection to release
* @param tenantIdentifier The identifier of the tenant.
*
* @throws SQLException Indicates a problem closing the connection
* @throws org.hibernate.HibernateException Indicates a problem otherwise releasing a connection.
*/
public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException;
/**
* Does this connection provider support aggressive release of JDBC
* connections and re-acquisition of those connections (if need be) later?
* <p/>
* This is used in conjunction with {@link org.hibernate.cfg.Environment#RELEASE_CONNECTIONS}
* to aggressively release JDBC connections. However, the configured ConnectionProvider
* must support re-acquisition of the same underlying connection for that semantic to work.
* <p/>
* Typically, this is only true in managed environments where a container
* tracks connections by transaction or thread.
*
* Note that JTA semantic depends on the fact that the underlying connection provider does
* support aggressive release.
*
* @return {@code true} if aggressive releasing is supported; {@code false} otherwise.
*/
public boolean supportsAggressiveRelease();
}

View File

@ -29,6 +29,7 @@ import org.hibernate.service.jta.platform.spi.JtaPlatform;
import org.hibernate.service.spi.Configurable;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.spi.ServiceRegistryAwareService;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
@ -44,10 +45,10 @@ public abstract class AbstractJtaPlatform
implements JtaPlatform, Configurable, ServiceRegistryAwareService, TransactionManagerAccess {
private boolean cacheTransactionManager;
private boolean cacheUserTransaction;
private ServiceRegistry serviceRegistry;
private ServiceRegistryImplementor serviceRegistry;
@Override
public void injectServices(ServiceRegistry serviceRegistry) {
public void injectServices(ServiceRegistryImplementor serviceRegistry) {
this.serviceRegistry = serviceRegistry;
}

View File

@ -23,8 +23,6 @@
*/
package org.hibernate.service.spi;
import org.hibernate.service.ServiceRegistry;
/**
* Allows services to be injected with the {@link org.hibernate.service.ServiceRegistry} during configuration phase.
*
@ -36,5 +34,5 @@ public interface ServiceRegistryAwareService {
*
* @param serviceRegistry The registry
*/
public void injectServices(ServiceRegistry serviceRegistry);
public void injectServices(ServiceRegistryImplementor serviceRegistry);
}

View File

@ -32,7 +32,7 @@ import java.sql.SQLException;
*
* @author Steve Ebersole
*/
interface ConnectionHelper {
public interface ConnectionHelper {
/**
* Prepare the helper for use.
*

View File

@ -157,6 +157,17 @@ public class SchemaExport {
);
}
public SchemaExport(
ConnectionHelper connectionHelper,
String[] dropSql,
String[] createSql) {
this.connectionHelper = connectionHelper;
this.dropSQL = dropSql;
this.createSQL = createSql;
this.importFiles = "";
this.formatter = FormatStyle.DDL.getFormatter();
}
/**
* For generating a export script file, this is the file which will be written.
*

View File

@ -0,0 +1,61 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.common;
import java.sql.Connection;
import java.sql.SQLException;
import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess;
import org.hibernate.engine.transaction.spi.TransactionEnvironment;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.jdbc.connections.spi.ConnectionProvider;
/**
* @author Steve Ebersole
*/
public class JdbcConnectionAccessImpl implements JdbcConnectionAccess {
private final ConnectionProvider connectionProvider;
public JdbcConnectionAccessImpl(TransactionEnvironment transactionEnvironment) {
this( transactionEnvironment.getSessionFactory().getServiceRegistry() );
}
public JdbcConnectionAccessImpl(ConnectionProvider connectionProvider) {
this.connectionProvider = connectionProvider;
}
public JdbcConnectionAccessImpl(ServiceRegistry serviceRegistry) {
this( serviceRegistry.getService( ConnectionProvider.class ) );
}
@Override
public Connection obtainConnection() throws SQLException {
return connectionProvider.getConnection();
}
@Override
public void releaseConnection(Connection connection) throws SQLException {
connectionProvider.closeConnection( connection );
}
}

View File

@ -24,18 +24,30 @@
package org.hibernate.test.common;
import org.hibernate.ConnectionReleaseMode;
import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess;
import org.hibernate.engine.transaction.spi.TransactionContext;
import org.hibernate.engine.transaction.spi.TransactionEnvironment;
import org.hibernate.engine.transaction.spi.TransactionImplementor;
import org.hibernate.service.ServiceRegistry;
/**
* @author Steve Ebersole
*/
public class TransactionContextImpl implements TransactionContext {
private final TransactionEnvironment transactionEnvironment;
private final JdbcConnectionAccess jdbcConnectionAccess;
public TransactionContextImpl(TransactionEnvironment transactionEnvironment, JdbcConnectionAccess jdbcConnectionAccess) {
this.transactionEnvironment = transactionEnvironment;
this.jdbcConnectionAccess = jdbcConnectionAccess;
}
public TransactionContextImpl(TransactionEnvironment transactionEnvironment, ServiceRegistry serviceRegistry) {
this( transactionEnvironment, new JdbcConnectionAccessImpl( serviceRegistry ) );
}
public TransactionContextImpl(TransactionEnvironment transactionEnvironment) {
this.transactionEnvironment = transactionEnvironment;
this( transactionEnvironment, new JdbcConnectionAccessImpl( transactionEnvironment.getJdbcServices().getConnectionProvider() ) );
}
@Override
@ -48,6 +60,11 @@ public class TransactionContextImpl implements TransactionContext {
return transactionEnvironment.getTransactionFactory().getDefaultReleaseMode();
}
@Override
public JdbcConnectionAccess getJdbcConnectionAccess() {
return jdbcConnectionAccess;
}
@Override
public boolean shouldAutoJoinTransaction() {
return true;

View File

@ -38,6 +38,7 @@ import org.junit.Test;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.hibernate.test.common.BasicTestingJdbcServiceImpl;
import org.hibernate.test.common.JdbcConnectionAccessImpl;
import org.hibernate.test.common.JournalingConnectionObserver;
import static org.junit.Assert.assertEquals;
@ -112,7 +113,12 @@ public class AggressiveReleaseTest extends BaseUnitTestCase {
@Test
public void testBasicRelease() {
LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl( null, ConnectionReleaseMode.AFTER_STATEMENT, services );
LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl(
null,
ConnectionReleaseMode.AFTER_STATEMENT,
services ,
new JdbcConnectionAccessImpl( services.getConnectionProvider() )
);
Connection proxiedConnection = ProxyBuilder.buildConnection( logicalConnection );
JournalingConnectionObserver observer = new JournalingConnectionObserver();
logicalConnection.addObserver( observer );
@ -142,7 +148,12 @@ public class AggressiveReleaseTest extends BaseUnitTestCase {
@Test
public void testReleaseCircumventedByHeldResources() {
LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl( null, ConnectionReleaseMode.AFTER_STATEMENT, services );
LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl(
null,
ConnectionReleaseMode.AFTER_STATEMENT,
services,
new JdbcConnectionAccessImpl( services.getConnectionProvider() )
);
Connection proxiedConnection = ProxyBuilder.buildConnection( logicalConnection );
JournalingConnectionObserver observer = new JournalingConnectionObserver();
logicalConnection.addObserver( observer );
@ -196,7 +207,12 @@ public class AggressiveReleaseTest extends BaseUnitTestCase {
@Test
public void testReleaseCircumventedManually() {
LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl( null, ConnectionReleaseMode.AFTER_STATEMENT, services );
LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl(
null,
ConnectionReleaseMode.AFTER_STATEMENT,
services,
new JdbcConnectionAccessImpl( services.getConnectionProvider() )
);
Connection proxiedConnection = ProxyBuilder.buildConnection( logicalConnection );
JournalingConnectionObserver observer = new JournalingConnectionObserver();
logicalConnection.addObserver( observer );

View File

@ -41,6 +41,7 @@ import org.hibernate.JDBCException;
import org.hibernate.engine.jdbc.internal.LogicalConnectionImpl;
import org.hibernate.engine.jdbc.internal.proxy.ProxyBuilder;
import org.hibernate.test.common.BasicTestingJdbcServiceImpl;
import org.hibernate.test.common.JdbcConnectionAccessImpl;
import org.hibernate.testing.junit4.BaseUnitTestCase;
/**
@ -64,7 +65,8 @@ public class BasicConnectionProxyTest extends BaseUnitTestCase {
LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl(
null,
ConnectionReleaseMode.AFTER_TRANSACTION,
services
services,
new JdbcConnectionAccessImpl( services.getConnectionProvider() )
);
Connection proxiedConnection = ProxyBuilder.buildConnection( logicalConnection );
try {
@ -92,7 +94,8 @@ public class BasicConnectionProxyTest extends BaseUnitTestCase {
LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl(
null,
ConnectionReleaseMode.AFTER_TRANSACTION,
services
services,
new JdbcConnectionAccessImpl( services.getConnectionProvider() )
);
Connection proxiedConnection = ProxyBuilder.buildConnection( logicalConnection );
try {
@ -114,7 +117,8 @@ public class BasicConnectionProxyTest extends BaseUnitTestCase {
LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl(
null,
ConnectionReleaseMode.AFTER_TRANSACTION,
services
services,
new JdbcConnectionAccessImpl( services.getConnectionProvider() )
);
Connection proxiedConnection = ProxyBuilder.buildConnection( logicalConnection );

View File

@ -91,7 +91,9 @@ public class BatchingTest extends BaseUnitTestCase implements BatchKey {
@Test
public void testNonBatchingUsage() throws Exception {
final TransactionContext transactionContext = new TransactionContextImpl( new TransactionEnvironmentImpl( serviceRegistry ) );
final TransactionContext transactionContext = new TransactionContextImpl(
new TransactionEnvironmentImpl( serviceRegistry )
);
TransactionCoordinatorImpl transactionCoordinator = new TransactionCoordinatorImpl( null, transactionContext );
JournalingTransactionObserver observer = new JournalingTransactionObserver();

View File

@ -0,0 +1,62 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.multitenancy.schema;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
/**
* @author Steve Ebersole
*/
@Entity
public class Customer {
private Long id;
private String name;
public Customer() {
}
public Customer(String name) {
this.name = name;
}
@Id
@GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,206 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.multitenancy.schema;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import org.hibernate.HibernateException;
import org.hibernate.MultiTenancyStrategy;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.internal.BasicServiceRegistryImpl;
import org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl;
import org.hibernate.service.jdbc.connections.spi.AbstractMultiTenantConnectionProvider;
import org.hibernate.service.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.hibernate.tool.hbm2ddl.ConnectionHelper;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.hibernate.testing.env.ConnectionProviderBuilder;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.hibernate.testing.junit4.BaseUnitTestCase;
/**
* @author Steve Ebersole
*/
public class SchemaBasedMultiTenancyTest extends BaseUnitTestCase {
private DriverManagerConnectionProviderImpl acmeProvider;
private DriverManagerConnectionProviderImpl jbossProvider;
private ServiceRegistryImplementor serviceRegistry;
private SessionFactory sessionFactory;
@Before
public void setUp() {
acmeProvider = ConnectionProviderBuilder.buildConnectionProvider( "acme" );
jbossProvider = ConnectionProviderBuilder.buildConnectionProvider( "jboss" );
AbstractMultiTenantConnectionProvider multiTenantConnectionProvider = new AbstractMultiTenantConnectionProvider() {
@Override
protected ConnectionProvider getAnyConnectionProvider() {
return acmeProvider;
}
@Override
protected ConnectionProvider selectConnectionProvider(String tenantIdentifier) {
if ( "acme".equals( tenantIdentifier ) ) {
return acmeProvider;
}
else if ( "jboss".equals( tenantIdentifier ) ) {
return jbossProvider;
}
throw new HibernateException( "Unknown tenant identifier" );
}
};
Configuration cfg = new Configuration();
cfg.getProperties().put( Environment.MULTI_TENANT, MultiTenancyStrategy.DATABASE );
cfg.addAnnotatedClass( Customer.class );
cfg.buildMappings();
// do the acme export
new SchemaExport(
new ConnectionHelper() {
private Connection connection;
@Override
public void prepare(boolean needsAutoCommit) throws SQLException {
connection = acmeProvider.getConnection();
}
@Override
public Connection getConnection() throws SQLException {
return connection;
}
@Override
public void release() throws SQLException {
acmeProvider.closeConnection( connection );
}
},
cfg.generateDropSchemaScript( ConnectionProviderBuilder.getCorrespondingDialect() ),
cfg.generateSchemaCreationScript( ConnectionProviderBuilder.getCorrespondingDialect() )
).execute( // so stupid...
false, // do not script the export (write it to file)
true, // do run it against the database
false, // do not *just* perform the drop
false // do not *just* perform the create
);
// do the jboss export
new SchemaExport(
new ConnectionHelper() {
private Connection connection;
@Override
public void prepare(boolean needsAutoCommit) throws SQLException {
connection = jbossProvider.getConnection();
}
@Override
public Connection getConnection() throws SQLException {
return connection;
}
@Override
public void release() throws SQLException {
jbossProvider.closeConnection( connection );
}
},
cfg.generateDropSchemaScript( ConnectionProviderBuilder.getCorrespondingDialect() ),
cfg.generateSchemaCreationScript( ConnectionProviderBuilder.getCorrespondingDialect() )
).execute( // so stupid...
false, // do not script the export (write it to file)
true, // do run it against the database
false, // do not *just* perform the drop
false // do not *just* perform the create
);
serviceRegistry = new BasicServiceRegistryImpl( cfg.getProperties() );
serviceRegistry.registerService( MultiTenantConnectionProvider.class, multiTenantConnectionProvider );
sessionFactory = cfg.buildSessionFactory( serviceRegistry );
}
@After
public void tearDown() {
if ( sessionFactory != null ) {
sessionFactory.close();
}
if ( serviceRegistry != null ) {
serviceRegistry.destroy();
}
if ( jbossProvider != null ) {
jbossProvider.stop();
}
if ( acmeProvider != null ) {
acmeProvider.stop();
}
}
private Session openSession() {
return sessionFactory.openSession();
}
@Test
public void testBasicExpectedBehavior() {
Session session = openSession();
session.setTenantIdentifier( "jboss" );
session.beginTransaction();
Customer steve = new Customer( "steve" );
session.save( steve );
session.getTransaction().commit();
session.close();
session = openSession();
try {
session.setTenantIdentifier( "acme" );
session.beginTransaction();
Customer check = (Customer) session.get( Customer.class, steve.getId() );
Assert.assertNull( "tenancy not properly isolated", check );
}
finally {
session.getTransaction().commit();
session.close();
}
session = openSession();
session.setTenantIdentifier( "jboss" );
session.beginTransaction();
session.delete( steve );
session.getTransaction().commit();
session.close();
}
}

View File

@ -29,7 +29,6 @@ import org.hibernate.cfg.Environment;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl;
import org.hibernate.service.jdbc.connections.spi.ConnectionProvider;
/**
* Defines the JDBC connection information (currently H2) used by Hibernate for unit (not functional!) tests
@ -38,24 +37,36 @@ import org.hibernate.service.jdbc.connections.spi.ConnectionProvider;
*/
public class ConnectionProviderBuilder {
public static final String DRIVER = "org.h2.Driver";
public static final String URL = "jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE";
public static final String URL = "jdbc:h2:mem:%s;DB_CLOSE_DELAY=-1;MVCC=TRUE";
public static final String USER = "sa";
public static final String PASS = "";
public static Properties getConnectionProviderProperties() {
public static Properties getConnectionProviderProperties(String dbName) {
Properties props = new Properties( null );
props.put( Environment.DRIVER, DRIVER );
props.put( Environment.URL, URL );
props.put( Environment.URL, String.format( URL, dbName ) );
props.put( Environment.USER, USER );
props.put( Environment.PASS, PASS );
return props;
}
public static ConnectionProvider buildConnectionProvider() {
public static Properties getConnectionProviderProperties() {
return getConnectionProviderProperties( "db1" );
}
public static DriverManagerConnectionProviderImpl buildConnectionProvider() {
return buildConnectionProvider( false );
}
public static ConnectionProvider buildConnectionProvider(final boolean allowAggressiveRelease) {
final Properties props = getConnectionProviderProperties();
public static DriverManagerConnectionProviderImpl buildConnectionProvider(String dbName) {
return buildConnectionProvider( getConnectionProviderProperties( dbName ), false );
}
public static DriverManagerConnectionProviderImpl buildConnectionProvider(final boolean allowAggressiveRelease) {
return buildConnectionProvider( getConnectionProviderProperties( "db1" ), allowAggressiveRelease );
}
private static DriverManagerConnectionProviderImpl buildConnectionProvider(Properties props, final boolean allowAggressiveRelease) {
DriverManagerConnectionProviderImpl connectionProvider = new DriverManagerConnectionProviderImpl() {
public boolean supportsAggressiveRelease() {
return allowAggressiveRelease;