今天要给大家分享的是quarkus的动态数据源问题,在没有开始之前大家可以想想在spring体系中是怎么实现的都需要写那些代码。

首先大家要明白我们做动态数据源的目的是干嘛的:在一个系统里面来处理不同数据库中的业务需求

有这种业务需求的系统做的是什么项目呢?这个可能就不好总结了,比较普遍的就是多租户大家都知道的独立数据源的模式,还有别的用途这个就比较个性了,只要你们业务需要怎么着都行,比如你们的系统就是要操作不同的数据库等等。

那么动态切换数据源的核心技术原理是什么呢?我个人认为是拦截技术。直白点说就是http一个请求过来肯定是要携带可以用来识别不同数据库连接的信息,那么这个信息就比较灵活比如可以用header信息甚至可以用用户名,你登录系统用户名总是要输入的吧,只要能在数据库检查到用户名就可以发生一系列关于该用户的一切信息,这个也很容易理解。但大多数情况大家还是比较喜欢用header来携带此类信息。

今天说的是quarkus怎么实现动态切换数据源其实无形中也给spring体系做了对比,大家看完就明白了。

视频我做了一期了:
头条:https://www.ixigua.com/i7110837364703887904/
B站:https://www.bilibili.com/video/BV1nA4y1d7rC?share_source=copy_web

当时基本实现了,今天我觉得是真正的解决了所有的技术难题,可以做到比较完美的效果,当然我并没有做的很完善但是技术难点都解决了,大家可以自己发挥做的更适合自己。

首先说下quarkus官网关于多租户的信息描述地方:https://quarkus.io/guides/hibernate-orm#multitenancy
编程模式的动态数据源就提到了我们用到的几个接口,当然没有具体实现。
这里还要提一下quarkus目前可以实现的模式有两种:一种基于OIDC(OpenID Connect)和我们现在实现的传统模式,他们都是基于header的。




这个图是我实现的动态数据源的逻辑,我想大家都能看懂,我就不一一说了。

我贴出来关键代码,源码大家自己去看(https://gitee.com/weir_admin/weirblog-quarkus/tree/master/quarkus-tanent2)

package com.weir.quarku.tanent;import io.quarkus.arc.Unremovable;import io.quarkus.hibernate.orm.runtime.tenant.TenantResolver;import javax.enterprise.context.RequestScoped;import javax.inject.Inject;import java.util.Optional;/** * 识别动态租户信息(一般通过web的request从header传递租户信息) * @author weir * */@RequestScoped@Unremovablepublic class InjectableTenantResolver implements TenantResolver {    @Inject    TenantConnections tenantConnections;    private Optional<String> requestTenant = Optional.empty();    public void setRequestTenant(String requestTenant) {        this.requestTenant = Optional.of(requestTenant);    }    /**     * 默认租户     */    @Override    public String getDefaultTenantId() {        return tenantConnections.allTenants()                .stream()                .findAny()                .orElseThrow(() -> new RuntimeException("No tenants known at all"));    }    /**     * 当前租户     */    @Override    public String resolveTenantId() {        return requestTenant.orElseThrow(() -> new RuntimeException("No tenant specified by current request"));    }}package com.weir.quarku.tanent;import io.agroal.api.AgroalDataSource;import io.agroal.api.configuration.AgroalDataSourceConfiguration;import io.agroal.api.configuration.supplier.AgroalDataSourceConfigurationSupplier;import io.agroal.api.security.NamePrincipal;import io.agroal.api.security.SimplePassword;import io.quarkus.arc.Unremovable;import io.quarkus.hibernate.orm.runtime.customized.QuarkusConnectionProvider;import io.quarkus.hibernate.orm.runtime.tenant.TenantConnectionResolver;import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;import javax.enterprise.context.ApplicationScoped;import javax.inject.Inject;import java.sql.Connection;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;import java.util.*;import static io.agroal.api.configuration.AgroalConnectionPoolConfiguration.ConnectionValidator.defaultValidator;import static java.time.Duration.ofSeconds;/** * 动态产生并切换数据源连接 * @author weir * *///@PersistenceUnitExtension@ApplicationScoped@Unremovablepublic class TenantConnections implements TenantConnectionResolver {        private final Map<String, DBConnectionInfo> dbConnectionInfoMap = new HashMap<>();//    {//            {//                    put("weir",new DBConnectionInfo("localhost", 3306, "root", "336393", "quarkus-demo"));//                    put("weir-blog",new DBConnectionInfo("localhost", 3306, "root", "336393", "weirblog"));//            }//    };        private final Map<String, ConnectionProvider> cache = new HashMap<>();        private static AgroalDataSourceConfiguration createDataSourceConfiguration(DBConnectionInfo dbConnectionInfo) {                System.out.println("------------------createDataSourceConfiguration--------------" + dbConnectionInfo);                return new AgroalDataSourceConfigurationSupplier()                                .dataSourceImplementation(AgroalDataSourceConfiguration.DataSourceImplementation.AGROAL)                                .metricsEnabled(false)                                .connectionPoolConfiguration(cp -> cp.minSize(0).maxSize(5).initialSize(0)                                                .connectionValidator(defaultValidator()).acquisitionTimeout(ofSeconds(5))                                                .leakTimeout(ofSeconds(5)).validationTimeout(ofSeconds(50)).reapTimeout(ofSeconds(500))                                                .connectionFactoryConfiguration(cf -> cf                                                                .jdbcUrl("jdbc:mysql://" + dbConnectionInfo.getHost() + ":" + dbConnectionInfo.getPort()                                                                                + "/" + dbConnectionInfo.getDb())                                                                .connectionProviderClassName("com.mysql.cj.jdbc.Driver")//                                .connectionProviderClassName("org.postgresql.Driver")                                                                .principal(new NamePrincipal(dbConnectionInfo.getUser()))                                                                .credential(new SimplePassword(dbConnectionInfo.getPassword()))))                                .get();        }        public Set<String> allTenants() {                getTenant();                System.out.println("---------------TenantConnections--------allTenants-------" + dbConnectionInfoMap.keySet());                return dbConnectionInfoMap.keySet();        }        @Inject        AgroalDataSource defaultDataSource;        private void getTenant() {                Connection connection = null;                Statement statement = null;                ResultSet resultSet = null;                try {                        connection = defaultDataSource.getConnection();                        statement = connection.createStatement();                        resultSet = statement.executeQuery("select * from SysTenant");                        while (resultSet.next()) {                                dbConnectionInfoMap.put(resultSet.getString("code"),                                                new DBConnectionInfo(resultSet.getString("host"), resultSet.getInt("port"),                                                                resultSet.getString("username"), resultSet.getString("password"),                                                                resultSet.getString("dbName")));//                                System.out.println("--------------------------"+resultSet.getString("name")+"--"+resultSet.getString("username"));                        }                } catch (SQLException e) {                        // TODO Auto-generated catch block                        e.printStackTrace();                }        }        @Override        public ConnectionProvider resolve(String tenant) {                System.out.println("---------------TenantConnections--------resolve-------" + tenant);                if (!dbConnectionInfoMap.containsKey(tenant)) {                        throw new IllegalStateException("Unknown tenantId: " + tenant);                }                if (!cache.containsKey(tenant)) {                        try {                                DBConnectionInfo dbConnectionInfo = dbConnectionInfoMap.get(tenant);                                AgroalDataSource agroalDataSource = AgroalDataSource                                                .from(createDataSourceConfiguration(dbConnectionInfo));                                QuarkusConnectionProvider quarkusConnectionProvider = new QuarkusConnectionProvider(agroalDataSource);                                cache.put(tenant, quarkusConnectionProvider);                                return quarkusConnectionProvider;                        } catch (SQLException ex) {                                throw new IllegalStateException("Failed to create a new data source based on the tenantId: " + tenant,                                                ex);                        }                }                return cache.get(tenant);        }}package com.weir.quarku.tanent;import javax.enterprise.context.ApplicationScoped;import javax.inject.Inject;import javax.ws.rs.container.ContainerRequestContext;import javax.ws.rs.container.ContainerRequestFilter;import javax.ws.rs.ext.Provider;import java.io.IOException;/** * 拦截web中header的租户信息并设置给TenantResolver(InjectableTenantResolver) * @author weir * */@Provider@ApplicationScopedpublic class TenantRequestFilter implements ContainerRequestFilter {    @Inject    InjectableTenantResolver tenantResolver;    @Override    public void filter(ContainerRequestContext containerRequestContext) throws IOException {        String tenantId = containerRequestContext.getHeaderString("X-tenant");        if (tenantId != null) {            tenantResolver.setRequestTenant(tenantId);        }    }}毫不夸张的说这是我见过的最简单的saas多租户动态切换数据源的代码。
看完什么感受,我相信你能看懂,我也相信这个实现要比spring体系简单,反正我信。
温馨提示:
1、在论坛里发表的文章仅代表作者本人的观点,与本网站立场无关。
2、论坛的所有内容都不保证其准确性,有效性,时间性。阅读本站内容因误导等因素而造成的损失本站不承担连带责任。
3、当政府机关依照法定程序要求披露信息时,论坛均得免责。
4、若因线路及非本站所能控制范围的故障导致暂停服务期间造成的一切不便与损失,论坛不负任何责任。
5、注册会员通过任何手段和方法针对论坛进行破坏,我们有权对其行为作出处理。并保留进一步追究其责任的权利。
回复

使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    • 售后服务
    • 关注我们
    • 社区新手

    QQ|手机版|小黑屋|数据通

    Powered by datatong.net X3.4  © 2008-2020 数据通