Hibernate(55)Hibernate的多租户支持如何实现?

7 阅读3分钟

在现代企业应用中,多租户支持是一个重要的需求。Hibernate提供了对多租户架构的支持,可以使一个应用程序支持多个独立的客户(租户)共享一个数据源。Hibernate的多租户支持主要有两种模式:

  1. Schema-based 多租户:每个租户有独立的数据库模式(schema)。
  2. Database-based 多租户:每个租户有独立的数据库实例。
  3. Discriminator-based 多租户:单一数据库和单一表结构,通过一个租户标识符来区分不同租户的数据。

下面将详细介绍如何实现这三种多租户支持,结合代码示例说明。

1. Schema-based 多租户

在这种模式中,每个租户有独立的数据库模式(schema),但共享同一个数据库实例。

配置Hibernate

hibernate.cfg.xml中配置Hibernate:

<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <!-- 数据库连接配置 -->
        <property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/your_database</property>
        <property name="hibernate.connection.username">your_username</property>
        <property name="hibernate.connection.password">your_password</property>

        <!-- Hibernate 属性配置 -->
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.format_sql">true</property>

        <!-- 多租户配置 -->
        <property name="hibernate.multiTenancy">SCHEMA</property>
        <property name="hibernate.tenant_identifier_resolver">com.example.TenantIdentifierResolverImpl</property>
        <property name="hibernate.multi_tenant_connection_provider">com.example.MultiTenantConnectionProviderImpl</property>

        <!-- 映射类 -->
        <mapping class="com.example.domain.Product"/>
    </session-factory>
</hibernate-configuration>

实现MultiTenantConnectionProvider接口

MultiTenantConnectionProvider接口用于提供基于租户的连接。

import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.hibernate.service.spi.Stoppable;

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

public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider, Stoppable {
    private final DataSource dataSource;

    public MultiTenantConnectionProviderImpl(DataSource dataSource) {
        this.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 {
        final Connection connection = getAnyConnection();
        try {
            connection.createStatement().execute("USE " + tenantIdentifier);
        } catch (SQLException e) {
            throw new HibernateException("Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]", e);
        }
        return connection;
    }

    @Override
    public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
        connection.createStatement().execute("USE your_default_schema");
        connection.close();
    }

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

    // Implement other required methods...
}

实现CurrentTenantIdentifierResolver接口

CurrentTenantIdentifierResolver接口用于解析当前租户标识符。

import org.hibernate.context.spi.CurrentTenantIdentifierResolver;

public class TenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver {

    @Override
    public String resolveCurrentTenantIdentifier() {
        // 从上下文或者请求中获取当前租户标识符
        return TenantContext.getCurrentTenant();
    }

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

实体类定义

以下是一个简单的实体类Product的定义:

import javax.persistence.*;

@Entity
@Table(name = "product")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

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

    @Column(name = "price")
    private Double price;

    // Getters and Setters
}

使用多租户支持

在应用程序中使用多租户支持:

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

public class MultiTenantExample {
    private static final SessionFactory sessionFactory;

    static {
        try {
            sessionFactory = new Configuration().configure("hibernate.cfg.xml").buildSessionFactory();
        } catch (Throwable ex) {
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static void main(String[] args) {
        // 设置当前租户
        TenantContext.setCurrentTenant("tenant1");

        Session session = sessionFactory.openSession();
        Transaction transaction = session.beginTransaction();
        try {
            Product product = new Product();
            product.setName("Product A");
            product.setPrice(100.0);
            session.save(product);
            transaction.commit();
        } catch (Exception e) {
            if (transaction != null) {
                transaction.rollback();
            }
            e.printStackTrace();
        } finally {
            session.close();
        }
    }
}

2. Database-based 多租户

在这种模式中,每个租户有独立的数据库实例。

配置Hibernate

hibernate.cfg.xml中配置Hibernate:

<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <!-- 数据库连接配置 -->
        <property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/your_default_database</property>
        <property name="hibernate.connection.username">your_username</property>
        <property name="hibernate.connection.password">your_password</property>

        <!-- Hibernate 属性配置 -->
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.format_sql">true</property>

        <!-- 多租户配置 -->
        <property name="hibernate.multiTenancy">DATABASE</property>
        <property name="hibernate.tenant_identifier_resolver">com.example.TenantIdentifierResolverImpl</property>
        <property name="hibernate.multi_tenant_connection_provider">com.example.MultiTenantConnectionProviderImpl</property>

        <!-- 映射类 -->
        <mapping class="com.example.domain.Product"/>
    </session-factory>
</hibernate-configuration>

实现MultiTenantConnectionProvider接口

实现MultiTenantConnectionProvider接口,用于提供基于租户的连接。

import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.hibernate.service.spi.Stoppable;

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

public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider, Stoppable {
    private final Map<String, DataSource> dataSources = new HashMap<>();

    public MultiTenantConnectionProviderImpl() {
        // 初始化各个租户的数据源
        dataSources.put("tenant1", createDataSource("jdbc:mysql://localhost:3306/tenant1", "username", "password"));
        dataSources.put("tenant2", createDataSource("jdbc:mysql://localhost:3306/tenant2", "username", "password"));
        // ... add other tenants
    }

    private DataSource createDataSource(String url, String username, String password) {
        DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
        dataSourceBuilder.url(url);
        dataSourceBuilder.username(username);
        dataSourceBuilder.password(password);
        return dataSourceBuilder.build();
    }

    @Override
    public Connection getAnyConnection() throws SQLException {
        return dataSources.get("tenant1").getConnection(); // 默认租户连接
    }

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

    @Override
    public Connection getConnection(String tenantIdentifier) throws SQLException {
        DataSource dataSource = dataSources.get(tenantIdentifier);
        if (dataSource != null) {
            return dataSource.getConnection();
        } else {
            throw new HibernateException("Could not find data source for tenant " + tenantIdentifier);
        }
    }

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

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

    // Implement other required methods...
}

实现CurrentTenantIdentifierResolver接口

实现CurrentTenantIdentifierResolver接口,用于解析当前租户标识符。

import org.hibernate.context.spi.CurrentTenantIdentifierResolver;

public class TenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver {