在现代企业应用中,多租户支持是一个重要的需求。Hibernate提供了对多租户架构的支持,可以使一个应用程序支持多个独立的客户(租户)共享一个数据源。Hibernate的多租户支持主要有两种模式:
- Schema-based 多租户:每个租户有独立的数据库模式(schema)。
- Database-based 多租户:每个租户有独立的数据库实例。
- 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 {