web-dev-qa-db-ja.com

Hibernate 4.2およびSpring 3.1.1を使用したMultiTenantConnectionProviderのセットアップ

現在、別のスキーマアプローチを使用してマルチテナンシー用にHibernateをセットアップしようとしています。
約2日間作業し、Googleで見つけたほぼすべてのソースを閲覧した後、私はかなりイライラし始めています。

基本的に私はHibernate devguide http://docs.jboss.org/hibernate/orm/4.1/devguide/en-US/html_single/#d5e4691 で提供されているガイドに従うようにしています
残念ながら、ConnectionProviderを構築するためのConnectionProviderUtilsを見つけることができません。現在、私は2つのポイントを理解しようとしています:

  1. MSSQLMultiTenantConnectionProviderのconfigure(Properties props)メソッドが呼び出されないのはなぜですか。他のさまざまなConnectionProvider実装のソースと説明から解釈した内容から、このメソッドがConnectionProviderを初期化するために呼び出されると想定しています。

  2. 私はconfigure(Properties props)を操作できないので、アプリケーションのコンテキストと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;
    }

}

現在、必要な構成を構成ファイルから取得するには、2つの方法が考えられます。 configure()メソッドを実行するか、何らかの方法でDataSourceの注入を可能にします。最初の方が良い方法だと思います。

言及すべき重要なことは、Hibernateを1つのテナントのみで稼働させていることです(つまり、MultiTenantConnectionProviderを使用せず、Hibernateで使用される標準のConnectionProviderを使用します)。

この投稿を読んでくださった方には、すでに感謝しています。答えを楽しみにしています。

宜しくお願いします

更新1:

私はこれを少し試し、接続詳細をMultiTenantConnectionProviderにハードコードしました(上記のコードを更新しました)。これは、MultiTenantConnectionProviderに関しては正常に機能しています。しかし、これはまだ私の問題を解決していません。 Transaction Managerの初期化でアプリケーションが失敗します:

<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>

これは例外スタックトレースの先頭です:

原因:org.springframework.orm.hibernate4.SessionFactoryUtils.getDataSource(SessionFactoryUtils.Java:101)at org.springframework.orm.hibernate4.HibernateTransactionManager.afterPropertiesSet(HibernateTransactionManager.Java:264)at org.springframeworkのJava.lang.NullPointerException org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.Java:1452)の.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.Java:1514)

この問題をデバッグモードで追跡しましたが、SessionFactoryがどういうわけかDataSourceを保持していないことが問題であることがわかりました。 (hibernate.cfg.xmlでDataSourceを指定してもしなくてもかまいません)しかし、TransactionManagerを初期化すると、SessionFactoryからDataSourceを取得しようとし、結果としてNullPointerExceptionで失敗します。誰もがこれが失敗している休止状態の内部動作のどの時点でヒントを持っていますか?私が見たすべてのドキュメントと投稿で、SessionFactoryへのDataSourceの注入を処理する必要があるという指摘はありませんでした。とりあえず、DataSourceを必要な場所に配置する方法、または初期化フローを変更する方法を理解しようとしていると思います。誰かがより良いアイデアを持っているなら、私は本当に幸せです。

編集:これもHibernateフォーラムに投稿されました:

Update 2:

したがって、TransactionManagerのautodetectDataSourceプロパティをfalseに設定することで、この問題を回避することができました。

<property name="autodetectDataSource" value="false"/>

私は次の投稿からこのヒントを得ました http://forum.springsource.org/showthread.php?123478-SessionFactory-configured-for-multi-tenancy-but-no-tenant-identifier-specified 。残念ながら、私は今まさにその問題に行き詰まっています。 ^^ "しかし、これは別のトピックの問題です(編集:これは、以前のテストと1つの古い依存関係の構成ミスだけであることがわかりました)

このトピックに関しては、Spring Securityを使用するための構成で既に持っているDataSourceを何らかの方法で再利用して、Hibernateが2か所でDataSourceを構成する必要を回避できるようにしたいという問題が残っています。したがって、質問は、DataSourceの使用をMultiTenantConnectionProviderに統合する方法を示しています。どこにヒントを見つけるかについて誰かがアイデアを持っていますか?

17
Carsten

まとめると、次のようになります。単純なCurrentTenantIdentifierResolverを使用します。そして、DataSourceを別の場所からMultiTenantConnectionProviderImplに注入しようとする代わりに、ConnectionProviderにDataSource(c3p0 ComboPooledDatasource)を作成し、my 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

資格情報は、別のファイルから同様の方法でフェッチされます。

改善を提供するフィードバックはありがたいです。

11
Carsten

この質問のコメント投稿者の1人が参照したJIRAの問題に関する Steve Ebersole のコメントによると( HHH-8752 ):

最初に、Hibernateが「... MULTI_TENANT_CONNECTION_PROVIDERおよびMULTI_TENANT_IDENTIFIER_RESOLVERによって参照されるクラスをインスタンス化する」ということは単に正しくありません。 Hibernateは最初にこれらの設定を目的のタイプのオブジェクトとして処理しようとします(MULTI_TENANT_CONNECTION_PROVIDERのMultiTenantConnectionProviderおよびMULTI_TENANT_IDENTIFIER_RESOLVERのCurrentTenantIdentifierResolver。

だから、あなたが望むように設定されたBeanを直接渡してください。

私は彼の提案に従って、それを機能させることができました

これは、Spring Beanとして定義されているCurrentTenantIdentifierResolverです。

@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;
    }
}

これは、Spring Beanとして定義されているMultiTenantConnectionProviderです。

@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;
    }
}

そして最後に、これは上記の2つのコンポーネントを利用するように配線された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 Boot によって自動的に利用可能になります。

これがお役に立てば幸いです。

13
mhnagaoka

<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>
5
Andrew Chen

Spring Frameworkバージョン3.2.4以降、MultiTenantConnectionProviderとCurrentTenantIdentifierResolverをSpringコンテナで管理する方法はありません。これにより、すでに構成されているDataSource、WebContext、およびその他のSpringマネージドBeanと機能の使用など、多くの障害が生じます。私はよりクリーンな解決策を見つけようとしましたが、1つだけ思いつきました:

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の置き換え(唯一の変更は、カスタムLocalSessionFactoryBuilderを使用するためのafterPropertiesSet()メソッドにあります):

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's 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's 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's 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's 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's 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();
    }

}

アプリケーションコンテキストでBeanを定義します。

<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のプロパティでhibernate.tenant_identifier_resolverおよびhibernate.multi_tenant_connection_providerの値を指定しないでください。

これですべての設定が完了し、すべてのBeanがSpringで管理されます。あなたは再びDIを自由に使用できます!これが誰かを助けることを願っています。 Jiraに機能のリクエストをしました。

4
user979051

これらの人たちの応答とこれ link を使用して、SpringやC3P0以外の何もない状態でまとめました。

これら2つのプロパティを休止状態の構成に追加する必要がありました

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();
    }
}
0
Alex