使用injectservices来休眠多租户实现问题

时间:2014-12-28 12:45:07

标签: hibernate

认为我非常接近,如果这有效,它应该会帮助很多人:) 是关于SO的这个非常有用的链接:Manage Connection Pooling in multi-tenant web app with Spring, Hibernate and C3P0
我的问题是在我的类中实现 - MultiTenantConnectionProvider

我没有得到javax.sql.Datasource的句柄 这是我的完整代码:
spring应用程序上下文:

<bean id="testService" class="com.mkyong.common.service.TestServiceImpl" lazy-init="true">
    <property name="testDao" ref="testDao" />
</bean>

<bean id="testDao" class="com.mkyong.common.dao.TestDaoImpl" lazy-init="true">
    <!-- Injecting Standard Session Factory -->
    <property name="sessionFactory" ref="sessionFactoryWorking" />
</bean>

<!-- this seems to work -->
<jee:jndi-lookup id="dataSource" jndi-name="MYSQLDS"/>

<!-- SessionFactories -->
<!-- Standard Session Factory -->
<bean id="sessionFactoryWorking" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    <property name="configLocation" value="classpath:local.JADE.PIT.hibernate.cfg.xml" />
    <property name="dataSource" ref="dataSource" />
</bean>

<bean id="transactionManagerWorking" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="autodetectDataSource" value="false" />
    <property name="sessionFactory" ref="sessionFactoryWorking" />
</bean>  

在文件 local.JADE.PIT.hibernate.cfg.xml

<hibernate-configuration>
<session-factory>
    <property name="show_sql">true</property>
    <property name="multiTenancy">SCHEMA</property>
    <property name="multi_tenant_connection_provider">com.mkyong.common.provider.MySQLMultiTenantConnectionProviderImpl</property>
    &globalpit;
</session-factory>

这是我的 MySQLMultiTenantConnectionProviderImpl

package com.mkyong.common.provider;

import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.service.UnknownUnwrapTypeException;
import org.hibernate.service.spi.ServiceRegistryAwareService;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.cfg.Environment;

import java.util.Map;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;

public class MySQLMultiTenantConnectionProviderImpl implements  MultiTenantConnectionProvider,ServiceRegistryAwareService{

private DataSource lazyDatasource;;

@Override
public void injectServices(ServiceRegistryImplementor serviceRegistry) {
    Map lSettings = serviceRegistry.getService(ConfigurationService.class).getSettings();
    System.out.println(" satish ********************** " + Environment.DATASOURCE );
    System.out.println(" satish ********************** " + lSettings.get( Environment.DATASOURCE ) );
    lazyDatasource = (DataSource) lSettings.get( Environment.DATASOURCE );
}

@Override
public boolean supportsAggressiveRelease() {
    System.out.println("<<<<<<< satish supportsAggressiveRelease >>>>>>>>>");
    /** this method must be overriden **/
    return false;
}

@Override
public void releaseConnection(String tenantIdentifier, Connection connection){
    /** this method must be overriden **/
    System.out.println("<<<<<<< satish releaseConnection 1 >>>>>>>>>");
    try {
        this.releaseAnyConnection(connection);
    }catch (SQLException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

@Override
public void releaseAnyConnection(Connection connection) throws SQLException {
    System.out.println("<<<<<<< satish releaseAnyConnection 2 >>>>>>>>>");
    /** this method must be overriden **/
    connection.close();
}

 @Override
 public Connection getConnection(String tenantIdentifier) throws SQLException {
    System.out.println("<<<<<<< satish getConnection 1 >>>>>>>>>");
    final Connection connection = getAnyConnection();
    System.out.println("<<<<<<< satish getConnection 2 >>>>>>>>>");
    try {
       /** this is the place where we can change our schema based on identifier **/
       connection.createStatement().execute("USE " + tenantIdentifier );
    }catch (SQLException e) {
       e.printStackTrace();
       throw new HibernateException("Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]", e);
    }
    return connection;
 }

 @Override
 public Connection getAnyConnection() throws SQLException {
/** this method is getting called first **/ 
System.out.println("<<<<<<< satish getAnyConnection >>>>>>>>>");
return lazyDatasource.getConnection();
}

@SuppressWarnings("unchecked")
@Override
public <T> T unwrap(Class<T> unwrapType) {
    if ( isUnwrappableAs( unwrapType ) ) {
        return (T) this;
    }else {
        throw new UnknownUnwrapTypeException( unwrapType );
    }

}

@Override
public boolean isUnwrappableAs(Class unwrapType) {
 return ConnectionProvider.class.equals( unwrapType ) ||  MultiTenantConnectionProvider.class.equals( unwrapType ) || MySQLMultiTenantConnectionProviderImpl.class.isAssignableFrom( unwrapType );
}
}  

这是我的DAO:

public class TestDaoImpl{

/** schema choice which comes from UI / http - which is outside the scope of this example **/
private String schema = null;

/** this is the injected way which works **/
private SessionFactory sessionFactory;

public final void setSessionFactory(SessionFactory sessionFactory) {
    this.sessionFactory = sessionFactory;
}

public void setSchema(String schema) {
            this.schema = schema;
}

public Session getCurrentSession() {
    Session session = null;
    try {
        **/** this is where we are getting a connection based on client / tenant **/**
        session = getSessionFactory().withOptions().tenantIdentifier(schema).openSession();
    } catch (HibernateException e) {
        e.printStackTrace();
        System.out.println("<<<<<< inside exception while getting session from sf >>>>>");
        session = getSessionFactory().openSession(); 
    }

    return session;
}
@SuppressWarnings("unchecked")
public List<Person> list() {
    Session session = getCurrentSession();
    List<Person> personList = session.createQuery("from Person").list();
    session.close();
    return personList;
}

}

所以正在发生的是方法 - injectServices - 它被调用了 但是,数据源在以下行为空:
lSettings.get(Environment.DATASOURCE)

如果我在cfg.xml文件中设置数据源名称 -
MYSQLDS

然后我将返回的值作为'MYSQLDS' - 但它是一个String - 所以它在尝试强制转换为javax.sql.DataSource时失败

还有几点说明:
阅读Hibernate文档非常重要逐字逐句 - 它们在提供示例方面很差 - 但文档具有全新的增强意义 - 如果您仔细阅读:) - {{3 }}

注意 - 我的数据源是weblogic jndi提供的数据源
谢谢

EDIT 1 2014年12月29日上午11:35 IST
正如M Deinum所述 - 问题是我没有连接我的数据源 现在它确实在工作 - 更新上面的代码 - 这样就可以工作并帮助其他人了! 重申步骤的执行顺序:
在我们的UI层中,我们知道哪个客户端正在访问应用程序 我们将此客户/租户信息传递给DAO层
DAO注入了在Spring中配置的SessionFactory 在DAO中 - 我们基于租户得到会话 - getCurrentSession()
这对于获得基于承租人的承认非常重要 session = getSessionFactory()。withOptions()。tenantIdentifier(schema).openSession();

现在我们有了会话,我们就打电话了:

List<Person> personList = session.createQuery("from Person").list();  

此时调用类 MySQLMultiTenantConnectionProviderImpl 的以下方法

public Connection getConnection(String tenantIdentifier) throws SQLException {  

这是mojo发生/我们应该写的地方 - 你改变了架构 在我的情况下,我使用MySQL,因此语法会根据使用的数据库而有所不同:

connection.createStatement().execute("USE " + tenantIdentifier );  

多数认为 - 这种多租户方法(单独的架构)现在可以使用了 还有一个注意事项 - 我没有使用 - CurrentTenantIdentifierResolver
来自Hibernate文档:(我不明白下面的第一段 - 但第二段似乎表明如果从SessionFactory指定租户标识符则不需要这个类) - 这就是我正在做的所以我没有定义类 - CurrentTenantIdentifierResolver

  

有两种情况使用CurrentTenantIdentifierResolver:

The first situation is when the application is using the  
org.hibernate.context.spi.CurrentSessionContext feature in conjunction with multi-tenancy.  
In the case of the current-session feature, Hibernate will need to open a session if it cannot  
find an existing one in scope. However, when a session is opened in a multi-tenant environment  
the tenant identifier has to be specified. This is where the CurrentTenantIdentifierResolver  
comes into play; Hibernate will consult the implementation you provide to determine the tenant  
identifier to use when opening the session. In this case, it is required that a  
CurrentTenantIdentifierResolver be supplied.

The other situation is when you do not want to have to explicitly specify the tenant  identifier  
all the time as we saw in Example 16.1, “Specifying tenant identifier from SessionFactory”.  
If a CurrentTenantIdentifierResolver has been specified, Hibernate will use it to determine  
the default tenant identifier to use when opening the session.

再次非常感谢M Deinum在这个方向上轻推我并帮助他们 谢谢

1 个答案:

答案 0 :(得分:2)

<jee:jndi-lookup id="dataSource" jndi-name="MYSQLDS"/>

<bean id="sessionFactoryWorking" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    <property name="configLocation" value="classpath:local.JADE.PIT.hibernate.cfg.xml" />
</bean>

在上面的配置中,查找DataSource就好了。它并没有被使用它只是坐着。您希望通过将其注入LocalSessionFactoryBean属性将其连接到dataSource

<bean id="sessionFactoryWorking" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    <property name="configLocation" value="classpath:local.JADE.PIT.hibernate.cfg.xml" />
    <property name="dataSource" ref="dataSource" />
</bean>

使用此配置,数据源已连线并可用。

hibernate配置文件中的另一件事connection.*属性由于注入了数据源而无用。