多库操作2:终于实现多个数据库操作
sinye56 2024-09-29 21:56 4 浏览 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;
}
动图太大,这么不放了,肯定是正确的,直接看结果吧:
打完收工!距离微服务又近了一步。
"
每一个努力的,或者正在努力的人,都应该值得被尊重。
——老张的哲学
"
相关推荐
- RHEL8和CentOS8怎么重启网络
-
本文主要讲解如何重启RHEL8或者CentOS8网络以及如何解决RHEL8和CentOS8系统的网络管理服务报错,当我们安装好RHEL8或者CentOS8,重启启动网络时,会出现以下报错:...
- Linux 内、外网双网卡路由配置
-
1.路由信息的影响Linux系统中如果有多张网卡的情况下,如果路由信息配置不正确,...
- Linux——centos7修改网卡名
-
修改网卡名这个操作可能平时用不太上,可作为了解。修改网卡默认名从ens33改成eth01.首先修改网卡配置文件名(建议将原配置文件进行备份)...
- CentOS7下修改网卡名称为ethX的操作方法
-
?Linux操作系统的网卡设备的传统命名方式是eth0、eth1、eth2等,而CentOS7提供了不同的命名规则,默认是基于固件、拓扑、位置信息来分配。这样做的优点是命名全自动的、可预知的...
- Linux 网卡名称enss33修改为eth0
-
一、CentOS修改/etc/sysconfig/grub文件(修改前先备份)为GRUB_CMDLINE_LINUX变量增加2个参数(net.ifnames=0biosdevname=0),修改完成...
- CentOS下双网卡绑定,实现带宽飞速
-
方式一1.新建/etc/sysconfig/network-scripts/ifcfg-bond0文件DEVICE=bond0IPADDR=191.3.60.1NETMASK=255.255.2...
- linux 双网卡双网段设置路由转发
-
背景网络情况linux双网卡:网卡A(ens3)和网卡B(...
- Linux-VMware设置网卡保持激活
-
Linux系统只有在激活网卡的状态下才能去连接网络,进行网络通讯。修改配置文件(永久激活网卡)...
- VMware虚拟机三种网络模式
-
01.VMware虚拟机三种网络模式由于linux目前很热门,越来越多的人在学习linux,但是买一台服务放家里来学习,实在是很浪费。那么如何解决这个问题?虚拟机软件是很好的选择,常用的虚拟机软件有v...
- 2023年最新版 linux克隆虚拟机 解决网卡uuid重复问题
-
问题描述1、克隆了虚拟机,两台虚拟机里面的ip以及网卡的uuid都是一样的2、ip好改,但是uuid如何改呢?解决问题1、每台主机应该保证网卡的UUID是唯一的,避免后面网络通信有问题...
- Linux网卡的Vlan配置,你可能不了解的玩法
-
如果服务器上连的交换机端口已经预先设置了TRUNK,并允许特定的VLAN可以通过,那么服务器的网卡在配置时就必须指定所属的VLAN,否则就不通了,这种情形在虚拟化部署时较常见。例如在一个办公环境中,办...
- Centos7 网卡绑定
-
1、切换到指定目录#备份网卡数据cd/etc/sysconfig/network-scriptscpifcfg-enp5s0f0ifcfg-enp5s0f0.bak...
- Linux搭建nginx+keepalived 高可用(主备+双主模式)
-
一:keepalived简介反向代理及负载均衡参考:...
- Linux下Route 路由指令使用详解
-
linuxroute命令用于显示和操作IP路由表。要实现两个不同子网之间的通信,需要一台连接两个网络的路由器,或者同时位于两个网络的网关来实现。在Linux系统中,设置路由通常是为了解决以下问题:该...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle忘记用户名密码 (59)
- oracle11gr2安装教程 (55)
- mybatis调用oracle存储过程 (67)
- oracle spool的用法 (57)
- oracle asm 磁盘管理 (67)
- 前端 设计模式 (64)
- 前端面试vue (56)
- linux格式化 (55)
- linux图形界面 (62)
- linux文件压缩 (75)
- Linux设置权限 (53)
- linux服务器配置 (62)
- mysql安装linux (71)
- linux启动命令 (59)
- 查看linux磁盘 (72)
- linux用户组 (74)
- linux多线程 (70)
- linux设备驱动 (53)
- linux自启动 (59)
- linux网络命令 (55)
- linux传文件 (60)
- linux打包文件 (58)
- linux查看数据库 (61)
- linux获取ip (64)
- linux进程通信 (63)