Quarkus:运行时动态更新属性(多租户数据库切换)
Hibernate 独立数据库模式多租户切换实现方案
针对你开发的多租户应用需求(用户登录后切换到对应公司的独立数据库),可以基于Hibernate的DATABASE多租户策略来实现,以下是具体步骤:
1. 配置Hibernate多租户基础属性
替换application.properties中的单一数据库配置,添加多租户相关配置:
# Hibernate核心配置 spring.jpa.hibernate.ddl-auto=none spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect # 多租户配置 spring.jpa.properties.hibernate.multiTenancy=DATABASE # 指定租户标识符解析器 spring.jpa.properties.hibernate.tenant_identifier_resolver=com.yourproject.config.CustomTenantIdentifierResolver # 指定多租户连接提供者 spring.jpa.properties.hibernate.multi_tenant_connection_provider=com.yourproject.config.CustomMultiTenantConnectionProvider
2. 实现租户标识符解析器
这个类的作用是获取当前登录用户对应的租户(公司)数据库标识,从安全上下文直接读取用户信息即可:
package com.yourproject.config; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; public class CustomTenantIdentifierResolver implements CurrentTenantIdentifierResolver { @Override public String resolveCurrentTenantIdentifier() { // 从登录上下文获取当前用户的租户标识 Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth != null && auth.getPrincipal() instanceof CustomUserDetails) { CustomUserDetails userDetails = (CustomUserDetails) auth.getPrincipal(); // 返回用户对应的公司数据库标识(比如公司ID对应的DB名称) return userDetails.getCompanyDbId(); } // 未登录时返回默认数据库标识 return "default_db"; } @Override public boolean validateExistingCurrentSessions() { return true; } }
3. 实现多租户连接提供者
该类负责根据租户标识动态获取对应的数据库连接,建议用连接池管理租户数据源:
package com.yourproject.config; import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class CustomMultiTenantConnectionProvider implements MultiTenantConnectionProvider { private final Map<String, DataSource> tenantDataSources = new ConcurrentHashMap<>(); private final DataSource defaultDataSource; // 注入默认数据源(用于初始化或默认租户) public CustomMultiTenantConnectionProvider(DataSource defaultDataSource) { this.defaultDataSource = defaultDataSource; } @Override public Connection getConnection(String tenantIdentifier) throws SQLException { // 优先从缓存取租户数据源 if (tenantDataSources.containsKey(tenantIdentifier)) { return tenantDataSources.get(tenantIdentifier).getConnection(); } // 按需创建租户数据源(实际可从配置文件/租户管理表读取DB信息) DataSource tenantDs = createTenantDataSource(tenantIdentifier); tenantDataSources.put(tenantIdentifier, tenantDs); return tenantDs.getConnection(); } private DataSource createTenantDataSource(String tenantId) { HikariConfig config = new HikariConfig(); // 从配置读取租户DB信息(示例:application.properties中配置tenant.db.{tenantId}.url等) config.setJdbcUrl(getProperty("tenant.db." + tenantId + ".url")); config.setUsername(getProperty("tenant.db." + tenantId + ".username")); config.setPassword(getProperty("tenant.db." + tenantId + ".password")); // 连接池配置 config.setMaximumPoolSize(8); config.setConnectionTimeout(30000); return new HikariDataSource(config); } // 封装配置读取方法,实际可使用Spring的Environment private String getProperty(String key) { // 示例实现,实际替换为Spring Environment的getProperty方法 return System.getProperty(key); } @Override public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException { connection.close(); } @Override public boolean supportsAggressiveRelease() { return false; } @Override public <T> T unwrap(Class<T> unwrapType) { if (unwrapType.isInstance(this)) { return (T) this; } throw new IllegalArgumentException("无法转换为类型: " + unwrapType.getName()); } @Override public boolean isUnwrappableAs(Class<?> unwrapType) { return MultiTenantConnectionProvider.class.isAssignableFrom(unwrapType); } }
4. 登录时绑定租户信息
在用户登录成功后,将对应的公司数据库标识存入自定义的UserDetails中,确保后续请求能获取到租户标识:
// 自定义UserDetails类,扩展租户标识字段 public class CustomUserDetails implements UserDetails { private final String username; private final String password; private final String companyDbId; private final Collection<? extends GrantedAuthority> authorities; public CustomUserDetails(String username, String password, String companyDbId) { this.username = username; this.password = password; this.companyDbId = companyDbId; this.authorities = List.of(new SimpleGrantedAuthority("ROLE_USER")); } public String getCompanyDbId() { return companyDbId; } // 实现UserDetails其他必要方法... @Override public String getUsername() { return username; } @Override public String getPassword() { return password; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } // 省略isAccountNonExpired、isAccountNonLocked等方法实现 }
关键注意事项
- 所有租户的数据库结构必须完全一致,否则Hibernate实体映射会出现异常
- 连接池配置要根据租户数量调整,避免连接数耗尽
- 若支持动态新增租户,需实现数据源的动态刷新逻辑(比如监听租户新增事件,更新tenantDataSources缓存)
- 可添加租户数据源的过期清理机制,释放闲置资源
内容的提问来源于stack exchange,提问作者Milton Marino Lopez Montero
相关产品推荐
相关产品推荐

