Car*_*ten 17 orm spring hibernate multi-tenant
我目前正在尝试使用单独的Schema aproach为多租户设置Hibernate.
在完成了大约2天的工作并浏览了几乎所有我可以通过Google找到的来源后,我开始变得非常沮丧.
Basicaly我试图遵循Hibernate devguide http://docs.jboss.org/hibernate/orm/4.1/devguide/en-US/html_single/#d5e4691中提供的指南
但不幸的是我无法找到ConnectionProviderUtils到构建ConnectionProvider.目前我想弄清楚2点:
为什么永远不会调用我的MSSQLMultiTenantConnectionProvider的configure(Properties props)方法.根据我从不同的其他ConnectionProvider实现的来源和描述中解释,我假设将调用此方法来初始化ConnectionProvider.
由于我无法使用configure(属性道具),我尝试了以某种方式获取应用程序Context和hibernate.cfg.xml中指定的hibernate属性和DataSource的其他方法.(就像将数据源直接注入ConnectionProvider一样)
任何可能的解决方法的指针(方法,类,教程)
以下是我的实现的相关部分:
Data Source和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>
Run Code Online (Sandbox Code Playgroud)
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;
}
}
Run Code Online (Sandbox Code Playgroud)
现在有两种可能的方法,我看到从配置文件获得我需要的配置.要么运行configure()方法,要么以某种方式使DataSource的注入成为可能.我想第一个会是更好的方式.
值得一提的是,我只为一个租户启动并运行Hibernate(不使用MultiTenantConnectionProvider,使用Hibernate使用的标准ConnectionProvider)
已经非常感谢正在阅读这篇文章的人.期待着答案.
最好的祝福
我已经玩了一下这个,并将连接详细信息硬编码到我的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>
Run Code Online (Sandbox Code Playgroud)
这是异常堆栈跟踪的顶部:
由org.springframework上org.springframework.orm.hibernate4.HringnateTransactionManager.afterPropertiesSet(HibernateTransactionManager.java:264)的org.springframework.orm.hibernate4.SessionFactoryUtils.getDataSource(SessionFactoryUtils.java:101)中的java.lang.NullPointerException引起.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1514)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1452)
我在调试模式下追溯了这个问题,发现问题是我的SessionFactory在某种程度上没有得到DataSource.(无论我是否在hibernate.cfg.xml中指定DataSource都没有区别)但是在初始化TransactionManager时,它尝试从SessionFactory获取DataSource并因此失败并返回NullPointerException.有没有人暗示休眠的内部工作在哪个方面失败?在我看到的所有文档和帖子中,没有迹象表明我需要处理将DataSource注入SessionFactory.现在我只是猜测我试图找出如何将DataSource放入所需的位置或如何更改初始化流程.如果有人有更好的主意,我会非常高兴.
编辑:现在也在Hibernate论坛中发布:
所以我设法通过将TransactionManager中的autodetectDataSource属性设置为false来解决此问题:
<property name="autodetectDataSource" value="false"/>
Run Code Online (Sandbox Code Playgroud)
我从以下帖子http://forum.springsource.org/showthread.php?123478-SessionFactory-configured-for-multi-tenancy-but-no-tenant-identifier-specified获得了这个提示.不幸的是,我现在陷入了这个问题.^^"但这是另一个主题的问题.(编辑:原来这只是早期测试中的错误配置+一个旧的依赖项)
至于这个主题,问题仍然是我想以某种方式能够重用DataSource,我已经在配置中使用了Spring Security,因为Hibernate避免了在两个地方配置DataSource的需要.所以问题仍然是如何在我的MultiTenantConnectionProvider中集成DataSource的使用.有没有人知道在哪里可以找到任何提示?
mhn*_*oka 13
根据史蒂夫·埃伯索尔关于JIRA问题的评论,该问题的评论者之一(HHH-8752):
首先,Hibernate"实例化由...... MULTI_TENANT_CONNECTION_PROVIDER和MULTI_TENANT_IDENTIFIER_RESOLVER引用的类"是不正确的.Hibernate首先尝试将这些设置视为其预期类型的对象(MULTI_TENANT_CONNECTION_PROVIDER的MultiTenantConnectionProvider和MULTI_TENANT_IDENTIFIER_RESOLVER的CurrentTenantIdentifierResolver.
所以直接传递你的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;
}
}
Run Code Online (Sandbox Code Playgroud)
这被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;
}
}
Run Code Online (Sandbox Code Playgroud)
最后,这是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;
}
}
Run Code Online (Sandbox Code Playgroud)
我正在使用的数据源由Spring Boot自动提供.
我希望这有帮助.
Car*_*ten 11
好了把它包起来,这就是我最后得到的结果.我使用一个简单的CurrentTenantIdentifierResolver.而不是尝试将DataSource从其他地方注入我的MultiTenantConnectionProviderImpl,而是在ConnectionProvider中创建DataSource(c3p0 ComboPooledDatasource),并仅使用我的ConnectionProvider提供的连接开始.所以我删除了额外的DataSource.为了使DataSource的属性易于配置,我选择从属性文件中获取配置数据.
CurrentTenantIdentifierResolverImpl:
public class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver {
/**
* The method returns the RequestServerName as tenantidentifier.
* If no FacesContext is available null is returned.
*
* @return String tenantIdentifier
*/
@Override
public String resolveCurrentTenantIdentifier() {
if (FacesContext.getCurrentInstance() != null){
return FacesContext.getCurrentInstance().getExternalContext().getRequestServerName();
} else {
return null;
}
}
@Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
Run Code Online (Sandbox Code Playgroud)
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 );
}
}
}
Run Code Online (Sandbox Code Playgroud)
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>
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
从另一个文件以类似的方式获取凭证.
任何提供改进的反馈表示赞赏.
小智 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>
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
22066 次 |
最近记录: |