Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 381|回复: 0

企业级工作流解决方案(十三)--集成Abp和ng-alain--数据库读写分离

[复制链接]
  • TA的每日心情
    奋斗
    2024-11-24 15:47
  • 签到天数: 804 天

    [LV.10]以坛为家III

    2053

    主题

    2111

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    726782
    发表于 2021-6-12 14:57:38 | 显示全部楼层 |阅读模式

      说到程序里面数据库管理,无非就是两件事情,一是数据库操作,对于数据库的操作,各种程序语言都有封装,也就是所谓的ORM框架,.net 方向一般用得比较多和就是.net framework和dapper,abp里还集成了NHibernate,另外就是连接字符串的管理,简单的应用直接用一个数据库连接字符串就可以了,但是对于大型的应用,比如有多租户概念的系统,比如有一些分库分表需求的设计系统,那么连接字符串的管理将是非常复杂和核心的内容。

      对于读写分离,大家应该比较熟悉,数据库层面,大型的关系型数据库都支持,这里的读写分离是指代码层面,针对DBA已经做好的数据库读写分离来管理数据库连接字符串。

      Abp基本框架提供了最基础的数据库连接字符串管理,zero项目实现了多租户的数据库连接管理,即把每个租户的连接字符串存储在租户里面,对于每一个Uow操作,都会找租户的连接字符串,如果找到,就使用,没有找到,向上层找默认的连接字符串。代码如下:

    /// <summary>
        /// Implements <see cref="IDbPerTenantConnectionStringResolver"/> to dynamically resolve
        /// connection string for a multi tenant application.
        /// </summary>
        public class DbPerTenantConnectionStringResolver : DefaultConnectionStringResolver, IDbPerTenantConnectionStringResolver
        {
            /// <summary>
            /// Reference to the session.
            /// </summary>
            public IAbpSession AbpSession { get; set; }
    
            private readonly ICurrentUnitOfWorkProvider _currentUnitOfWorkProvider;
            private readonly ITenantCache _tenantCache;
    
            /// <summary>
            /// Initializes a new instance of the <see cref="DbPerTenantConnectionStringResolver"/> class.
            /// </summary>
            public DbPerTenantConnectionStringResolver(
                IAbpStartupConfiguration configuration,
                ICurrentUnitOfWorkProvider currentUnitOfWorkProvider,
                ITenantCache tenantCache)
                : base(configuration)
            {
                _currentUnitOfWorkProvider = currentUnitOfWorkProvider;
                _tenantCache = tenantCache;
    
                AbpSession = NullAbpSession.Instance;
            }
    
            public override string GetNameOrConnectionString(ConnectionStringResolveArgs args)
            {
                if (args.MultiTenancySide == MultiTenancySides.Host)
                {
                    return GetNameOrConnectionString(new DbPerTenantConnectionStringResolveArgs(null, args));
                }
    
                return GetNameOrConnectionString(new DbPerTenantConnectionStringResolveArgs(GetCurrentTenantId(), args));
            }
    
            public virtual string GetNameOrConnectionString(DbPerTenantConnectionStringResolveArgs args)
            {
                if (args.TenantId == null)
                {
                    //Requested for host
                    return base.GetNameOrConnectionString(args);
                }
    
                var tenantCacheItem = _tenantCache.Get(args.TenantId.Value);
                if (tenantCacheItem.ConnectionString.IsNullOrEmpty())
                {
                    //Tenant has not dedicated database
                    return base.GetNameOrConnectionString(args);
                }
    
                return tenantCacheItem.ConnectionString;
            }
    
            protected virtual int? GetCurrentTenantId()
            {
                return _currentUnitOfWorkProvider.Current != null
                    ? _currentUnitOfWorkProvider.Current.GetTenantId()
                    : AbpSession.TenantId;
            }
        }

      那么我们要改造的地方其他就可以参照这个来管理连接字符串

      还有一个问题,就是我们怎么让框架知道我们使用的是读库还是写库呢?

      Abp里面,公开给用户控制Uow的,就是UnitOfWorkAttribute装饰器,增加一个读库还是写库的标识IsReadDb,在UnitOfWorkOptions类里面也要加对应的属性,那么我们在构造UnitOfWorkOptions类的时候,可以把属性装饰器里面的IsReadDb属性赋值给UnitOfWorkOptions,再获取DbContext方法的时候,把此参数传入Uow连接字符串管理,在连接字符串管理里面,判断此参数的值,确定数据库字符串选择。

      主要代码:

    /// <summary>
        /// Unit of work options.
        /// </summary>
        public class UnitOfWorkOptions
        {
            // ......
    
            /// <summary>
            /// 自定义:设置是否是读库
            /// </summary>
            public bool IsReadDb { get; set; }
    }
    
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Interface)]
        public class UnitOfWorkAttribute : Attribute
        {
            // ......
    
            /// <summary>
            /// 自定义:设置是否是读库
            /// </summary>
            public bool IsReadDb { get; set; }
            public UnitOfWorkOptions CreateOptions()
            {
                return new UnitOfWorkOptions
                {
                    IsTransactional = IsTransactional,
                    IsolationLevel = IsolationLevel,
                    Timeout = Timeout,
                    Scope = Scope,
                    IsReadDb = IsReadDb
                };
            }
        }

      在获取DbContext方法的时候,传递数据库读写参数

    public static class UnitOfWorkExtensions
        {
            public static TDbContext GetDbContext<TDbContext>(this IActiveUnitOfWork unitOfWork, MultiTenancySides? multiTenancySide = null, string name = null)
                where TDbContext : DbContext
            {
                if (unitOfWork == null)
                {
                    throw new ArgumentNullException("unitOfWork");
                }
    
                if (!(unitOfWork is EfCoreUnitOfWork))
                {
                    throw new ArgumentException("unitOfWork is not type of " + typeof(EfCoreUnitOfWork).FullName, "unitOfWork");
                }
    
                return (unitOfWork as EfCoreUnitOfWork).GetOrCreateDbContext<TDbContext>(multiTenancySide, name, unitOfWork.Options.IsReadDb);
            }
    }
    
    public virtual TDbContext GetOrCreateDbContext<TDbContext>(MultiTenancySides? multiTenancySide = null, string name = null,bool isReadDb = false)
                where TDbContext : DbContext
            {
                var concreteDbContextType = _dbContextTypeMatcher.GetConcreteType(typeof(TDbContext));
    
                var connectionStringResolveArgs = new ConnectionStringResolveArgs(multiTenancySide);
                connectionStringResolveArgs["DbContextType"] = typeof(TDbContext);
                connectionStringResolveArgs["DbContextConcreteType"] = concreteDbContextType;
                connectionStringResolveArgs["IsReadDb"] = isReadDb;
                var connectionString = ResolveConnectionString(connectionStringResolveArgs);
    }

      最终,参照Abp连接字符串的管理,代码如下:

    public class DbPerTenantConnectionStringResolver : DefaultConnectionStringResolver, IDbPerTenantConnectionStringResolver
        {
            /// <summary>
            /// Reference to the session.
            /// </summary>
            public IAbpSession AbpSession { get; set; }
    
            private readonly ICurrentUnitOfWorkProvider _currentUnitOfWorkProvider;
    
            /// <summary>
            /// Initializes a new instance of the <see cref="DbPerTenantConnectionStringResolver"/> class.
            /// </summary>
            public DbPerTenantConnectionStringResolver(
                IAbpStartupConfiguration configuration,
                ICurrentUnitOfWorkProvider currentUnitOfWorkProvider)
                : base(
                      configuration)
            {
                _currentUnitOfWorkProvider = currentUnitOfWorkProvider;
                AbpSession = NullAbpSession.Instance;
            }
    
            public override string GetNameOrConnectionString(ConnectionStringResolveArgs args)
            {
                if (args.MultiTenancySide == MultiTenancySides.Host)
                {
                    return GetNameOrConnectionString(new DbPerTenantConnectionStringResolveArgs(null, args));
                }
    
                return GetNameOrConnectionString(new DbPerTenantConnectionStringResolveArgs(GetCurrentTenantId(), args));
            }
    
            public virtual string GetNameOrConnectionString(DbPerTenantConnectionStringResolveArgs args)
            {
                if (args.TenantId == null)
                {
                    //Requested for host
                    return base.GetNameOrConnectionString(args);
                }
    
                var tenantCacheItem = Rpc.Call<Sys_TenantQueryDto>("CommonService.CommonServiceServiceAppService.GetTenantInfo", args.TenantId);
                if(tenantCacheItem == null)
                {
                    return base.GetNameOrConnectionString(args);
                }
    
                if(Convert.ToBoolean(args["IsReadDb"]))
                {
                    if(!string.IsNullOrEmpty(tenantCacheItem.ReadConnectionString))
                    {
                        return tenantCacheItem.ReadConnectionString;
                    }
                    else
                    {
                        return base.GetNameOrConnectionString(args);
                    }
                }
                else
                {
                    if (!string.IsNullOrEmpty(tenantCacheItem.ConnectionString))
                    {
                        return tenantCacheItem.ConnectionString;
                    }
                    else
                    {
                        return base.GetNameOrConnectionString(args);
                    }
                }
            }
    
            protected virtual int? GetCurrentTenantId()
            {
                return _currentUnitOfWorkProvider.Current != null
                    ? _currentUnitOfWorkProvider.Current.GetTenantId()
                    : AbpSession.TenantId;
            }
        }

      使用的时候,在类或者方法上,增加Uow属性装饰器上定义参数即可

      补充说明:可以参照这种方式,自定义的扩展,比如每一个DbContext自定义连接字符串,我们可以在自己的租户管理表中添加属性,自定义数据库连接字符串选择逻辑。

     

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

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

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2025-1-23 05:59 , Processed in 0.067772 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表