百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 优雅编程 > 正文

多库操作2:终于实现多个数据库操作

sinye56 2024-09-29 21:56 7 浏览 0 评论

更多精彩推荐,上午10点到达

在上篇的文章【多库操作:多个数据库的动态切换(一)】中,我们简单说了说,如何切换数据库,虽然实现了大部分的功能,但是最后也遗留了小问题,后来我和别的小伙伴讨论了下,那个小问题其实不是Bug,而是设计思路的偏差,所以我又重新思考了一下,做了一定的优化,从而实现了多个数据库共存的情况,当然目前这个也能满足事务提交。

借鉴大佬思路:@銀翼の奇術師

1 常见的两种多库操作方式

之前咱们简单说过,不过这里再详细的说一说,多库操作到底是如何操作的。

在平时开发中,我们习惯了面向数据库开发,就是一个项目下来以后,我们就会赶紧的去构思逻辑,然后领导一声令下,五点办公室开会,大家一顿操作,设计了一个堪称完美的数据库表结构,无论是备用字段,还是以后的业务逻辑的扩展,都应有尽有。后来为了满足可能遇到的各种情况,表是能详细就详细,不怕多,就怕改,这也是为何现在ORM很火的原因,当年我也是改了很久的DBHelper,修改一下,整个人都崩溃

后来随着业务的发展,和数据库的瓶颈,就出现了分库的口号,大家开始拆分数据库了,常见的有两种模式:

①、读写分离,多个数据库的表结构是一样的,但是Query和Command不是在一起的,这样能突破瓶颈,使得业务能进一步提高。

②、模块分离,还是多个数据库,只不过每个数据库负责不同的模块,比如密码库,就只有密码表相关的,用户库仅仅是用户相关的,商品库就是商品相关的。

当然如果使用了微服务,是完全没有这个问题的,微服务就是多个api服务,每个api一个数据库,一个模块,一个业务逻辑的,比如这样:

(图片来源网络,侵删)

第一种方案呢,我在我的第二个DDD系列已经说到了,这里应该就不会在Blog.Core里再添加这个功能了,那今天咱们就做一下第②个方案,多个数据库负责不同的模块,可以进行不同的切换,当然如果以后想要新增其他模块功能,只需要自己新建个数据库就行了,然后配置连接字符串,轻松搞定,具体的好处很多,用到的时候肯定都知道了。

下面,我们就详细来说说具体的操作过程吧,随便交代一下,我用的ORM是Sqlsugar5.0.10+版本,其他ORM我没有尝试,自行修改。

2 修改Sqlsugar服务注入方式

在SqlsugarSetup.cs中,修改SqlSugarClient注入方式,要把多个数据库连接同时注入进去:

 /// <summary> /// SqlSugar 启动服务 /// </summary> public static class SqlsugarSetup { public static void AddSqlsugarSetup(this IServiceCollection services) { if (services == ) throw new ArgumentException(nameof(services));
// 默认添加主数据库连接 MainDb.CurrentDbConnId = Appsettings.app(new string[] { "MainDB" });
// 把多个连接对象注入服务,这里必须采用Scope,因为有事务操作 services.AddScoped<ISqlSugarClient>(o => { var listConfig = new List<ConnectionConfig> BaseDBConfig.MutiConnectionString.ForEach(m => { listConfig.Add(new ConnectionConfig { ConfigId = m.ConnId.ObjToString.ToLower, ConnectionString = m.Conn, DbType = (DbType)m.DbType, IsAutoCloseConnection = true, IsShardSameThread = false, MoreSettings = new ConnMoreSettings { IsAutoRemoveDataCache = true } //InitKeyType = InitKeyType.SystemTable } ); }); return new SqlSugarClient(listConfig); }); } }

3 修改工作单元,获取DB服务

在UnitOfWork.cs文件中,把刚刚第二步中注册的服务,通过构造函数注入获取,并且做相应的事务操作,注意,这里必须要保证同一个scope,如果想要实现事务,就必须保证DB的唯一性:

 public class UnitOfWork : IUnitOfWork { private readonly ISqlSugarClient _sqlSugarClient;
public UnitOfWork(ISqlSugarClient sqlSugarClient) { _sqlSugarClient = sqlSugarClient; }
/// <summary> /// 获取DB,保证唯一性 /// </summary> /// <returns></returns> public SqlSugarClient GetDbClient { // 必须要as,后边会用到切换数据库操作 return _sqlSugarClient as SqlSugarClient; }
public void BeginTran { GetDbClient.BeginTran; }
public void CommitTran { try { GetDbClient.CommitTran; // } catch (Exception ex) { GetDbClient.RollbackTran; throw ex; } }
public void RollbackTran { GetDbClient.RollbackTran; }
}

如果修改好了,工作单元,记得也要修改要工作单元接口。

4 动态获取 _db 实例

在上边,我们在工作单元uow(unitOfWork)中,注入了数据库实例,那现在就要获取这个实例了,很简单,直接基类仓储BaseRepository.cs构造函数中,依赖注入我们的IUnitOfWork接口:

 private SqlSugarClient _dbBase; public BaseRepository(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; _dbBase = unitOfWork.GetDbClient; }

获取到这个 _dbBase 以后,其实这个时候已经可以了,我们就可以任意的使用这个db实例,但是我们今天的目的是要动态切换,重头戏来了,既然我们每次请求都需要这个db,那简单,我们就修改它的属性:

 private ISqlSugarClient _db { get { /* 如果要开启多库支持, * 1、在appsettings.json 中开启MutiDBEnabled节点为true,必填 * 2、设置一个主连接的数据库ID,MainDB,必填 */ if (Appsettings.app(new string[] { "MutiDBEnabled" }).ObjToBool) { // 默认会获取当前Model的特性,看看是否配置了连接db的ConnID if (typeof(TEntity).GetTypeInfo.GetCustomAttributes(typeof(SugarTable), true).FirstOrDefault((x => x.GetType == typeof(SugarTable))) is SugarTable sugarTable) { _dbBase.ChangeDatabase(sugarTable.TableDescription.ToLower); } else { // 如果不存在,则表明当前Model是主数据库操作 // 这个配置的地点,看文章上边第二节,注入服务的时候 _dbBase.ChangeDatabase(MainDb.CurrentDbConnId.ToLower); } } return _dbBase; } }

5 实体类和连接字符串的配置

首先呢,我们需要在appsettings.json中,配置多个库的连接字符串,注意,如果想要打开多个,就要把那几个的Enabled全部设置为true:

 "MainDB": "WMBLOG_SQLITE",// 当前主库的连接字符串,不填写的话,默认是下边true的第一个 "MutiDBEnabled": false,// 是否开启多库,默认是false "DBS": [ /* MySql = 0, SqlServer = 1, Sqlite = 2, Oracle = 3, PostgreSQL = 4 */ { "ConnId": "WMBLOG_SQLITE", "DBType": 2, "Enabled": true, "Connection": "WMBlog.db" //只写数据库名就行,我会拼接字符串 }, { "ConnId": "WMBLOG_MSSQL", "DBType": 1, "Enabled": true, "Connection": "Server=.;Database=WMBlogDB;User ID=sa;Password=123;", "ProviderName": "System.Data.SqlClient" }, { "ConnId": "WMBLOG_MYSQL", "DBType": 0, "Enabled": false, "Connection": "Server=localhost; Port=3306;Stmt=; Database=wmblogdb; Uid=root; Pwd=456;" }, { "ConnId": "WMBLOG_ORACLE", "DBType": 3, "Enabled": false, "Connection": "Provider=OraOLEDB.Oracle; Data Source=WMBlogDB; User Id=sss; Password=789;", "OracleConnection_other1": "User ID=sss;Password=789;Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.8.65)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME = orcl)))" } ],

然后我们修改一下Model,配置SugarTable特性,第一个参数是表明,第二个是对应的db连接字符串的ConnID,这里我们用密码表做测试:

/// <summary>/// 密码库表/// </summary>[SugarTable("PasswordLib", "WMBLOG_MSSQL")]public class PasswordLib{ [SugarColumn(Isable = false, IsPrimaryKey = true, IsIdentity = true)] public int PLID { get; set; }
}

到了这里,我们就可以随意的做多库操作了。

我们看看效果吧。

6 实现操作两个数据库效果

首先,开启多库配置:

我们测试两个数据库,一个是Sqlite主库,一个是Sqlserver从库,

从主库中,获取博客信息,从库中获取密码表信息,就是刚刚我们在上边配置的实体。

 /// <summary> /// 测试多库连接 /// </summary> /// <returns></returns> [HttpGet("TestMutiDBAPI")] [AllowAnonymous] public async Task<object> TestMutiDBAPI { // 从主库(Sqlite)中,操作blogs var blogs = await _blogArticleServices.Query(d => d.bID == 1);
// 从从库(Sqlserver)中,获取pwds var pwds = await _passwordLibServices.Query(d => d.PLID > 0;
return new { blogs, pwds }; }

为了做对比效果,我把这两个表,从数据库中删掉,也就是blog在从库中删掉,pwd在主库中删掉:

我们做一个动图,来看看效果:

尽管两个表在对方的数据库不存在,但是我们还是获取到了数据:

那是不是这样就高枕无忧了呢,别慌!咱们是事务不会被破坏吧!来再试试。

7 检验事务操作是否正常

项目中有一个测试接口,大概意思就是先读取一个表数据,然后添加一条数据,中间制造一个异常,最后做回滚操作,看看是否添加的数据被删掉。

 [HttpGet] public async Task<IEnumerable<string>> Get { List<string> returnMsg = new List<string> { }; try { returnMsg.Add($"Begin Transaction");
_unitOfWork.BeginTran; var passwords = await _passwordLibServices.Query(d=>d.IsDeleted==false); returnMsg.Add($"first time : the count of passwords is :{passwords.Count}");

returnMsg.Add($"insert a data into the table PasswordLib now."); var insertPassword = await _passwordLibServices.Add(new PasswordLib { IsDeleted = false, plAccountName = "aaa", plCreateTime = DateTime.Now });

passwords = await _passwordLibServices.Query(d => d.IsDeleted == false); returnMsg.Add($"second time : the count of passwords is :{passwords.Count}"); returnMsg.Add($" ");
//......
var guestbooks = await _guestbookServices.Query; returnMsg.Add($"first time : the count of guestbooks is :{guestbooks.Count}");
int ex = 0; returnMsg.Add($"There's an exception!!"); returnMsg.Add($" "); int throwEx = 1 / ex;
var insertGuestbook = await _guestbookServices.Add(new Guestbook { username = "bbb", blogId = 1, createdate = DateTime.Now, isshow = true });
guestbooks = await _guestbookServices.Query; returnMsg.Add($"first time : the count of guestbooks is :{guestbooks.Count}"); returnMsg.Add($" ");
_unitOfWork.CommitTran; } catch (Exception) { _unitOfWork.RollbackTran; var passwords = await _passwordLibServices.Query; returnMsg.Add($"third time : the count of passwords is :{passwords.Count}");
var guestbooks = await _guestbookServices.Query; returnMsg.Add($"third time : the count of guestbooks is :{guestbooks.Count}"); }
return returnMsg; }

动图太大,这么不放了,肯定是正确的,直接看结果吧:

打完收工!距离微服务又近了一步。

"

每一个努力的,或者正在努力的人,都应该值得被尊重。

——老张的哲学

"

相关推荐

程序员:JDK的安装与配置(完整版)_jdk的安装方法

对于Java程序员来说,jdk是必不陌生的一个词。但怎么安装配置jdk,对新手来说确实头疼的一件事情。我这里以jdk10为例,详细的说明讲解了jdk的安装和配置,如果有不明白的小伙伴可以评论区留言哦下...

Linux中安装jdk并配置环境变量_linux jdk安装教程及环境变量配置

一、通过连接工具登录到Linux(我这里使用的Centos7.6版本)服务器连接工具有很多我就不一一介绍了今天使用比较常用的XShell工具登录成功如下:二、上传jdk安装包到Linux服务器jdk...

麒麟系统安装JAVA JDK教程_麒麟系统配置jdk

检查检查系统是否自带java在麒麟系统桌面空白处,右键“在终端打开”,打开shell对话框输入:java–version查看是否自带java及版本如图所示,系统自带OpenJDK,要先卸载自带JDK...

学习笔记-Linux JDK - 安装&amp;配置

前提条件#检查是否存在JDKrpm-qa|grepjava#删除现存JDKyum-yremovejava*安装OracleJDK不分系统#进入安装文件目...

Linux新手入门系列:Linux下jdk安装配置

本系列文章是把作者刚接触和学习Linux时候的实操记录分享出来,内容主要包括Linux入门的一些理论概念知识、Web程序、mysql数据库的简单安装部署,希望能够帮到一些初学者,少走一些弯路。注意:L...

测试员必备:Linux下安装JDK 1.8你必须知道的那些事

1.简介在Oracle收购Sun后,Java的一系列产品就被整合到Oracle官网中,打开官网乍眼一看也不知道去哪里下载,还得一个一个的摸索尝试,而且网上大多数都是一些Oracle收购Sun前,或者就...

Linux 下安装JDK17_linux 安装jdk1.8 yum

一、安装环境操作系统:JDK版本:17二、安装步骤第一步:下载安装包下载Linux环境下的jdk1.8,请去官网(https://www.oracle.com/java/technologies/do...

在Ubuntu系统中安装JDK 17并配置环境变量教程

在Ubuntu系统上安装JDK17并配置环境变量是Java开发环境搭建的重要步骤。JDK17是Oracle提供的长期支持版本,广泛用于开发Java应用程序。以下是详细的步骤,帮助你在Ubuntu系...

如何在 Linux 上安装 Java_linux安装java的步骤

在桌面上拥抱Java应用程序,然后在所有桌面上运行它们。--SethKenlon(作者)无论你运行的是哪种操作系统,通常都有几种安装应用程序的方法。有时你可能会在应用程序商店中找到一个应用程序...

Windows和Linux环境下的JDK安装教程

JavaDevelopmentKit(简称JDK),是Java开发的核心工具包,提供了Java应用程序的编译、运行和开发所需的各类工具和类库。它包括了JRE(JavaRuntimeEnviro...

linux安装jdk_linux安装jdk软连接

JDK是啥就不用多介绍了哈,外行的人也不会进来看我的博文。依然记得读大学那会,第一次实验课就是在机房安装jdk,编写HelloWorld程序。时光飞逝啊,一下过了十多年了,挣了不少钱,买了跑车,娶了富...

linux安装jdk,全局配置,不同用户不同jdk

jdk1.8安装包链接:https://pan.baidu.com/s/14qBrh6ZpLK04QS8ogCepwg提取码:09zs上传文件解压tar-zxvfjdk-8u152-linux-...

运维大神教你在linux下安装jdk8_linux安装jdk1.7

1.到官网下载适合自己机器的版本。楼主下载的是jdk-8u66-linux-i586.tar.gzhttp://www.oracle.com/technetwork/java/javase/downl...

window和linux安装JDK1.8_linux 安装jdk1.8.tar

Windows安装JDK1.8的步骤:步骤1:下载JDK打开浏览器,找到JDK下载页面https://d.injdk.cn/download/oraclejdk/8在页面中找到并点击“下载...

最全的linux下安装JavaJDK的教程(图文详解)不会安装你来打我?

默认已经有了linux服务器,且有root账号首先检查一下是否已经安装过java的jdk任意位置输入命令:whichjava像我这个已经安装过了,就会提示在哪个位置,你的肯定是找不到。一般我们在...

取消回复欢迎 发表评论: