Настройка MultiTenantConnectionProvider с использованием Hibernate 4.2 и Spring 3.1.1
В настоящее время я пытаюсь настроить Hibernate на несколько арендаторов, используя отдельный подход к схеме.
После работы над ним около 2 дней и просмотра почти каждого источника, который я могу найти через Google, я начинаю расстраиваться.
В основном я пытаюсь следовать руководству, представленному в Devguide Hibernate http://docs.jboss.org/hibernate/orm/4.1/devguide/en-US/html_single/#d5e4691
Но, к сожалению, я не могу найти ConnectionProviderUtils для создания ConnectionProvider.
В настоящее время я пытаюсь выяснить 2 очка:
-
Почему метод configure (Свойства реквизита) моего MSSQLMultiTenantConnectionProvider никогда не вызывается. Из того, что я интерпретировал из источника и описания различных других реализаций ConnectionProvider, я предполагаю, что этот метод будет вызван для инициализации ConnectionProvider.
-
Так как я не могу работать с настройкой (реквизиты свойств), я опробовал другие подходы к получению свойств hibernate и DataSource, указанных в приложении Context и hibernate.cfg.xml. (Как впрыскивание источника данных непосредственно в ConnectionProvider)
Любые указатели на возможные способы решения этой проблемы (Методы, Классы, Учебники)
Итак, вот некоторые составляющие моей реализации:
Источник данных и Hibernate.cfg.xml:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
<property name="url" value="jdbc:sqlserver://<host>:<port>;databaseName=<DbName>;" />
<property name="username" value=<username> />
<property name="password" value=<password> />
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<!-- property name="dataSource" ref="dataSource" /-->
<property name="annotatedClasses">
<list>
<value>c.h.utils.hibernate.User</value>
<value>c.h.utils.hibernate.Role</value>
<value>c.h.utils.hibernate.Tenant</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.SQLServerDialect
hibernate.show_sql=true
hibernate.multiTenancy=SCHEMA
hibernate.tenant_identifier_resolver=c.h.utils.hibernate.CurrentTenantIdentifierResolver
hibernate.multi_tenant_connection_provider=c.h.utils.hibernate.MSSQLMultiTenantConnectionProviderImpl
</value>
</property>
</bean>
MSSQLMultiTenantConnectionProviderImpl:
package c.hoell.utils.hibernate;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.hibernate.service.UnknownUnwrapTypeException;
import org.hibernate.service.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MSSQLMultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
private static final long serialVersionUID = 8074002161278796379L;
@Autowired
private DataSource dataSource;
public void configure(Properties props) throws HibernateException {
}
@Override
public Connection getAnyConnection() throws SQLException {
Properties properties = getConnectionProperties(); //method which sets the hibernate properties
DriverManagerConnectionProviderImpl defaultProvider = new DriverManagerConnectionProviderImpl();
defaultProvider.configure(properties);
Connection con = defaultProvider.getConnection();
ResultSet rs = con.createStatement().executeQuery("SELECT * FROM [schema].table");
rs.close(); //the statement and sql is just to test the connection
return defaultProvider.getConnection();
}
@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
<--not sure how to implement this-->
}
@Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connection.close();
}
@Override
public void releaseConnection(String tenantIdentifier, Connection connection){
try {
this.releaseAnyConnection(connection);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public boolean supportsAggressiveRelease() {
return false;
}
@Override
public boolean isUnwrappableAs(Class unwrapType) {
return ConnectionProvider.class.equals( unwrapType ) || MultiTenantConnectionProvider.class.equals( unwrapType ) || MSSQLMultiTenantConnectionProviderImpl.class.isAssignableFrom( unwrapType );
}
@SuppressWarnings("unchecked")
@Override
public <T> T unwrap(Class<T> unwrapType) {
if ( isUnwrappableAs( unwrapType ) ) {
return (T) this;
}
else {
throw new UnknownUnwrapTypeException( unwrapType );
}
}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
Прямо сейчас есть два возможных подхода, которые я вижу для получения конфигураций, которые мне нужны из конфигурационных файлов. Либо получите способ configure() для запуска, либо каким-то образом сделать инъекцию DataSource возможной. Я предполагаю, что первый из них был бы лучшим способом.
Важно отметить, что у меня был Hibernate и работает только для одного арендатора (это означает, что без использования MultiTenantConnectionProvider, используя стандартный ConnectionProvider, используемый Hibernate)
Уже большое спасибо всем, кто читает этот пост. С нетерпением ждем ответов.
С наилучшими пожеланиями
Обновление 1:
Я немного поиграл с этим и жестко закодировал ссылки на подключение в свой MultiTenantConnectionProvider (обновил код выше). Это отлично работает в отношении MultiTenantConnectionProvider. Но это все еще не решает моих проблем. Теперь мое приложение не удается инициализировать Менеджер транзакций:
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>
<bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
Это верхняя часть stacktrace > :
Вызвано: java.lang.NullPointerException в org.springframework.orm.hibernate4.SessionFactoryUtils.getDataSource(SessionFactoryUtils.java:101) в org.springframework.orm.hibernate4.HibernateTransactionManager.afterPropertiesSet(HibernateTransactionManager.java:264) в org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1514) в org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1452)
Я проследил эту проблему в режиме отладки и выяснил, что проблема в том, что мой SessionFactory каким-то образом не овладевает DataSource. (Не имеет значения, укажу ли я DataSource в файле hibernate.cfg.xml или нет)
Но при инициализации TransactionManager он пытается получить DataSource из SessionFactory и в результате не получается исключение NullPointerException. Есть ли у кого-нибудь намек на то, в какой точке внутренней работы спящего режима это терпит неудачу? Во всех документах и сообщениях, которые я видел, не было никаких указаний на то, что мне нужно обработать инъекцию DataSource в SessionFactory. На данный момент я просто думаю, что я пытаюсь выяснить, как получить DataSource в нужное место или как изменить поток инициализации. Если у кого-то есть лучшая идея, я был бы действительно счастлив.
Изменить: Также опубликовано это на форумах Hibernate:
Обновление 2:
Мне удалось обойти эту проблему, установив свойство autodetectDataSource в TransactionManager равным false:
<property name="autodetectDataSource" value="false"/>
Я получил этот намек со следующего сообщения http://forum.springsource.org/showthread.php?123478-SessionFactory-configured-for-multi-tenancy-but-no-tenant-identifier-specified. К сожалению, я сейчас придерживаюсь именно этой проблемы. ^^ "Но это проблема для другой темы. (Edit: Оказывается, это была неправильная конфигурация из более раннего тестирования + одна старая зависимость)
Что касается этого вопроса, остается проблема: я хочу как-то снова использовать DataSource, который у меня уже есть в конфигурации для использования Spring Security в любом случае, для Hibernate, чтобы избежать необходимости настраивать DataSource в двух местах. Поэтому остается вопрос, как интегрировать использование DataSource в моем MultiTenantConnectionProvider. Кто-нибудь имеет представление о том, где найти какие-либо намеки на это?
Ответы
Ответ 1
Хорошо, чтобы обернуть это, вот что я закончил со следующим. Я использую простой CurrentTenantIdentifierResolver. И вместо того, чтобы пытаться внедрить DataSource из другого места в мой MultiTenantConnectionProviderImpl, я создаю DataSource (c3p0 ComboPooledDatasource) в ConnectionProvider и начал использовать только соединения, предоставляемые моим ConnectionProvider. Поэтому я исключил дополнительный DataSource. Чтобы упростить конфигурирование свойств DataSource, я решил получить данные конфигурации из файла свойств.
CurrentTenantIdentifierResolverImpl:
public class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver {
/**
* The method returns the RequestServerName as tenantidentifier.
* If no FacesContext is available null is returned.
*
* @return String tenantIdentifier
*/
@Override
public String resolveCurrentTenantIdentifier() {
if (FacesContext.getCurrentInstance() != null){
return FacesContext.getCurrentInstance().getExternalContext().getRequestServerName();
} else {
return null;
}
}
@Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
MultiTenantConnectionProviderImpl:
Обратите внимание, что PropertyUtil - это просто простой вспомогательный класс для извлечения моих свойств. Поскольку это ничего особенного, я не буду включать его, чтобы не загромождать ответ.
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
private static final long serialVersionUID = 8074002161278796379L;
private static Logger log = LoggerFactory.getLogger(MultiTenantConnectionProviderImpl.class );
private ComboPooledDataSource cpds;
private Properties properties;
/**
*
* Constructor. Initializes the ComboPooledDataSource based on the config.properties.
*
* @throws PropertyVetoException
*/
public MultiTenantConnectionProviderImpl() throws PropertyVetoException {
log.info("Initializing Connection Pool!");
properties = new Properties();
try {
properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("config.properties"));
} catch (IOException e) {
throw new RuntimeException(e);
}
cpds = new ComboPooledDataSource("Example");
cpds.setDriverClass(properties.getProperty("jdbc.driver"));
cpds.setJdbcUrl(properties.getProperty("jdbc.url"));
cpds.setUser(properties.getProperty("jdbc.user"));
cpds.setPassword(PropertyUtil.getCredential("jdbc.password"));
log.info("Connection Pool initialised!");
}
@Override
public Connection getAnyConnection() throws SQLException {
log.debug("Get Default Connection:::Number of connections (max: busy - idle): {} : {} - {}",new int[]{cpds.getMaxPoolSize(),cpds.getNumBusyConnectionsAllUsers(),cpds.getNumIdleConnectionsAllUsers()});
if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize()){
log.warn("Maximum number of connections opened");
}
if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize() && cpds.getNumIdleConnectionsAllUsers()==0){
log.error("Connection pool empty!");
}
return cpds.getConnection();
}
@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
log.debug("Get {} Connection:::Number of connections (max: busy - idle): {} : {} - {}",new Object[]{tenantIdentifier, cpds.getMaxPoolSize(),cpds.getNumBusyConnectionsAllUsers(),cpds.getNumIdleConnectionsAllUsers()});
if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize()){
log.warn("Maximum number of connections opened");
}
if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize() && cpds.getNumIdleConnectionsAllUsers()==0){
log.error("Connection pool empty!");
}
return cpds.getConnection(tenantIdentifier, PropertyUtil.getCredential(tenantIdentifier));
}
@Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connection.close();
}
@Override
public void releaseConnection(String tenantIdentifier, Connection connection){
try {
this.releaseAnyConnection(connection);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean supportsAggressiveRelease() {
return false;
}
@SuppressWarnings("rawtypes")
@Override
public boolean isUnwrappableAs(Class unwrapType) {
return ConnectionProvider.class.equals( unwrapType ) || MultiTenantConnectionProvider.class.equals( unwrapType ) || MultiTenantConnectionProviderImpl.class.isAssignableFrom( unwrapType );
}
@SuppressWarnings("unchecked")
@Override
public <T> T unwrap(Class<T> unwrapType) {
if ( isUnwrappableAs( unwrapType ) ) {
return (T) this;
}
else {
throw new UnknownUnwrapTypeException( unwrapType );
}
}
}
Конкретная конфигурация c3p0 берется из c3p0-config.xml:
<c3p0-config>
<named-config name="Example">
<property name="acquireIncrement">3</property>
<property name="preferredTestQuery">SELECT 1</property>
<property name="checkoutTimeout">2000</property>
<property name="idleConnectionTestPeriod">30</property>
<property name="initialPoolSize">1</property>
<property name="maxIdleTime">18000</property>
<property name="maxPoolSize">30</property>
<property name="minPoolSize">1</property>
<property name="maxStatements">50</property>
<property name="testConnectionOnCheckin">true</property>
</named-config>
</c3p0-config>
И специальные свойства db предоставляются файлом config.properties:
jdbc.url=<serverUrl>
jdbc.driver=<driverClass>
jdbc.dbName=<dBname>
jdbc.dbowner=<dbo>
jdbc.username=<user>
jdbc.password=<password>
hibernate.dialect=<hibernateDialect>
hibernate.debug=false
Учетные данные получены аналогичным образом из другого файла.
Приветствуется любая обратная связь, обеспечивающая улучшения.
Ответ 2
По словам Стив Эберсол комментарии по вопросу JIRA, упомянутые одним из этих комментаторов вопросов (HHH-8752):
Ну, во-первых, просто неправда, что Hibernate "создает экземпляры классов, на которые ссылается... MULTI_TENANT_CONNECTION_PROVIDER и MULTI_TENANT_IDENTIFIER_RESOLVER". Hibernate сначала пытается рассматривать эти настройки как объекты их предполагаемых типов (MultiTenantConnectionProvider для MULTI_TENANT_CONNECTION_PROVIDER и CurrentTenantIdentifierResolver для MULTI_TENANT_IDENTIFIER_RESOLVER.
Итак, просто передайте свой beans напрямую, настроенный, тем не менее, вы хотите.
Я просто последовал его предложению и сумел заставить его работать.
Это CurrentTenantIdentifierResolver
, определяемый как Spring Bean:
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestURITenantIdentifierResolver implements CurrentTenantIdentifierResolver {
@Autowired
private HttpServletRequest request;
@Override
public String resolveCurrentTenantIdentifier() {
String[] pathElements = request.getRequestURI().split("/");
String tenant = pathElements[1];
return tenant;
}
@Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
Это MultiTenantConnectionProvider
, определяемый как Spring Bean:
@Component
public class SchemaPerTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
@Autowired
private DataSource dataSource;
@Override
public Connection getAnyConnection() throws SQLException {
return dataSource.getConnection();
}
@Override
public void releaseAnyConnection(final Connection connection) throws SQLException {
connection.close();
}
@Override
public Connection getConnection(final String tenantIdentifier) throws SQLException {
final Connection connection = getAnyConnection();
try {
connection.createStatement().execute("USE " + tenantIdentifier);
} catch (SQLException e) {
throw new HibernateException("Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]",
e);
}
return connection;
}
@Override
public void releaseConnection(final String tenantIdentifier, final Connection connection) throws SQLException {
try {
connection.createStatement().execute("USE dummy");
} catch (SQLException e) {
// on error, throw an exception to make sure the connection is not returned to the pool.
// your requirements may differ
throw new HibernateException(
"Could not alter JDBC connection to specified schema [" +
tenantIdentifier + "]",
e
);
} finally {
connection.close();
}
}
@Override
public boolean supportsAggressiveRelease() {
return true;
}
@Override
public boolean isUnwrappableAs(Class aClass) {
return false;
}
@Override
public <T> T unwrap(Class<T> aClass) {
return null;
}
}
И, наконец, это проводка LocalContainerEntityManagerFactoryBean
для использования двух компонентов выше:
@Configuration
public class HibernateConfig {
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
MultiTenantConnectionProvider multiTenantConnectionProvider,
CurrentTenantIdentifierResolver tenantIdentifierResolver) {
LocalContainerEntityManagerFactoryBean emfBean = new LocalContainerEntityManagerFactoryBean();
emfBean.setDataSource(dataSource);
emfBean.setPackagesToScan(VistoJobsApplication.class.getPackage().getName());
emfBean.setJpaVendorAdapter(jpaVendorAdapter());
Map<String, Object> jpaProperties = new HashMap<>();
jpaProperties.put(org.hibernate.cfg.Environment.MULTI_TENANT,
MultiTenancyStrategy.SCHEMA);
jpaProperties.put(org.hibernate.cfg.Environment.MULTI_TENANT_CONNECTION_PROVIDER,
multiTenantConnectionProvider);
jpaProperties.put(org.hibernate.cfg.Environment.MULTI_TENANT_IDENTIFIER_RESOLVER,
tenantIdentifierResolver);
emfBean.setJpaPropertyMap(jpaProperties);
return emfBean;
}
}
Источник данных, который я использую, автоматически предоставляется Spring Загрузка.
Надеюсь, это поможет.
Ответ 3
Полагаю, что использование <map>
вместо <props>
работает для меня. https://jira.springsource.org/browse/SPR-10823#comment-94855
<bean id="multiTenantConnectionProvider"
class="test.MultiTenantConnectionProviderImpl"/>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="packagesToScan" value="test.models" />
<property name="hibernateProperties">
<map>
<entry key="hibernate.dialect" value="org.hibernate.dialect.PostgreSQL82Dialect"/>
<entry key="hibernate.multiTenancy" value="SCHEMA"/>
<entry key="hibernate.tenant_identifier_resolver" value="test.CurrentTenantIdentifierResolverImpl"/>
<entry key="hibernate.multi_tenant_connection_provider" value-ref="multiTenantConnectionProvider"/>
</map>
</property>
</bean>
Ответ 4
Как и в случае с Spring Framework версии 3.2.4, нет возможности управлять MultiTenantConnectionProvider и CurrentTenantIdentifierResolver контейнером Spring. Это создает множество препятствий, таких как использование уже настроенного DataSource, WebContext и других Spring управляемых beans и функций. Я попытался найти более чистые решения, но придумал только один:
Расширьте org.springframework.orm.hibernate4.LocalSessionFactoryBuilder и напишите пользовательский файл LocalSessionFactoryBean (не можете подклассы и предоставлять LocalSessionFactoryBuilder, в основном копию оригинала с небольшим изменением)
Здесь:
package com.levitech.hibernate;
import javax.sql.DataSource;
import org.hibernate.cfg.Environment;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.springframework.core.io.ResourceLoader;
public class CustomLocalSessionFactoryBuilder extends org.springframework.orm.hibernate4.LocalSessionFactoryBuilder {
public CustomLocalSessionFactoryBuilder(DataSource dataSource,ResourceLoader resourceLoader, MultiTenantConnectionProvider connectionProvider,
CurrentTenantIdentifierResolver tenantIdResolver) {
super(dataSource, resourceLoader);
getProperties().put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, connectionProvider);
getProperties().put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, tenantIdResolver);
}
}
Замена LocalSessionFactoryBean (единственное изменение в методе afterPropertiesSet() используется для использования настраиваемого LocalSessionFactoryBuilder):
package com.levitech.hibernate;
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.File;
import java.io.IOException;
import java.util.Properties;
import javax.sql.DataSource;
import org.hibernate.Interceptor;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.NamingStrategy;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.orm.hibernate4.HibernateExceptionTranslator;
import org.springframework.orm.hibernate4.LocalSessionFactoryBuilder;
/**
* {@link org.springframework.beans.factory.FactoryBean} that creates a Hibernate
* {@link org.hibernate.SessionFactory}. This is the usual way to set up a shared
* Hibernate SessionFactory in a Spring application context; the SessionFactory can
* then be passed to Hibernate-based data access objects via dependency injection.
*
* <p><b>NOTE:</b> This variant of LocalSessionFactoryBean requires Hibernate 4.0 or higher.
* It is similar in role to the same-named class in the {@code orm.hibernate3} package.
* However, in practice, it is closer to {@code AnnotationSessionFactoryBean} since
* its core purpose is to bootstrap a {@code SessionFactory} from annotation scanning.
*
* <p><b>NOTE:</b> To set up Hibernate 4 for Spring-driven JTA transactions, make
* sure to either specify the {@link #setJtaTransactionManager "jtaTransactionManager"}
* bean property or to set the "hibernate.transaction.factory_class" property to
* {@link org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory}.
* Otherwise, Hibernate smart flushing mechanism won't work properly.
*
* @author Juergen Hoeller
* @since 3.1
* @see #setDataSource
* @see #setPackagesToScan
* @see LocalSessionFactoryBuilder
*/
public class CustomLocalSessionFactoryBean extends HibernateExceptionTranslator
implements FactoryBean<SessionFactory>, ResourceLoaderAware, InitializingBean, DisposableBean {
private MultiTenantConnectionProvider multiTenantConnectionProvider;
private CurrentTenantIdentifierResolver tenantIdResolver;
private DataSource dataSource;
private Resource[] configLocations;
private String[] mappingResources;
private Resource[] mappingLocations;
private Resource[] cacheableMappingLocations;
private Resource[] mappingJarLocations;
private Resource[] mappingDirectoryLocations;
private Interceptor entityInterceptor;
private NamingStrategy namingStrategy;
private Properties hibernateProperties;
private Class<?>[] annotatedClasses;
private String[] annotatedPackages;
private String[] packagesToScan;
private Object jtaTransactionManager;
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
private Configuration configuration;
private SessionFactory sessionFactory;
public MultiTenantConnectionProvider getMultiTenantConnectionProvider() {
return multiTenantConnectionProvider;
}
public void setMultiTenantConnectionProvider(
MultiTenantConnectionProvider multiTenantConnectionProvider) {
this.multiTenantConnectionProvider = multiTenantConnectionProvider;
}
public CurrentTenantIdentifierResolver getTenantIdResolver() {
return tenantIdResolver;
}
public void setTenantIdResolver(CurrentTenantIdentifierResolver tenantIdResolver) {
this.tenantIdResolver = tenantIdResolver;
}
/**
* Set the DataSource to be used by the SessionFactory.
* If set, this will override corresponding settings in Hibernate properties.
* <p>If this is set, the Hibernate settings should not define
* a connection provider to avoid meaningless double configuration.
*/
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* Set the location of a single Hibernate XML config file, for example as
* classpath resource "classpath:hibernate.cfg.xml".
* <p>Note: Can be omitted when all necessary properties and mapping
* resources are specified locally via this bean.
* @see org.hibernate.cfg.Configuration#configure(java.net.URL)
*/
public void setConfigLocation(Resource configLocation) {
this.configLocations = new Resource[] {configLocation};
}
/**
* Set the locations of multiple Hibernate XML config files, for example as
* classpath resources "classpath:hibernate.cfg.xml,classpath:extension.cfg.xml".
* <p>Note: Can be omitted when all necessary properties and mapping
* resources are specified locally via this bean.
* @see org.hibernate.cfg.Configuration#configure(java.net.URL)
*/
public void setConfigLocations(Resource[] configLocations) {
this.configLocations = configLocations;
}
/**
* Set Hibernate mapping resources to be found in the class path,
* like "example.hbm.xml" or "mypackage/example.hbm.xml".
* Analogous to mapping entries in a Hibernate XML config file.
* Alternative to the more generic setMappingLocations method.
* <p>Can be used to add to mappings from a Hibernate XML config file,
* or to specify all mappings locally.
* @see #setMappingLocations
* @see org.hibernate.cfg.Configuration#addResource
*/
public void setMappingResources(String[] mappingResources) {
this.mappingResources = mappingResources;
}
/**
* Set locations of Hibernate mapping files, for example as classpath
* resource "classpath:example.hbm.xml". Supports any resource location
* via Spring resource abstraction, for example relative paths like
* "WEB-INF/mappings/example.hbm.xml" when running in an application context.
* <p>Can be used to add to mappings from a Hibernate XML config file,
* or to specify all mappings locally.
* @see org.hibernate.cfg.Configuration#addInputStream
*/
public void setMappingLocations(Resource[] mappingLocations) {
this.mappingLocations = mappingLocations;
}
/**
* Set locations of cacheable Hibernate mapping files, for example as web app
* resource "/WEB-INF/mapping/example.hbm.xml". Supports any resource location
* via Spring resource abstraction, as long as the resource can be resolved
* in the file system.
* <p>Can be used to add to mappings from a Hibernate XML config file,
* or to specify all mappings locally.
* @see org.hibernate.cfg.Configuration#addCacheableFile(java.io.File)
*/
public void setCacheableMappingLocations(Resource[] cacheableMappingLocations) {
this.cacheableMappingLocations = cacheableMappingLocations;
}
/**
* Set locations of jar files that contain Hibernate mapping resources,
* like "WEB-INF/lib/example.hbm.jar".
* <p>Can be used to add to mappings from a Hibernate XML config file,
* or to specify all mappings locally.
* @see org.hibernate.cfg.Configuration#addJar(java.io.File)
*/
public void setMappingJarLocations(Resource[] mappingJarLocations) {
this.mappingJarLocations = mappingJarLocations;
}
/**
* Set locations of directories that contain Hibernate mapping resources,
* like "WEB-INF/mappings".
* <p>Can be used to add to mappings from a Hibernate XML config file,
* or to specify all mappings locally.
* @see org.hibernate.cfg.Configuration#addDirectory(java.io.File)
*/
public void setMappingDirectoryLocations(Resource[] mappingDirectoryLocations) {
this.mappingDirectoryLocations = mappingDirectoryLocations;
}
/**
* Set a Hibernate entity interceptor that allows to inspect and change
* property values before writing to and reading from the database.
* Will get applied to any new Session created by this factory.
* @see org.hibernate.cfg.Configuration#setInterceptor
*/
public void setEntityInterceptor(Interceptor entityInterceptor) {
this.entityInterceptor = entityInterceptor;
}
/**
* Set a Hibernate NamingStrategy for the SessionFactory, determining the
* physical column and table names given the info in the mapping document.
* @see org.hibernate.cfg.Configuration#setNamingStrategy
*/
public void setNamingStrategy(NamingStrategy namingStrategy) {
this.namingStrategy = namingStrategy;
}
/**
* Set Hibernate properties, such as "hibernate.dialect".
* <p>Note: Do not specify a transaction provider here when using
* Spring-driven transactions. It is also advisable to omit connection
* provider settings and use a Spring-set DataSource instead.
* @see #setDataSource
*/
public void setHibernateProperties(Properties hibernateProperties) {
this.hibernateProperties = hibernateProperties;
}
/**
* Return the Hibernate properties, if any. Mainly available for
* configuration through property paths that specify individual keys.
*/
public Properties getHibernateProperties() {
if (this.hibernateProperties == null) {
this.hibernateProperties = new Properties();
}
return this.hibernateProperties;
}
/**
* Specify annotated entity classes to register with this Hibernate SessionFactory.
* @see org.hibernate.cfg.Configuration#addAnnotatedClass(Class)
*/
public void setAnnotatedClasses(Class<?>[] annotatedClasses) {
this.annotatedClasses = annotatedClasses;
}
/**
* Specify the names of annotated packages, for which package-level
* annotation metadata will be read.
* @see org.hibernate.cfg.Configuration#addPackage(String)
*/
public void setAnnotatedPackages(String[] annotatedPackages) {
this.annotatedPackages = annotatedPackages;
}
/**
* Specify packages to search for autodetection of your entity classes in the
* classpath. This is analogous to Spring component-scan feature
* ({@link org.springframework.context.annotation.ClassPathBeanDefinitionScanner}).
*/
public void setPackagesToScan(String... packagesToScan) {
this.packagesToScan = packagesToScan;
}
/**
* Set the Spring {@link org.springframework.transaction.jta.JtaTransactionManager}
* or the JTA {@link javax.transaction.TransactionManager} to be used with Hibernate,
* if any.
* @see LocalSessionFactoryBuilder#setJtaTransactionManager
*/
public void setJtaTransactionManager(Object jtaTransactionManager) {
this.jtaTransactionManager = jtaTransactionManager;
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
}
public void afterPropertiesSet() throws IOException {
LocalSessionFactoryBuilder sfb = new CustomLocalSessionFactoryBuilder(this.dataSource, this.resourcePatternResolver, multiTenantConnectionProvider, tenantIdResolver);
if (this.configLocations != null) {
for (Resource resource : this.configLocations) {
// Load Hibernate configuration from given location.
sfb.configure(resource.getURL());
}
}
if (this.mappingResources != null) {
// Register given Hibernate mapping definitions, contained in resource files.
for (String mapping : this.mappingResources) {
Resource mr = new ClassPathResource(mapping.trim(), this.resourcePatternResolver.getClassLoader());
sfb.addInputStream(mr.getInputStream());
}
}
if (this.mappingLocations != null) {
// Register given Hibernate mapping definitions, contained in resource files.
for (Resource resource : this.mappingLocations) {
sfb.addInputStream(resource.getInputStream());
}
}
if (this.cacheableMappingLocations != null) {
// Register given cacheable Hibernate mapping definitions, read from the file system.
for (Resource resource : this.cacheableMappingLocations) {
sfb.addCacheableFile(resource.getFile());
}
}
if (this.mappingJarLocations != null) {
// Register given Hibernate mapping definitions, contained in jar files.
for (Resource resource : this.mappingJarLocations) {
sfb.addJar(resource.getFile());
}
}
if (this.mappingDirectoryLocations != null) {
// Register all Hibernate mapping definitions in the given directories.
for (Resource resource : this.mappingDirectoryLocations) {
File file = resource.getFile();
if (!file.isDirectory()) {
throw new IllegalArgumentException(
"Mapping directory location [" + resource + "] does not denote a directory");
}
sfb.addDirectory(file);
}
}
if (this.entityInterceptor != null) {
sfb.setInterceptor(this.entityInterceptor);
}
if (this.namingStrategy != null) {
sfb.setNamingStrategy(this.namingStrategy);
}
if (this.hibernateProperties != null) {
sfb.addProperties(this.hibernateProperties);
}
if (this.annotatedClasses != null) {
sfb.addAnnotatedClasses(this.annotatedClasses);
}
if (this.annotatedPackages != null) {
sfb.addPackages(this.annotatedPackages);
}
if (this.packagesToScan != null) {
sfb.scanPackages(this.packagesToScan);
}
if (this.jtaTransactionManager != null) {
sfb.setJtaTransactionManager(this.jtaTransactionManager);
}
// Build SessionFactory instance.
this.configuration = sfb;
this.sessionFactory = buildSessionFactory(sfb);
}
/**
* Subclasses can override this method to perform custom initialization
* of the SessionFactory instance, creating it via the given Configuration
* object that got prepared by this LocalSessionFactoryBean.
* <p>The default implementation invokes LocalSessionFactoryBuilder buildSessionFactory.
* A custom implementation could prepare the instance in a specific way (e.g. applying
* a custom ServiceRegistry) or use a custom SessionFactoryImpl subclass.
* @param sfb LocalSessionFactoryBuilder prepared by this LocalSessionFactoryBean
* @return the SessionFactory instance
* @see LocalSessionFactoryBuilder#buildSessionFactory
*/
protected SessionFactory buildSessionFactory(LocalSessionFactoryBuilder sfb) {
return sfb.buildSessionFactory();
}
/**
* Return the Hibernate Configuration object used to build the SessionFactory.
* Allows for access to configuration metadata stored there (rarely needed).
* @throws IllegalStateException if the Configuration object has not been initialized yet
*/
public final Configuration getConfiguration() {
if (this.configuration == null) {
throw new IllegalStateException("Configuration not initialized yet");
}
return this.configuration;
}
public SessionFactory getObject() {
return this.sessionFactory;
}
public Class<?> getObjectType() {
return (this.sessionFactory != null ? this.sessionFactory.getClass() : SessionFactory.class);
}
public boolean isSingleton() {
return true;
}
public void destroy() {
this.sessionFactory.close();
}
}
В контексте вашего приложения определите beans:
<bean id="multiTenantProvider" class="com.levitech.hibernate.MultiTenantConnectionProviderImpl" depends-on="myDataSource" lazy-init="false"></bean>
<bean id="tenantIdResolver" class="com.levitech.hibernate.TenantIdResolver"></bean>
<bean id="sessionFactory" class="com.levitech.hibernate.CustomLocalSessionFactoryBean" depends-on="liquibase, myDataSource, multiTenantProvider">
<property name="dataSource" ref="myDataSource"></property>
<property name="multiTenantConnectionProvider" ref="multiTenantProvider"></property>
<property name="tenantIdResolver" ref="tenantIdResolver"></property>
<property name="mappingLocations" value="classpath*:hibernate/**/*.hbm.xml" />
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
hibernate.show_sql=true
hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory
hibernate.cache.use_query_cache=true
hibernate.cache.use_second_level_cache=true
hibernate.multiTenancy=SCHEMA
</value>
</property>
</bean>
В настройках спящего режима НЕ указывайте следующие значения:
hibernate.tenant_identifier_resolver и
hibernate.multi_tenant_connection_provider
Вы все настроены, и все ваши beans управляются Spring. Вы можете снова использовать DI! Надеюсь, это поможет кому-то. Я поставил запрос Джиры на эту функцию.
Ответ 5
Используя ответы этих парней и ссылку я поставил это без Spring или что-то еще, кроме C3P0.
Мне пришлось добавить эти 2 свойства в мою конфигурацию hibernate
properties.setProperty("hibernate.multiTenancy", "SCHEMA");
properties.setProperty("hibernate.multi_tenant_connection_provider", MultiTenantConnectionProviderImpl.class.getName());
HibernateUtils.java
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author Alex
*/
public class HibernateUtils {
private static final Logger logger = LoggerFactory.getLogger(HibernateUtils.class);
private static SessionFactory sessionFactory;
static{
init();
}
public static void init(){
try {
Configuration configuration = new Configuration()
.setProperties(ConnectionPropertiesUtils.getProperties());
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();
sessionFactory = configuration.buildSessionFactory(serviceRegistry);
} catch (Exception e) {
logger.error(e.getMessage());
}
}
public static Session getTenantSession(String tenant){
return getSession(tenant);
}
public static Session getAuthSession(){
return getSession("AUTH");
}
public static Session getLogSession(){
return getSession("LOG");
}
public static Session getConfigSession(){
return getSession("CONFIG");
}
public static Session getSession(String tenant)
throws HibernateException {
if(sessionFactory == null){
init();
}
return sessionFactory.withOptions().tenantIdentifier(tenant).openSession();
}
@Deprecated
public static Session getSession()
throws HibernateException {
if(sessionFactory == null){
init();
}
return sessionFactory.openSession();
}
}
И MultiTenantConnectionProviderImpl.java
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;
import org.hibernate.HibernateException;
import org.hibernate.service.UnknownUnwrapTypeException;
import org.hibernate.service.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.hibernate.service.spi.Stoppable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Simplistic implementation for illustration purposes showing a single
* connection pool used to serve multiple schemas using "connection altering".
* Here we use the T-SQL specific USE command; Oracle users might use the ALTER
* SESSION SET SCHEMA command; etc.
*/
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider, Stoppable {
private static Logger log = LoggerFactory.getLogger(MultiTenantConnectionProviderImpl.class);
private ComboPooledDataSource cpds;
public MultiTenantConnectionProviderImpl() throws PropertyVetoException {
log.info("Initializing Connection Pool!");
cpds = new ComboPooledDataSource("Example");
cpds.setDriverClass(ConnectionPropertiesUtils.getProperty("hibernate.connection.driver_class"));
cpds.setJdbcUrl(ConnectionPropertiesUtils.getProperty("hibernate.connection.url"));
cpds.setUser(ConnectionPropertiesUtils.getProperty("hibernate.connection.username"));
cpds.setPassword(ConnectionPropertiesUtils.getProperty("hibernate.connection.password"));
log.info("Connection Pool initialised!");
}
@Override
public Connection getAnyConnection() throws SQLException {
log.debug("Get Default Connection:::Number of connections (max: busy - idle): {} : {} - {}", new int[]{cpds.getMaxPoolSize(), cpds.getNumBusyConnectionsAllUsers(), cpds.getNumIdleConnectionsAllUsers()});
if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize()) {
log.warn("Maximum number of connections opened");
}
if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize() && cpds.getNumIdleConnectionsAllUsers() == 0) {
log.error("Connection pool empty!");
}
return cpds.getConnection();
}
@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
final Connection connection = getAnyConnection();
try {
//This is DB specific syntax. This work for MSSQL and MySQL
//Oracle uses the ALTER SESSION SET SCHEMA command
connection.createStatement().execute("USE " + tenantIdentifier);
} catch (SQLException e) {
throw new HibernateException("Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]", e);
}
return connection;
}
@Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connection.close();
}
@Override
public void releaseConnection(String tenantIdentifier, Connection connection) {
try {
this.releaseAnyConnection(connection);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean supportsAggressiveRelease() {
return false;
}
@SuppressWarnings("rawtypes")
@Override
public boolean isUnwrappableAs(Class unwrapType) {
return ConnectionProvider.class.equals(unwrapType) || MultiTenantConnectionProvider.class.equals(unwrapType) || MultiTenantConnectionProviderImpl.class.isAssignableFrom(unwrapType);
}
@SuppressWarnings("unchecked")
@Override
public <T> T unwrap(Class<T> unwrapType) {
if (isUnwrappableAs(unwrapType)) {
return (T) this;
} else {
throw new UnknownUnwrapTypeException(unwrapType);
}
}
public void stop() {
cpds.close();
}
}