我目前正在尝试使用单独的Schema方法为多租户设置Hibernate。 经过大约2天的研究并浏览了几乎所有我可以通过Google找到的资源,我开始感到非常沮丧。
基本上,我试图遵循Hibernate开发指南http://docs.jboss.org/hibernate/orm/4.1/devguide/zh-CN/html_single/#d5e4691中提供的指南, 但是很遗憾,我找不到ConnectionProviderUtils来建立ConnectionProvider。目前,我正在尝试找出2点:
为什么从不调用我的MSSQLMultiTenantConnectionProvider的configure(Properties props)方法。根据我从其他ConnectionProvider实现的来源和描述中所解释的内容,我假设将调用此方法来初始化ConnectionProvider。
由于我无法使用configure(Properties props),因此尝试了其他方法来以某种方式获取应用程序Context和hibernate.cfg.xml中指定的hibernate属性和数据源。(就像将数据源直接注入到ConnectionProvider中一样)
解决此问题的可能方法的任何指针(方法,类,教程)
所以这是我实现的相关部分: 数据源和Hibernate.cfg.xml:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" /> <property name="url" value="jdbc:sqlserver://<host>:<port>;databaseName=<DbName>;" /> <property name="username" value=<username> /> <property name="password" value=<password> /> </bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> <!-- property name="dataSource" ref="dataSource" /--> <property name="annotatedClasses"> <list> <value>c.h.utils.hibernate.User</value> <value>c.h.utils.hibernate.Role</value> <value>c.h.utils.hibernate.Tenant</value> </list> </property> <property name="hibernateProperties"> <value> hibernate.dialect=org.hibernate.dialect.SQLServerDialect hibernate.show_sql=true hibernate.multiTenancy=SCHEMA hibernate.tenant_identifier_resolver=c.h.utils.hibernate.CurrentTenantIdentifierResolver hibernate.multi_tenant_connection_provider=c.h.utils.hibernate.MSSQLMultiTenantConnectionProviderImpl </value> </property> </bean>
MSSQLMultiTenantConnectionProviderImpl:
package c.hoell.utils.hibernate; import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; import org.hibernate.service.UnknownUnwrapTypeException; import org.hibernate.service.jdbc.connections.spi.ConnectionProvider; import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class MSSQLMultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider { private static final long serialVersionUID = 8074002161278796379L; @Autowired private DataSource dataSource; public void configure(Properties props) throws HibernateException { } @Override public Connection getAnyConnection() throws SQLException { Properties properties = getConnectionProperties(); //method which sets the hibernate properties DriverManagerConnectionProviderImpl defaultProvider = new DriverManagerConnectionProviderImpl(); defaultProvider.configure(properties); Connection con = defaultProvider.getConnection(); ResultSet rs = con.createStatement().executeQuery("SELECT * FROM [schema].table"); rs.close(); //the statement and sql is just to test the connection return defaultProvider.getConnection(); } @Override public Connection getConnection(String tenantIdentifier) throws SQLException { <--not sure how to implement this--> } @Override public void releaseAnyConnection(Connection connection) throws SQLException { connection.close(); } @Override public void releaseConnection(String tenantIdentifier, Connection connection){ try { this.releaseAnyConnection(connection); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public boolean supportsAggressiveRelease() { return false; } @Override public boolean isUnwrappableAs(Class unwrapType) { return ConnectionProvider.class.equals( unwrapType ) || MultiTenantConnectionProvider.class.equals( unwrapType ) || MSSQLMultiTenantConnectionProviderImpl.class.isAssignableFrom( unwrapType ); } @SuppressWarnings("unchecked") @Override public <T> T unwrap(Class<T> unwrapType) { if ( isUnwrappableAs( unwrapType ) ) { return (T) this; } else { throw new UnknownUnwrapTypeException( unwrapType ); } } public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } }
现在,我发现有两种可能的方法可以从配置文件中获取所需的配置。使configure()方法运行或以某种方式使注入DataSource成为可能。我想第一个会是更好的方法。
值得一提的是,我只为一个租户运行了Hibernate(这意味着不使用MultiTenantConnectionProvider,而是使用Hibernate使用的标准ConnectionProvider)
非常感谢正在阅读这篇文章的任何人。期待答案。
我对此进行了一些尝试,并将connectiondetails硬编码到我的MultiTenantConnectionProvider中(更新了上面的代码)。就MultiTenantConnectionProvider而言,这工作正常。但这仍然不能解决我的问题。现在,我的应用程序无法初始化事务管理器:
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/> <bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
这是异常stacktrace的顶部:
Caused by: java.lang.NullPointerException at org.springframework.orm.hibernate4.SessionFactoryUtils.getDataSource(SessionFactoryUtils.java:101) at org.springframework.orm.hibernate4.HibernateTransactionManager.afterPropertiesSet(HibernateTransactionManager.java:264) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1514) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1452)
我在调试模式下跟踪了此问题,发现问题出在我的SessionFactory无法以某种方式掌握数据源。(是否在hibernate.cfg.xml中指定DataSource都没有区别),但是在初始化TransactionManager时,它将尝试从SessionFactory获取DataSource并因此失败,并出现NullPointerException。有没有人暗示hibernate内部工作的哪一点失败了?在我看到的所有文档和帖子中,没有迹象表明我需要处理将DataSource注入SessionFactory的过程。现在,我只是想尝试找出如何将DataSource放入所需位置或如何更改初始化流程。如果有人有更好的主意,我会很高兴。
编辑:现在也将其发布在Hibernate论坛中:
所以我设法通过将TransactionManager中的autodetectDataSource属性设置为false来解决此问题:
<property name="autodetectDataSource" value="false"/>
至于这个主题,问题仍然是我希望能够以某种方式重用DataSource,以便Hibernate避免在两个地方配置DataSource。因此问题仍然存在,即如何在我的MultiTenantConnectionProvider中集成数据源的使用。有谁知道在哪里可以找到任何提示吗?
首先,Hibernate“实例化… MULTI_TENANT_CONNECTION_PROVIDER和MULTI_TENANT_IDENTIFIER_RESOLVER引用的类”是不对的。Hibernate首先尝试将这些设置视为其预期类型的对象((用于MULTI_TENANT_CONNECTION_PROVIDER的MultiTenantConnectionProvider和用于MULTI_TENANT_IDENTIFIER_RESOLVER的CurrentTenantIdentifierResolver。
因此,只需将你的bean直接传递,即可根据需要进行配置。
我只是听从他的建议,并设法使它可行。
这是CurrentTenantIdentifierResolver定义为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; } }
这是MultiTenantConnectionProvider定义为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利用上述两个组件的连接:
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; } }