You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot集成Hibernate 6.x单表多租户(区分符)遇阻求助

Spring Boot + Hibernate 6.x 单表多租户(区分符模式)实现方案

问题描述

Hibernate Core 6.x支持单表多租户(区分符)模式,我想在Spring Boot应用中实现这个功能,但尝试了好几天都没找到可行的具体方案。

我先把pom.xml里的Hibernate Core版本从5.2升级到6.2.Final(通过排除spring-data-starter-jpa依赖里的Hibernate Core),然后添加了TenantIdentifierResolver类,但一直报错。请问能不能在Spring Boot里用Hibernate实现单表多租户?如果可以,正确的实现方式是什么?

用户提供的现有代码:

租户标识符解析器

package com.ayush.csi2.iopa.selfonboarding.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

@Component
@Slf4j
public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver,
        HibernatePropertiesCustomizer {

    @Override
    public String resolveCurrentTenantIdentifier() {
        return resolveTenantByHttpSession();
    }

    @Override
    public boolean validateExistingCurrentSessions() {
        return false;
    }

    @Override
    public void customize(Map<String, Object> hibernateProperties) {
        hibernateProperties.put(AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, this);
    }

    public String resolveTenantByHttpSession() {

        HttpServletRequest req = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
        String currentTenant = req.getHeader("x-tenant-id");

        if(Strings.isBlank(currentTenant)) {
            return "USGM";
        }
        return currentTenant;
    }
}

实体类

package com.ayush.iopa.selfonboarding.entity;

import com.walmart.csi2.iteminsights.iop.selfonboarding.constants.Detector;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.Generated;
import org.hibernate.annotations.GenerationTime;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Type;

import javax.persistence.*;
import java.util.Date;
import java.util.UUID;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Builder
@Table(name = "ApplicationDetail")
public class ApplicationDetail {

    @Id
    @Column(name = "id", nullable = false, unique = true)
    @GeneratedValue(generator = "system-uuid")
    @GenericGenerator(name = "system-uuid", strategy = "uuid2")
    @Type(type= "uuid-char")
    private UUID id;

    @Column(name = "appName", nullable = false, unique = true)
    private String appName;

    @Column(name = "ownerId")
    private String ownerId;

    @Column(name = "description")
    private String description;

    @Column(name = "slackChannels")
    private String slackChannels;

    @Column(name = "emailIds", nullable = false)
    private String emailIds;

    @Column(name = "createdBy", nullable = false)
    private String createdBy;

    @Column(name = "createdDtm", nullable = false)
    private Date createdDtm;

    @Column(name = "modifiedDtm")
    private Date modifiedDtm;

    @Column(name = "modifiedBy")
    private String modifiedBy;

    @Column(name = "authorizedUsers")
    private String authorizedUsers;

    @Generated(value = GenerationTime.NEVER)
    @Column(name = "imperiumAppId")
    private Long imperiumAppId;

    @Column(name = "detector")
    @Enumerated(EnumType.STRING)
    private Detector detector;

    @Column(name = "tenantId")
    private String tenantId;
}

解决方案

Spring Boot完全可以结合Hibernate 6.x实现单表多租户,核心是配置两个组件:租户标识符解析器、多租户连接提供者,同时正确标记实体类的租户区分列。

一、依赖配置

优先使用Spring Boot 3.x(默认适配Hibernate 6.x),避免版本兼容问题。如果是Spring Boot 2.x,需手动排除并指定Hibernate版本:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <!-- 仅Spring Boot 2.x需添加此排除 -->
    <exclusions>
        <exclusion>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>6.2.Final</version>
</dependency>

二、修正租户标识符解析器

补充多租户模式配置,同时处理非Web请求场景(如定时任务)的空指针问题:

package com.ayush.csi2.iopa.selfonboarding.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

@Component
@Slf4j
public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver, HibernatePropertiesCustomizer {

    private static final String DEFAULT_TENANT_ID = "USGM";

    @Override
    public String resolveCurrentTenantIdentifier() {
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes != null) {
                HttpServletRequest req = attributes.getRequest();
                String currentTenant = req.getHeader("x-tenant-id");
                if (!Strings.isBlank(currentTenant)) {
                    return currentTenant;
                }
            }
        } catch (Exception e) {
            log.warn("无法从请求中获取租户ID,使用默认租户", e);
        }
        return DEFAULT_TENANT_ID;
    }

    @Override
    public boolean validateExistingCurrentSessions() {
        return false;
    }

    @Override
    public void customize(Map<String, Object> hibernateProperties) {
        hibernateProperties.put(AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, this);
        // 指定多租户模式为单表区分符
        hibernateProperties.put(AvailableSettings.MULTI_TENANT, "DISCRIMINATOR");
        // 指定租户列名
        hibernateProperties.put(AvailableSettings.MULTI_TENANT_DISCRIMINATOR_COLUMN_NAME, "tenantId");
        // 指定租户列类型
        hibernateProperties.put(AvailableSettings.MULTI_TENANT_DISCRIMINATOR_TYPE, String.class);
    }
}

三、配置多租户连接提供者

单表多租户不需要切换数据库连接,实现一个简单的提供者即可:

package com.ayush.csi2.iopa.selfonboarding.config;

import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
import org.springframework.stereotype.Component;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;

@Component
public class TenantConnectionProvider implements MultiTenantConnectionProvider, HibernatePropertiesCustomizer {

    @Autowired
    private DataSource dataSource;

    @Override
    public Connection getAnyConnection() throws SQLException {
        return dataSource.getConnection();
    }

    @Override
    public void releaseAnyConnection(Connection connection) throws SQLException {
        connection.close();
    }

    @Override
    public Connection getConnection(String tenantIdentifier) throws SQLException {
        // 单表多租户无需切换连接,直接返回默认连接
        return getAnyConnection();
    }

    @Override
    public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
        releaseAnyConnection(connection);
    }

    @Override
    public boolean supportsAggressiveRelease() {
        return false;
    }

    @Override
    public boolean isUnwrappableAs(Class<?> unwrapType) {
        return false;
    }

    @Override
    public <T> T unwrap(Class<T> unwrapType) {
        return null;
    }

    @Override
    public void customize(Map<String, Object> hibernateProperties) {
        hibernateProperties.put(AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, this);
    }
}

四、实体类调整

添加@TenantDiscriminator注解标记租户列,并设置字段非空:

package com.ayush.iopa.selfonboarding.entity;

import com.walmart.csi2.iteminsights.iop.selfonboarding.constants.Detector;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.Generated;
import org.hibernate.annotations.GenerationTime;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TenantDiscriminator;

import javax.persistence.*;
import java.util.Date;
import java.util.UUID;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Builder
@Table(name = "ApplicationDetail")
// 标记租户区分列
@TenantDiscriminator(column = "tenantId")
public class ApplicationDetail {

    @Id
    @Column(name = "id", nullable = false, unique = true)
    @GeneratedValue(generator = "system-uuid")
    @GenericGenerator(name = "system-uuid", strategy = "uuid2")
    @Type(type= "uuid-char")
    private UUID id;

    @Column(name = "appName", nullable = false, unique = true)
    private String appName;

    @Column(name = "ownerId")
    private String ownerId;

    @Column(name = "description")
    private String description;

    @Column(name = "slackChannels")
    private String slackChannels;

    @Column(name = "emailIds", nullable = false)
    private String emailIds;

    @Column(name = "createdBy", nullable = false)
    private String createdBy;

    @Column(name = "createdDtm", nullable = false)
    private Date createdDtm;

    @Column(name = "modifiedDtm")
    private Date modifiedDtm;

    @Column(name = "modifiedBy")
    private String modifiedBy;

    @Column(name = "authorizedUsers")
    private String authorizedUsers;

    @Generated(value = GenerationTime.NEVER)
    @Column(name = "imperiumAppId")
    private Long imperiumAppId;

    @Column(name = "detector")
    @Enumerated(EnumType.STRING)
    private Detector detector;

    @Column(name = "tenantId", nullable = false)
    private String tenantId;
}

验证与常见问题

  1. 功能验证:发送请求时携带x-tenant-id头,Hibernate会自动在CRUD操作中追加tenantId过滤条件,插入数据时若tenantId为空会自动填充解析到的租户ID。
  2. 非Web场景处理:定时任务、异步方法中,可通过ThreadLocal手动设置租户ID,在resolveCurrentTenantIdentifier中优先从ThreadLocal读取。
  3. 依赖冲突排查:确保Spring Boot与Hibernate版本匹配,Spring Boot 3.x对应Hibernate 6.x,Spring Boot 2.x最高兼容Hibernate 5.6.x。

内容的提问来源于stack exchange,提问作者ayush khandelwal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 13:15:05