Hibernate的多租户和Spring的问题

时间:2014-12-11 13:25:38

标签: spring hibernate spring-mvc multi-tenant

我试图了解如何使用Hibernate,Spring和MySQL实现多租户。对于第一个游乐场示例,我选择了单独的数据库方法:每个租户都有自己的数据库,这个数据库被称为accodingly。此外,另一个数据库用于管理租户。特定于租户的数据库包含一些人员数据。对于第一种方法,我不强制用户进行身份验证。

我发现很难获得关于该主题的全面教程,这就是我目前有点迷失的原因。当我尝试部署Tomcat时,我收到以下消息:

WARN : org.hibernate.engine.jdbc.connections.internal.MultiTenantConnectionProviderInitiator - Unable to instantiate specified class [sdb.persistence.TenantResolverImpl]
java.lang.ClassCastException: sdb.persistence.TenantResolverImpl cannot be cast to org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider
    at org.hibernate.engine.jdbc.connections.internal.MultiTenantConnectionProviderInitiator.initiateService(MultiTenantConnectionProviderInitiator.java:100)
    at org.hibernate.engine.jdbc.connections.internal.MultiTenantConnectionProviderInitiator.initiateService(MultiTenantConnectionProviderInitiator.java:45)
...
ERROR: org.springframework.web.servlet.DispatcherServlet - Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'staffSessionFactory' defined in ServletContext resource [/WEB-INF/spring/appServlet/spring-data.xml]: Invocation of init method failed; nested exception is org.hibernate.service.spi.ServiceException: Unable to instantiate specified multi-tenant connection provider [sdb.persistence.TenantResolverImpl]
...
Caused by: org.hibernate.service.spi.ServiceException: Unable to instantiate specified multi-tenant connection provider [sdb.persistence.TenantResolverImpl]
    at org.hibernate.engine.jdbc.connections.internal.MultiTenantConnectionProviderInitiator.initiateService(MultiTenantConnectionProviderInitiator.java:104)
    at org.hibernate.engine.jdbc.connections.internal.MultiTenantConnectionProviderInitiator.initiateService(MultiTenantConnectionProviderInitiator.java:45)

这是我的Java类的结构:

Java classes

xml配置分为三个文件。 1. servlet-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc" 
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
             xmlns:beans="http://www.springframework.org/schema/beans"
             xmlns:context="http://www.springframework.org/schema/context"
             xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
                                 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                                 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config />
    <beans:import resource="spring-data.xml" />
    <beans:import resource="spring-mvc.xml" />
    <context:component-scan base-package="sdb.controller" />
    <context:component-scan base-package="sdb.data" />
  <context:component-scan base-package="sdb.persistence" />
</beans:beans>
  1. spring-data.xml
  2. <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"   
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:mvc="http://www.springframework.org/schema/mvc" 
           xmlns:jpa="http://www.springframework.org/schema/data/jpa"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                               http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
                               http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
                               http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
    
        <!-- connection to the database which holds tenant-management information -->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver" />
            <property name="url" value="jdbc:mysql://localhost:3306/allTenants" />
            <property name="username" value="root" />
            <property name="password" value="root" />
        </bean>
    
        <bean id="tenantsSessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
            <property name="dataSource" ref="dataSource" />
            <property name="hibernateProperties">
                <props>
                    <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
                    <prop key="hibernate.show_sql">true</prop>
                </props>
            </property>
            <property name="annotatedClasses">
                <list>
                    <value>sdb.domain.Tenant</value>
                </list>
            </property>
        </bean>
    
        <bean id="tenantTransactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
            <property name="sessionFactory" ref="tenantsSessionFactory" />
        </bean>
    
        <!-- not quite sure if this is right -->
        <bean id="staffSessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
            <property name="packagesToScan" value="sdb.domain" />
            <property name="hibernateProperties">
                <props>
                    <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
                    <prop key="hibernate.show_sql">true</prop>
                    <prop key="hibernate.default_schema">public</prop>
                    <prop key="hibernate.multiTenancy">SCHEMA</prop>
                    <prop key="hibernate.tenant_identifier_resolver">sdb.persistence.ConnectionProviderImpl</prop>
                    <prop key="hibernate.multi_tenant_connection_provider">sdb.persistence.TenantResolverImpl</prop>
                </props>
            </property>
        </bean>
    
        <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
            <property name="sessionFactory" ref="staffSessionFactory" />
        </bean>
    </beans>
    
    1. spring-mvc.xml
    2. <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"            
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
             xmlns:mvc="http://www.springframework.org/schema/mvc"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                                 http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
      
          <mvc:default-servlet-handler />
          <mvc:annotation-driven />
          <mvc:resources mapping="/resources/**" location="/resources/" />
      
          <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
              <property name="prefix"><value>/WEB-INF/pages/</value></property>
              <property name="suffix"><value>.jsp</value></property>
          </bean>
      </beans>
      

      现在到Java类。我的DAO每个都有一个会话工厂字段:

      @Repository
      public class TenantDaoImpl implements TenantDao {
          @Autowired
          private SessionFactory tenantsSessionFactory;
      ...
      @Repository
      public class StaffDaoImpl implements StaffDao {
          @Autowired
          private SessionFactory staffSessionFactory;
      

      控制器:

      @Controller
      public class HomeController {
      
          @Autowired
          StaffDao staffDao;
      
          @RequestMapping(value = "/{companyName}/{staffId}", method = RequestMethod.GET)
          public String google(Model model, @PathVariable String companyName, @PathVariable String staffName) {
              List<Staff> staffs = staffDao.getStaff();
              // Yeah, I know that this functionality should actually reside in the Dao or a service layer
              Optional<Staff> foundStaff = staffs.stream().filter(staff -> staff.getSurName().equals(staffName)).findFirst();
              if (foundStaff.isPresent()) {
                  model.addAttribute("foreName", foundStaff.get().getForeName());
                  model.addAttribute("surName", foundStaff.get().getSurName());
              }
      
              model.addAttribute("companyName", companyName);
      
              return "staff";
          }
      }
      

      最后,多租户相关的源代码。 TenantResolverImpl.java

      public class TenantResolverImpl implements CurrentTenantIdentifierResolver {
          @Override
          public String resolveCurrentTenantIdentifier() {
              if (FacesContext.getCurrentInstance() != null) {
                  // I don't know if this will work - the line was copied from somewhere else
                  return FacesContext.getCurrentInstance().getExternalContext().getRequestServerName();
              } 
              return null;
          }
      
          // Not sure what to do with this method...
          @Override
          public boolean validateExistingCurrentSessions() { return true; }
      }
      

      ConnectionProviderImpl.java

      public class ConnectionProviderImpl implements MultiTenantConnectionProvider {
          private Map<String, ComboPooledDataSource> dataSources;
          @Autowired
          TenantDao tenantDao;
      
          @Override
          public Connection getAnyConnection() throws SQLException {
              // should probably return the connection for 'dummy' or so
              return getConnection("google");
          }
      
          @Override
          public Connection getConnection(String tenantIdentifier) throws SQLException {
      
              if (tenantDao.containsTenantWithName(tenantIdentifier)) {
                  if (!dataSources.containsKey(tenantIdentifier)) {
                      ComboPooledDataSource dataSource = new ComboPooledDataSource("Example");
                      try {
                          dataSource.setDriverClass("com.mysql.jdbc.Driver");
                      } catch (PropertyVetoException e) {
                          e.printStackTrace();
                          return null;
                      }
                      dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/" + tenantIdentifier);
                      dataSource.setUser("root");
                      dataSource.setPassword("root");
                      dataSources.put(tenantIdentifier, dataSource);
                  } 
                  return dataSources.get(tenantIdentifier).getConnection();
              }
              return null;
          }
          ...
      }
      

      由于我(但)对这些技术的肤浅理解,我想代码中有很多错误/缺点。随意批评任何值得注意的事情。

2 个答案:

答案 0 :(得分:0)

希望你自己想出来,如果不是这里是我的暗示: 更改属性,您应该完成;)

<prop key="hibernate.tenant_identifier_resolver">sdb.persistence.ConnectionProviderImpl</prop>
<prop key="hibernate.multi_tenant_connection_provider">sdb.persistence.TenantResolverImpl</prop>

答案 1 :(得分:0)

我对这个帖子已经晚了3年,但最近我不得不使用Spring和Hibernate实现一个每租户数据库的多租户Web应用程序。

我已经分享了我构建的应用程序如何与完整的源代码一起工作。看看here.