我们如何使用hibernate作为orm单独的数据库和模式来构建一个spring mvc多租户应用程序

Posted

技术标签:

【中文标题】我们如何使用hibernate作为orm单独的数据库和模式来构建一个spring mvc多租户应用程序【英文标题】:how can we build a spring mvc multitenant application using hibernate as orm separate database and schema 【发布时间】:2019-12-30 08:17:51 【问题描述】:

你从 spring io 转移到堆栈溢出,但是你没有解释多租户完整演示应用程序,它可以被看作是一个问题解决者,事实上没有人对你的答案感到满意,你现在可以分享一个多租户模式的工作演示并分开数据库完整代码在人们可以友好地使用的任何地方

package com.domain.model;


import javax.persistence.*;

@Entity
@Table
public class Employee 

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int employeeId;
    @Column
    private String employeeName;
    public String getEmployeeName() 
        return employeeName;
    
    public void setEmployeeName(String employeeName) 
        this.employeeName = employeeName;
    
    public int getEmployeeId() 
        return employeeId;
    
    public void setEmployeeId(int employeeId) 
        this.employeeId = employeeId;
    


package com.domain.multitenancy;

import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

public class CurrentTenantIdentifierResolverimpl implements CurrentTenantIdentifierResolver 

    @Override
    public String resolveCurrentTenantIdentifier() 
        ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        String tenantId = attr.getRequest().getParameter("tenantId");
        return tenantId;
    

    @Override
    public boolean validateExistingCurrentSessions() 
        return true;
    




package com.domain.multitenancy;

import com.domain.master.MasterService;
import org.hibernate.service.jdbc.connections.spi.AbstractDataSourceBasedMultiTenantConnectionProviderImpl;
import javax.sql.DataSource;

public class MultiTenantConnectionprovideImpl extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl 

    @Override
    protected DataSource selectAnyDataSource() 
        return MasterService.getDataSourceHashMap().get("tenantId1");
    

    @Override
    protected DataSource selectDataSource(String tenantIdentifier) 
        return MasterService.getDataSourceHashMap().get(tenantIdentifier);
    

package com.domain.master;

import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;
import java.util.HashMap;

public class MasterService 
    public static HashMap<String, DataSource> getDataSourceHashMap() 

        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/multiten");
        dataSource.setUsername("root");
        dataSource.setPassword("root");

        DriverManagerDataSource dataSource1 = new DriverManagerDataSource();
        dataSource1.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource1.setUrl("jdbc:mysql://localhost:3306/multiten_1");
        dataSource1.setUsername("root");
        dataSource1.setPassword("root");

        HashMap hashMap = new HashMap();
        hashMap.put("tenantId1", dataSource);
        hashMap.put("tenantId2", dataSource1);
        return hashMap;
    

<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:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
         http://www.springframework.org/schema/tx  http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.domain"/>
    <mvc:annotation-driven/>
    <context:property-placeholder location="classpath:application.properties"/>
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean" >
        <property name="packagesToScan">
            <list>
                <value>com.domain.model</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.hbm2ddl.auto">create</prop>
                <prop key="hibernate.dialect">$hibernate.dialect</prop>
                <prop key="hibernate.show_sql">$hibernate.show_sql:false</prop>
                <prop key="hibernate.format_sql">$hibernate.format_sql:false</prop>
                <prop key="hibernate.multiTenancy">DATABASE</prop>
                <prop key="hibernate.tenant_identifier_resolver">com.domain.multitenancy.CurrentTenantIdentifierResolverimpl</prop>
                <prop key="hibernate.multi_tenant_connection_provider">com.domain.multitenancy.MultiTenantConnectionprovideImpl</prop>
            </props>
        </property>
    </bean>
    <bean id="transactionManager"  class="org.springframework.orm.hibernate4.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>
 #Application.properties file in classpath
jdbc.driverClassName = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/multiten
jdbc.username = root
jdbc.password = root
hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
hibernate.show_sql = true
hibernate.format_sql = false

我已经拍了this example

这些是我得到的错误:

org.springframework.web.util.NestedServletException: 请求 处理失败;嵌套异常是 org.springframework.transaction.CannotCreateTransactionException: 无法为事务打开 Hibernate Session;嵌套异常是 org.hibernate.HibernateException:为 SessionFactory 配置 多租户,但未指定租户标识符 org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:979) org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:869) javax.servlet.http.HttpServlet.service(HttpServlet.java:660) org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843) javax.servlet.http.HttpServlet.service(HttpServlet.java:741) org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)

根本原因 org.springframework.transaction.CannotCreateTransactionException:无法为事务打开休眠会话;嵌套异常是 org.hibernate.HibernateException:为 SessionFactory 配置 多租户,但未指定租户标识符 org.springframework.orm.hibernate4.HibernateTransactionManager.doBegin(HibernateTransactionManager.java:544) org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:373) org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:427) org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:276) org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208) com.sun.proxy.$Proxy22.save(未知来源) com.domain.controller.EmployeeController.saveEmployee(EmployeeController.java:35) sun.reflect.NativeMethodAccessorImpl.invoke0(本机方法) sun.reflect.NativeMethodAccessorImpl.invoke(未知来源) sun.reflect.DelegatingMethodAccessorImpl.invoke(未知来源) java.lang.reflect.Method.invoke(未知来源) org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221) org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136) org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110) org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:832) org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:743) org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:961) org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:895) org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967) org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:869) javax.servlet.http.HttpServlet.service(HttpServlet.java:660) org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843) javax.servlet.http.HttpServlet.service(HttpServlet.java:741) org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)

根本原因 org.hibernate.HibernateException:为多租户配置了 SessionFactory,但未指定租户标识符 org.hibernate.internal.AbstractSessionImpl.(AbstractSessionImpl.java:85) org.hibernate.internal.SessionImpl.(SessionImpl.java:239) org.hibernate.internal.SessionFactoryImpl$SessionBuilderImpl.openSession(SessionFactoryImpl.java:1618) org.hibernate.internal.SessionFactoryImpl.openSession(SessionFactoryImpl.java:978) org.springframework.orm.hibernate4.HibernateTransactionManager.doBegin(HibernateTransactionManager.java:436) org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:373) org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:427) org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:276) org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208) com.sun.proxy.$Proxy22.save(未知来源) com.domain.controller.EmployeeController.saveEmployee(EmployeeController.java:35) sun.reflect.NativeMethodAccessorImpl.invoke0(本机方法) sun.reflect.NativeMethodAccessorImpl.invoke(未知来源) sun.reflect.DelegatingMethodAccessorImpl.invoke(未知来源) java.lang.reflect.Method.invoke(未知来源) org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221) org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136) org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110) org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:832) org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:743) org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:961) org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:895) org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967) org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:869) javax.servlet.http.HttpServlet.service(HttpServlet.java:660) org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843) javax.servlet.http.HttpServlet.service(HttpServlet.java:741) org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)

注意根本原因的完整堆栈跟踪可在服务器日志中找到。

【问题讨论】:

你的问题不是很可读。请提高可读性。 以个人投诉开头的问题不是在 SO 上互动的好方法 :-) 我采用了这个例子javadeveloperzone.com/hibernate/… Michiel Leegwater 并出现上述错误,请提供多租户代码 根本原因 org.hibernate.HibernateException:为多租户配置了 SessionFactory,但未指定租户标识符 org.hibernate.internal.AbstractSessionImpl。这是错误 【参考方案1】:

交易会话;嵌套异常是 org.hibernate.HibernateException:为 SessionFactory 配置 多租户,但未指定租户标识符

上述错误表明未指定租户标识符。请仔细检查您的配置和标识符类实现名称。

您收到此错误的原因是您的租户标识符解析器需要返回一些默认值,以防它无法从您选择的策略中找到合适的租户 ID(在您的情况下,您试图从请求参数中获取租户 ID )。我已经使用了下面的更新代码,它不再显示错误。

public class CurrentTenantIdentifierResolverimpl implements CurrentTenantIdentifierResolver 

    @Override
    public String resolveCurrentTenantIdentifier() 
        ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        String tenantId = attr.getRequest().getParameter("tenantId");
        if (tenantId==null) 
            //return default tenant
            return "tenantId1";
        
        return tenantId;
    

    @Override
    public boolean validateExistingCurrentSessions() 
        return true;
    

【讨论】:

先生不为我工作,我陷入了深深的混乱,请帮助我,请您可以通过发布代码来深入解释 您可以将您的代码发布在 Github 或任何可共享的地方吗? 否则看看github.com/subhashlamba/spring-examples/tree/master/… 感谢伟大的 sol 先生,但正在研究如何在 Spring Security 中使用这个概念【参考方案2】:

Shailendra 先生,我们如何在不设置默认 ID 的情况下为其设置租户 ID,并从 Spring Security 获取用户名并将其传递给租户 ID

【讨论】:

以上是关于我们如何使用hibernate作为orm单独的数据库和模式来构建一个spring mvc多租户应用程序的主要内容,如果未能解决你的问题,请参考以下文章

Hibernate入门核心配置文件和orm元数据配置文件详解

GraphQL 和休眠 (ORM)

Hibernate学习笔记 --- 使用注解定义ORM配置

Hibernate ORM框架——连接池相关

Spring ORM数据访问——Hibernate

Spring ORM数据访问——Hibernate