18

我目前正在尝试使用单独的模式方法为多租户设置 Hibernate。
在研究了大约 2 天并浏览了几乎所有我可以通过 Google 找到的资源之后,我开始感到非常沮丧。

基本上我正在尝试遵循 Hibernate devguide http://docs.jboss.org/hibernate/orm/4.1/devguide/en-US/html_single/#d5e4691
中提供的指南 但不幸的是我无法找到 ConnectionProviderUtils构建 ConnectionProvider。目前我正在尝试找出 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(意味着不使用 MultiTenantConnectionProvider,使用 Hibernate 使用的标准 ConnectionProvider)

已经非常感谢任何正在阅读这篇文章的人。期待答案。

此致

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

这是异常堆栈跟踪的顶部:

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

我在调试模式下追踪了这个问题,发现问题是我的 SessionFactory 不知何故没有掌握数据源。(我是否在 hibernate.cfg.xml 中指定 DataSource 没有区别)但是在初始化 TransactionManager 时,它尝试从 SessionFactory 获取 DataSource 并因此失败并出现 NullPointerException。有没有人暗示休眠的内部工作在哪一点失败了?在我看到的所有文档和帖子中,没有迹象表明我需要处理将 DataSource 注入到 SessionFactory 中。现在我只是想弄清楚如何将 DataSource 放入所需的位置或如何更改初始化流程。如果有人有更好的主意,我会非常高兴。

编辑:现在也在 Hibernate 论坛上发布了这个:

更新 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中得到了这个提示。不幸的是,我现在完全陷入了这个问题。^^" 但这是另一个主题的问题。(编辑:原来这只是早期测试的错误配置+一个旧的依赖项)

至于这个主题,问题仍然是我希望能够以某种方式重用 DataSource,无论如何我已经在配置中使用 Spring Security,以便 Hibernate 避免需要在两个地方配置 DataSource。所以问题仍然是如何在我的 MultiTenantConnectionProvider 中集成 DataSource 的使用。有没有人知道在哪里可以找到任何提示?

4

5 回答 5

14

根据Steve Ebersole对这个问题的一位评论者(HHH-8752)提到的 JIRA 问题的评论:

首先,Hibernate “实例化由 ... MULTI_TENANT_CONNECTION_PROVIDER 和 MULTI_TENANT_IDENTIFIER_RESOLVER 引用的类”根本不是真的。Hibernate 首先尝试将这些设置视为其预期类型的​​对象(MultiTenantConnectionProvider 用于 MULTI_TENANT_CONNECTION_PROVIDER 和 CurrentTenantIdentifierResolver 用于 MULTI_TENANT_IDENTIFIER_RESOLVER。

因此,只需直接传递您的 bean,根据需要进行配置。

我只是按照他的建议并设法使它起作用。

这是一个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 Boot自动提供。

我希望这有帮助。

于 2015-08-05T07:25:21.250 回答
12

好的,总结一下,这就是我最终得到的结果。我使用一个简单的 CurrentTenantIdentifierResolver。而不是尝试从其他地方将 DataSource 注入到我的 MultiTenantConnectionProviderImpl 我在 ConnectionProvider 中创建 DataSource (c3p0 ComboPooledDatasource) 并开始仅使用我的 ConnectionProvider 提供的连接。所以我消除了额外的数据源。为了使 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

凭据以类似的方式从另一个文件中获取。

感谢任何提供改进的反馈。

于 2013-08-12T07:14:09.763 回答
5

使用<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>
于 2013-10-25T19:26:56.210 回答
4

从 Spring Framework 版本 3.2.4 开始,无法让 Spring 容器管理 MultiTenantConnectionProvider 和 CurrentTenantIdentifierResolver。这会产生许多障碍,例如使用已配置的 DataSource、WebContext 和其他 Spring 托管 bean 和特性。我试图找到一个更清洁的解决方案,但只提出了一个:

扩展 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'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 请求..

于 2013-08-22T23:54:55.980 回答
0

使用这些人的回应和这个链接,我把它放在一起,没有 Spring 或 C3P0 以外的任何其他东西。

我必须将这两个属性添加到我的休眠配置中

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();
    }
}
于 2013-09-25T02:25:24.667 回答