【问题标题】:Setting up a MultiTenantConnectionProvider using Hibernate 4.2 and Spring 3.1.1使用 Hibernate 4.2 和 Spring 3.1.1 设置 MultiTenantConnectionProvider
【发布时间】:2013-04-19 06:40:30
【问题描述】:

我目前正在尝试使用单独的架构方法为多租户设置 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>

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

引起:java.lang.NullPointerException at 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 不知何故没有掌握数据源。 (我是否在 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 中得到了这个提示。不幸的是,我现在完全陷入了这个问题。 ^^" 但这是另一个主题的问题。(编辑:原来这只是早期测试的错误配置+一个旧依赖项)

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

【问题讨论】:

  • 运气好能找到注入数据源的方法吗?我也在为此苦苦挣扎(使用 c3p0 连接池)
  • 用 LocalSessionFactoryBuilder 的另一个实现覆盖 LocalSessionFactory 并添加多租户属性?公共 LocalSessionFactoryBuilder(DataSource 数据源,ResourceLoader 资源加载器) { getProperties().put(Environment.CURRENT_SESSION_CONTEXT_CLASS, SpringSessionContext.class.getName()); if (dataSource != null) { getProperties().put(Environment.DATASOURCE, dataSource); } getProperties().put(AvailableSettings.APP_CLASSLOADER, resourceLoader.getClassLoader()); this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader); }
  • 现在我选择了使用属性的解决方案。我想我应该发布我目前正在工作的解决方案。

标签: spring hibernate orm multi-tenant


【解决方案1】:

好的,总结一下,这就是我最终得到的以下内容。我使用一个简单的 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

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

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

【讨论】:

    【解决方案2】:

    从 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.tenant_identifier_resolver 和 hibernate.multi_tenant_connection_provider

    一切就绪,所有 bean 都由 Spring 管理。您可以再次使用 DI!希望这可以帮助某人。我确实为该功能提出了 Jira 请求..

    【讨论】:

    • 你能提供你的 MultiTenantConnectionProvider 和 TenantIdResolver 的实现吗?我看到你在 spring 配置中引用它。我试图让它与 oracle 一起工作,在概念证明中,稍后将发布所有代码。多租户使用 spring+hibernate 还是有点吓人。
    【解决方案3】:

    使用这些人的回复和这个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();
        }
    }
    

    【讨论】:

    • Alex ConnectionPropertiesUtils 是什么?
    • 这只是一个帮助类,我必须获取我的 Hibernate 属性。当我调用 getProperties() 时,它只是返回一个属性实例。当我调用 getProperty() 时,它返回一个字符串
    【解决方案4】:

    使用&lt;map&gt; 而不是&lt;props&gt; 的建议似乎对我有用。 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>
    

    【讨论】:

    • 它也适用于 Java config + JPA:@Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) { Map&lt;String, Object&gt; props = new HashMap&lt;&gt;(); props.put("hibernate.tenant_identifier_resolver", currentTenantIdentifierResolver()); props.put("hibernate.multi_tenant_connection_provider", multiTenantConnectionProvider()); props.put("hibernate.multiTenancy", "SCHEMA"); return builder.dataSource(arizonaDataSource()).packages(new String[] { /* ... */ }).properties(props).build(); }
    • @mhnagaoka - 使用 JPA,我尝试将连接提供程序指定为 spring bean,但我得到一个 NPE (pastebin.com/E3RwNt1y) 似乎 AbstractEntityManagerFactoryBean.getObject() 在 AbstractEntityManagerFactoryBean.afterPropertiesSet( )。知道可能出了什么问题吗?
    【解决方案5】:

    根据Steve Ebersole 的 cmets 关于此问题的一位评论者 (HHH-8752) 提到的 JIRA 问题:

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

    所以只需直接传入你的 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;
        }
    }
    

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

    我希望这会有所帮助。

    【讨论】:

    • 哦,我刚刚注意到 OP 使用的是普通的 Hibernate 而不是 JPA。无论如何,解决方案可能类似,使用LocalSessionFactoryBean而不是LocalContainerEntityManagerFactoryBean
    • 感谢您为答案添加另一个变体。是的,一般的解决方案是相同的,但是为使用 JPA 的人提供一个大纲可能仍然很好。
    • @mhnagaoka 谢谢你的评论。但是,由于一个问题,我无法使其正常工作- spring JPA 在启动时被初始化- 这意味着 URI 解析器上没有可用的请求范围。我有多种数据库类型,这就是为什么我的方言是动态的。请问有什么提示吗?我为此苦苦挣扎了 2 天。
    • @mhnagaoka 谢谢!我使用的是 Spring 4.2.5,不知何故它有这样的方法来支持 Hibernate 4,但只有其中一个支持 Hibernate 5... 从 4.3 开始,这两种方法都是 in place
    猜你喜欢
    • 2015-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-30
    • 2010-12-03
    • 2015-07-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多