注入服务的休眠多租户实现问题 [英] hibernate multitenancy implementation issue with injectservices

查看:150
本文介绍了注入服务的休眠多租户实现问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

认为我非常接近,如果这个工作,它应该会帮助很多人:)

是关于这个非常有用的链接SO:



  • 我的问题是,在我的类实现 - MultiTenantConnectionProvider

    我没有得到javax.sql.Datasource的句柄

    以下是我的完整代码:

    弹出应用程序上下文:

     < bean id = testServiceclass =com.mkyong.common.service.TestServiceImpllazy-init =true> 
    < property name =testDaoref =testDao/>
    < / bean>

    < bean id =testDaoclass =com.mkyong.common.dao.TestDaoImpllazy-init =true>
    <! - 注入标准会话工厂 - >
    < property name =sessionFactoryref =sessionFactoryWorking/>
    < / bean>

    <! - 这似乎有效 - >
    < jee:jndi-lookup id =dataSourcejndi-name =MYSQLDS/>

    <! - - SessionFactories - >
    <! - 标准会话工厂 - >
    < bean id =sessionFactoryWorkingclass =org.springframework.orm.hibernate4.LocalSessionFactoryBean>
    < property name =dataSourceref =dataSource/>
    < / bean>

    < bean id =transactionManagerWorkingclass =org.springframework.orm.hibernate4.HibernateTransactionManager>
    < property name =autodetectDataSourcevalue =false/>
    < property name =sessionFactoryref =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; 

    导入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;

    公共类MySQLMultiTenantConnectionProviderImpl实现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>>>>>>>>);
    / **这个方法必须被覆盖** /
    return false;

    $ b @Override
    public void releaseConnection(String tenantIdentifier,Connection connection){
    / **此方法必须被覆盖** /
    系统.out.println(<<<<<<<< satish releaseConnection 1>>>>>>>>);
    尝试{
    this.releaseAnyConnection(connection);
    } catch(SQLException e){
    // TODO自动生成的catch块
    e.printStackTrace();
    }
    }

    @Override
    public void releaseAnyConnection(Connection connection)throws SQLException {
    System.out.println(<<<< ;<<<<< satish releaseAnyConnection 2>>>>>>>>);
    / **这个方法必须被覆盖** /
    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>>>>>>>>>」);
    尝试{
    / **这是我们可以根据标识符改变模式的地方** /
    connection.createStatement()。execute(USE+ tenantIdentifier);
    } catch(SQLException e){
    e.printStackTrace();
    抛出新的HibernateException(无法将JDBC连接更改为指定模式[+ tenantIdentifier +],e);
    }
    返回连接;

    $ b @Override
    public Connection getAnyConnection()throws SQLException {
    / **此方法首先被调用** /
    System.out .println(<<<<<<<< satish getAnyConnection>>>>>>>>」);
    返回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);


    $ / code $ / pre

    这是我的DAO:

      public class TestDaoImpl {

    / **来自UI / http的模式选择 - 这超出了本示例的范围** /
    private String schema = null;

    / **这是注入的方式** /
    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;
    尝试{
    ** / **这是我们获取基于客户端/租户的连接** / **
    session = getSessionFactory()。withOptions()。tenantIdentifier(模式).openSession();
    } catch(HibernateException e){
    e.printStackTrace();
    System.out.println(<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    session = getSessionFactory()。openSession();
    }

    返回会话;
    }
    @SuppressWarnings(unchecked)
    public List< Person> list(){
    Session session = getCurrentSession();
    列表< Person> personList = session.createQuery(from Person)。list();
    session.close();
    返回personList;
    }

    }

    所以发生的是,方法 - injectServices - 它被调用

    然而,数据源在下面一行为null:

    lSettings.get(Environment.DATASOURCE)



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

    MYSQLDS



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



    还有一些注释:

    阅读Hibernate文档逐字非常重要 - 它们在给出示例时很差 - 但该文档具有全新的和增强的含义 - 如果您仔细阅读它的话) -
    http://docs.jboss.org/hibernate/orm/4.1/devguide/en-US/html/ch16.html



    注 - 我的数据源是一个weblogic jndi提供的数据源

    谢谢

    编辑1 12月2014 2014 11:35 AM IST < br>
    正如M Deinum所述 - 问题是我没有连线数据源

    现在确实正在运行 - 上面的代码已更新 - 所以这应该可以工作并且帮助其他人!
    重新执行步骤的顺序:

    在我们的UI层中,我们知道哪个客户端正在访问应用程序

    我们将此客户端/租户信息传递给DAO层

    在DAO中注入了在Spring中配置的SessionFactory

    在DAO中 - 我们获得了基于租户的会话 - getCurrentSession()

    这对于获得基于租户的合作非常重要

    session = getSessionFactory()。withOptions()。tenantIdentifier(schema).openSession();



    现在,一旦我们有了会话,我们就可以进行呼叫:

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

    此时,调用 MySQLMultiTenantConnectionProviderImpl p>

      public Connection getConnection(String tenantIdentifier)throws SQLException {

    这是mojo发生/我们期望写的地方 - 并且您更改架构

    在我的情况下,我使用的是MySQL,因此语法会有所不同取决于正在使用的数据库:

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

    多数民众赞成的 - 这种多租户方式(单独架构)现在可以工作 -
    One更多的注意事项 - 我没有使用 - CurrentTenantIdentifierResolver

    从Hibernate文档:(我不明白下面的第一段 - 但第二段似乎表明,如果从SessionFactory指定租户标识符,这个类是不必要的) - 这是我所做的,所以我没有定义类 - CurrentTenantIdentifierResolver


    有两种情况使用CurrentTenantIdentifierResolver:




     第一种情况是当应用程序使用
    org.hibernate.context.spi .CurrentSessionContext功能与多租户结合使用。
    在当前会话特性的情况下,如果不能
    在范围内找到现有会话,Hibernate将需要打开一个会话。但是,当在多租户环境
    中打开会话时,必须指定租户标识符。这是CurrentTenantIdentifierResolver
    发挥作用的地方; Hibernate将查询您提供的实现来确定打开会话时使用的租户
    标识符。在这种情况下,需要提供
    CurrentTenantIdentifierResolver。

    另一种情况是,您不希望必须像示例16.1从SessionFactory指定租户标识符中看到的那样,必须始终明确指定租户标识符

    如果指定了CurrentTenantIdentifierResolver,Hibernate将使用它来确定
    打开会话时使用的默认租户标识符。

    再次非常感谢M Deinum在这个方向上推动我并帮助
    Thanks

    解决方案

     < jee:jndi-lookup id =dataSource JNDI名称= MYSQLDS/> 

    < bean id =sessionFactoryWorkingclass =org.springframework.orm.hibernate4.LocalSessionFactoryBean>
    < / bean>

    在上面的配置中查看 DataSource 这就是它。它不被用于它只是坐在附近。您想通过将它注入到 dataSource 属性中来将它连接到 LocalSessionFactoryBean

     < bean id =sessionFactoryWorkingclass =org.springframework.orm.hibernate4.LocalSessionFactoryBean> 
    < property name =dataSourceref =dataSource/>
    < / bean>

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



    您的hibernate配置文件中的另一件事是连接。* 属性由于注入的数据源而无用。


    Think I am very close and if this works it should help a lot of people :)
    was following this very useful link on SO : Manage Connection Pooling in multi-tenant web app with Spring, Hibernate and C3P0
    My problem is that in my class that implements - MultiTenantConnectionProvider

    I am not getting a handle to the javax.sql.Datasource
    Here is my full code:
    springs application context:

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

    In the file 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>
    

    Here is my 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 );
    }
    }  
    

    Here is my 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;
    }
    

    }

    So what is happening is that the method - injectServices - it is getting invoked
    However the Datasource is null at the following line:
    lSettings.get( Environment.DATASOURCE )

    If I set the datasource name in my cfg.xml file -
    MYSQLDS

    Then I do get the returned value as 'MYSQLDS' - but it is a String - so it then fails while trying to cast to a javax.sql.DataSource

    A few more notes:
    It is extremely extremely important to read Hibernate docs word by word - they are poor in giving examples - but the documentation takes on a completely new and enhanced meaning - if you read it carefully :) - http://docs.jboss.org/hibernate/orm/4.1/devguide/en-US/html/ch16.html

    NOTE - my datasource is a weblogic jndi provided datasource
    Thanks

    EDIT 1 Dec 29 2014 11:35 AM IST
    As stated by M Deinum - the issue was that I had not wired my datasource
    Now it is indeed working - updated code above - so that this should work and help others too !
    Restating the order of execution of steps:
    In our UI layer we are aware of which client is accessing the application
    We pass this client / tenant information to the DAO layer
    The DAO is injected with the SessionFactory which is configured in Spring
    In the DAO - we get session based on tenant - getCurrentSession()
    This is very important to get tenant based conenction
    session = getSessionFactory().withOptions().tenantIdentifier(schema).openSession();

    Now once we have the session we make the call:

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

    At this time the below method of the class MySQLMultiTenantConnectionProviderImpl gets invoked

    public Connection getConnection(String tenantIdentifier) throws SQLException {  
    

    This is the place where the mojo happens / we are expected to write - and you change the schema
    In my case I am using MySQL so the syntax would vary depending on the database being used:

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

    Thats it - this approach of multitenancy ( separate schema ) now works
    One more note - I did not use - CurrentTenantIdentifierResolver
    From Hibernate Documentation: ( I did not understand the first paragraph below - but the second paragraph seemed to indicate that this class is not necessary if specifying tenant identifier from SessionFactory ) - which is what I am doing so I did not define the class - CurrentTenantIdentifierResolver

    There are 2 situations where CurrentTenantIdentifierResolver is used:

    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.
    

    Once again a big thanks to M Deinum for nudging me in this direction and helping out
    Thanks

    解决方案

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

    In the configuration above the DataSource is looked up and well that is it. It isn't used it just sits around. You want to connect it to your LocalSessionFactoryBean by injecting it into the dataSource property.

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

    With this configuration the datasource is wired and will be available.

    Another thing in your hibernate configuration file the connection.* properties are useless due to the injected datasource.

    这篇关于注入服务的休眠多租户实现问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆