C#实现SqlServer数据库同步

news2024/12/26 9:20:48

实现效果:

设计思路:
1. 开启数据库及表的cdc,定时查询cdc表数据,封装sql语句(通过执行类型,主键;修改类型的cdc数据只取最后更新的记录),添加到离线数据表;
2. 线程定时查询离线数据表,更新远程库数据;
3. 远程库数据被更改又会产生cdc数据,对此数据进行拦截;

配置文件说明:

{
"AsyncInterval": 30000,
"Drivers": [
{
"RefreshTime": 5000,
"Enable": 1,
"SrcConnect": "Data Source=192.168.8.77;Initial Catalog=master;User ID=sa;Pwd=Nflg1234",
"SrcMap": [ "dbsync2|student,table1,table2,table3", "dbsync3|*" ],
"SrcUpdateCDC": 1,
"DstConnect": [ "Data Source=192.168.8.81;Initial Catalog=master;User ID=sa;Pwd=Nflg1234" ]
}
]
}

{
"AsyncInterval": 25000,
"Drivers": [
{
"RefreshTime": 10000,
"Enable": 1,
"SrcConnect": "Data Source=192.168.8.77;Initial Catalog=master;User ID=sa;Pwd=Nflg1234",
"SrcMap": [ "testsync1|*"],
"SrcUpdateCDC": 1,
"DstConnect": [ "Data Source=192.168.8.81;Initial Catalog=master;User ID=sa;Pwd=Nflg1234" ]
},
{
"RefreshTime": 10000,
"Enable": 1,
"SrcConnect": "Data Source=192.168.8.81;Initial Catalog=master;User ID=sa;Pwd=Nflg1234",
"SrcMap": [ "testsync1|*" ],
"SrcUpdateCDC": 1,
"DstConnect": [ "Data Source=192.168.8.77;Initial Catalog=master;User ID=sa;Pwd=Nflg1234" ]
}
]
}

1. 设置同步间隔时间
2. 根据不同的配置文件,加载不同的模式,多驱动(Drivers 1主1备-单向同步,1主1主-双向同步,2主1备-多库汇总),多机同步(DstConnect),多库同多表同步(SrcMap,dbsync2|*表示监听该数据库下的所有表),设置刷新时间(RefreshTime),是否启用(Enable),是否重置cdc数据(SrcUpdateCDC)

数据表说明:

async_data 离线数据表
id 主键自增 INTEGER
connect_str 连接字符串 NVARCHAR(255)
excute_sql 需要同步的sql语句 NVARCHAR(255)
cdc_time cdc时间 DATETIME
event_time event时间 DATETIME
db_name 数据库名 NVARCHAR(255)
table_name 表名 NVARCHAR(255)
table_pk 表主键 NVARCHAR(255)
excute_type 执行类型(I/U/D) NVARCHAR(255)

sqlserver cdc表(日志表)中如果一条id多次更新,取最新一条数据
sqlite asy_data表(离线数据表),入库时,查dbname + table + pk,无记录则添加,有记录比较cdc记录时间,如果时间更新则更新sql语句

特殊数据处理:
uniqueidentifier类型的数据转为NULL,数据中含有'的替换''

核心代码:

复制代码

using SqlServerAsync.Util.config;
using SqlServerAsync.Util.sqlite;
using SqlServerAsync.Util.sqlite.model;
using System;
using System.Collections.Generic;
using System.Data;
using System.Threading;

namespace SqlServerAsync.Util
{
    public class SqlServerCDC
    {
        public void Listen(Driver driver)
        {
            var update_cdc = driver.SrcUpdateCDC == 1 ? true : false;
            var enable = driver.Enable == 1 ? true : false;
            foreach (var map in driver.SrcMap)
            {
                StartCDC(driver.SrcConnect, driver.DstConnect, driver.RefreshTime, enable, update_cdc, map);
            }
        }

        void StartCDC(string srcconnect, List<string> dstconnect, int refreshTime, bool enable, bool update_cdc, string map)
        {
            try
            {
                var freeSql = new FreeSql.FreeSqlBuilder()
                      .UseConnectionString(FreeSql.DataType.SqlServer, srcconnect)
                      .UseNoneCommandParameter(true)// 不使用参数化
                      .UseAutoSyncStructure(false)// 不同步表结构
                      .Build();

                var arrayMap = map.Split('|');
                var db = arrayMap[0];
                var tbs = arrayMap[1];
                string[] arrayTB = null;

                var dstStr = string.Join("#", dstconnect);

                if (!enable)
                {
                    Program.AddLog($"禁用监听,来源={srcconnect},目标数={dstconnect.Count},目标={dstStr},db={db},Tables={tbs}");
                    return;
                }

                string sql = string.Empty;

                Dictionary<string, Table> dicTable = new Dictionary<string, Table>();

                Program.AddLog($"启用监听,来源={srcconnect},目标数={dstconnect.Count},目标={dstStr},db={db},Tables={tbs}");

                if ("*" == tbs)
                {
                    // 查询db下所有表名
                    sql = $"use {db};select TABLE_NAME from {db}.information_schema.tables where TABLE_SCHEMA='dbo' and TABLE_NAME not in('systranschemas','sysdiagrams')";
                    DataTable dtAll = freeSql.Ado.ExecuteDataTable(sql); 
                    var rowCount = dtAll.Rows.Count; 
                    if (rowCount > 0) arrayTB = new string[rowCount]; 
                    for (int i = 0; i < rowCount; i++)
                    {
                        arrayTB[i] = dtAll.Rows[i]["TABLE_NAME"].ToString();
                    }
                }
                else
                {
                    arrayTB = tbs.Split(',');
                }

                if (null == arrayTB || 0 == arrayTB.Length)
                {
                    Program.AddLog($"数据库{db},查无数据表 ×");
                    return;
                }

                // 开启SQL Server数据库CDC
                sql = $"use {db};if exists(select 1 from {db}.sys.databases where name='{db}' and is_cdc_enabled=0)\n" +
                            "begin\n" +
                                $"exec {db}.sys.sp_cdc_enable_db\n" +
                            "end";
                freeSql.Ado.ExecuteNonQuery(sql);

                // 查询库cdc是否开启成功
                sql = $"use {db};select is_cdc_enabled from {db}.sys.databases where name='{db}'";
                DataTable dtCDC_DB = freeSql.Ado.ExecuteDataTable(sql);
                if (dtCDC_DB.Rows.Count <= 0 || !Convert.ToBoolean(dtCDC_DB.Rows[0]["is_cdc_enabled"]))
                {
                    Program.AddLog($"数据库CDC开启失败({db}) ×");
                    return;
                }
                Program.AddLog($"数据库CDC开启成功({db}) √");

                foreach (var table in arrayTB)
                {
                    if (string.IsNullOrEmpty(table)) continue;

                    if (update_cdc)
                    {
                        // 关闭单张表的CDC功能
                        sql = $"use {db};if exists(select 1 from {db}.sys.tables where name='{table}' AND is_tracked_by_cdc=1)\n" +
                              "begin\n" +
                                 $"exec {db}.sys.sp_cdc_disable_table @source_schema='dbo',@source_name='{table}',@capture_instance='dbo_{table}'" +
                              "end";
                        freeSql.Ado.ExecuteNonQuery(sql);
                    }

                    // 开启单张表的CDC功能
                    sql = $"use {db};if exists(select 1 from {db}.sys.tables where name='{table}' AND is_tracked_by_cdc=0)\n" +
                              "begin\n" +
                                  $"exec {db}.sys.sp_cdc_enable_table\n" +
                                      "@source_schema='dbo',\n" +
                                      $"@source_name='{table}',\n" +
                                      "@capture_instance=NULL,\n" +
                                      "@supports_net_changes=1,\n" +
                                      "@role_name=NULL\n" +
                              "end";
                    freeSql.Ado.ExecuteNonQuery(sql);

                    // 查询表cdc是否开启成功
                    sql = $"use {db};select is_tracked_by_cdc from {db}.sys.tables WHERE name='{table}'";
                    DataTable dtCDC_TB = freeSql.Ado.ExecuteDataTable(sql);
                    if (dtCDC_TB.Rows.Count <= 0 || !Convert.ToBoolean(dtCDC_TB.Rows[0]["is_tracked_by_cdc"]))
                    {
                        Program.AddLog($"数据表CDC开启失败({table}) ×");
                        continue;
                    }
                    Program.AddLog($"数据表CDC开启成功({table}) √");

                    Table tb = new Table() { Name = table };

                    // 获取字段名,是否主键,字段类型
                    sql = $"use {db};SELECT distinct col.name AS 'Name', idx.is_primary_key as 'IsPK',TYPE_NAME(system_type_id) as 'Type'\n" +
                                $"FROM sys.columns col\n" +
                                $"LEFT JOIN sys.index_columns idxcol ON col.object_id=idxcol.object_id AND col.column_id=idxcol.column_id\n" +
                                $"LEFT JOIN sys.indexes idx ON idxcol.object_id=idx.object_id AND idxcol.index_id=idx.index_id\n" +
                                $"WHERE col.object_id=OBJECT_ID('{table}')";

                    List<Field> lstField = freeSql.Ado.Query<Field>(sql);
                    foreach (var field in lstField)
                    {
                        var ispk = Convert.ToBoolean(field.IsPK);
                        if (ispk)
                        {
                            tb.LstPKField.Add(field);// 主键,用于更新删除
                        }
                        else
                        {
                            tb.LstDataField.Add(field);
                        }
                    }

                    dicTable.Add(table, tb);
                }

                Program.AddLog($"监听成功,{db}");

                // 定时轮询
                ThreadPool.QueueUserWorkItem(delegate
                {
                    Dictionary<string, string> dicTBUpdatePK = new Dictionary<string, string>();

                    while (true)
                    {
                        try
                        { 
                            foreach (var item in dicTable)
                            {
                                dicTBUpdatePK.Clear();

                                var table_name = item.Key;
                                var tableEntity = item.Value;
                                // cdc表查询
                                //__$start_lsn :与相应更改的提交事务关联的日志序列号(LSN)
                                //__$end_lsn : (在 SQL Server 2008中,此列始终为 NULL)
                                //__$seqval :对事务内的行更改顺序
                                //__$operation :源表DML操作
                                var cdc_table_name = $"{db}.cdc.dbo_{table_name}_CT";
                                sql = $"use {db};select sys.fn_cdc_map_lsn_to_time(__$start_lsn) as cdctime,* from {cdc_table_name}";// 查询cdc时间
                                var dt = freeSql.Ado.ExecuteDataTable(sql);

                                table_name = $"{db}.dbo." + table_name;

                                for (int i = 0; i < dt.Rows.Count; i++)
                                {
                                    var row = dt.Rows[i];

                                    var lstPKField = tableEntity.LstPKField;
                                    var lstDataField = tableEntity.LstDataField;

                                    var cdctime = Convert.ToDateTime(row["cdctime"]);
                                    var operation = Convert.ToInt32(row["__$operation"]);
                                    var seqval = (byte[])(row["__$seqval"]);// __$start_lsn代表事件时间,并发时,会有相同的情况,改用__$seqval
                                    var str_seqval = BitConverter.ToString(seqval, 0).Replace("-", string.Empty);

                                    if (3 == operation)
                                    {
                                        continue;
                                    }

                                    var sql_cdc_execute = string.Empty;

                                    string table_pk = string.Empty;
                                    foreach (var field1 in lstPKField)
                                    {
                                        table_pk += field1.Name + "='" + row[field1.Name] + "' and ";
                                    }
                                    table_pk = table_pk.Substring(0, table_pk.Length - 5);

                                    string cdc_dic_pk = table_name + ";" + table_pk;

                                    // cdc表中过滤多条表中一条记录多次更新,取最新一条数据(查询过的数据利用字典存储)
                                    string str_seqval1 = string.Empty;
                                    if (4 == operation)
                                    {
                                        if (dicTBUpdatePK.ContainsKey(cdc_dic_pk))
                                        {
                                            str_seqval1 = dicTBUpdatePK[cdc_dic_pk];
                                        }
                                        else
                                        {
                                            // 查询多次更新后的最新值
                                            sql = $"use {db};select top 1 __$seqval from {cdc_table_name} where {table_pk} and __$operation=4 order by __$seqval desc";
                                            var dtlsn = freeSql.Ado.ExecuteDataTable(sql);
                                            var seqval1 = (byte[])(dtlsn.Rows[0]["__$seqval"]);
                                            str_seqval1 = BitConverter.ToString(seqval1, 0).Replace("-", string.Empty);
                                            dicTBUpdatePK.Add(cdc_dic_pk, str_seqval1);
                                        }
                                    }

                                    // 删除cdc表数据
                                    sql = $"use {db};delete from {cdc_table_name} where __$seqval=CONVERT(BINARY(10), '{str_seqval}', 2)";
                                    freeSql.Ado.ExecuteNonQuery(sql);

                                    string excute_type = string.Empty;

                                    switch (operation)
                                    {
                                        case 1:
                                            // 删除
                                            excute_type = BaseEnum.Delete;
                                            sql_cdc_execute = $"delete from {table_name} where {table_pk}";
                                            break;
                                        case 2:
                                            // 插入
                                            excute_type = BaseEnum.Insert;
                                            string insertField = string.Empty;
                                            string insertValue = string.Empty;

                                            foreach (var field1 in lstPKField)
                                            { 
                                                insertField += field1.Name + ",";
                                                insertValue += HandleSpecialData(field1.Type, row[field1.Name]) + ",";
                                            }

                                            foreach (var field2 in lstDataField)
                                            {
                                                insertField += field2.Name + ",";
                                                insertValue += HandleSpecialData(field2.Type, row[field2.Name]) + ",";
                                            } 
                                            
                                            insertField = insertField.Substring(0, insertField.Length - 1);
                                            insertValue = insertValue.Substring(0, insertValue.Length - 1);
                                            sql_cdc_execute = $"insert into {table_name} ({insertField}) values({insertValue})";
                                            break;
                                        case 3:
                                            break;
                                        case 4:
                                            // 修改 
                                            if (str_seqval == str_seqval1)// 最新的数据
                                            {
                                                excute_type = BaseEnum.Update;
                                                string updateData = string.Empty; 

                                                foreach (var field2 in lstDataField)
                                                {
                                                    updateData += field2.Name + "=" + HandleSpecialData(field2.Type, row[field2.Name]) + ",";
                                                }

                                                updateData = updateData.Substring(0, updateData.Length - 1);
                                                sql_cdc_execute = $"update {table_name} set {updateData} where {table_pk}";
                                            }
                                            break;
                                    }

                                    if (!string.IsNullOrEmpty(sql_cdc_execute))
                                    {
                                        foreach (var dst in dstconnect)
                                        {
                                            bool add = true; 

                                            string key1 = srcconnect + "_" + table_name + "_" + table_pk; // A同步B,B更新后,CDC日志返回A,这边做截取
                                            if (Program.DicExecuted.ContainsKey(key1))
                                            {
                                                add = false;
                                                string removedValue;
                                                Program.DicExecuted.TryRemove(key1, out removedValue);
                                            }
                                            else
                                            {
                                                // 修改以最后时间的数据为准
                                                var entity = SqliteHelper.GetUpdateAsyncData(db, table_name, table_pk);

                                                if (null == entity)
                                                {
                                                    var asyncdata = new AsyncData() { ConnectStr = dst, ExcuteSQL = sql_cdc_execute, CDCTime = cdctime, EventTime = DateTime.Now, DBName = db, TableName = table_name, TablePK = table_pk, ExcuteType = excute_type };
                                                    SqliteHelper.InsertAsyncData(asyncdata);
                                                }
                                                else
                                                {
                                                    // 比较时间
                                                    if (DateTime.Compare(entity.CDCTime, cdctime) < 0)
                                                    {
                                                        SqliteHelper.UpdateAsyncData(dst, sql_cdc_execute, entity.Id);
                                                    }
                                                    else
                                                    {
                                                        add = false;
                                                    }
                                                }

                                                if (add)
                                                {
                                                    if (dst.Contains("192.168.8.81"))
                                                    {
                                                        Console.WriteLine("111");
                                                    }
                                                    Program.AddLog($"添加,dst:{dst},sql:{sql_cdc_execute}");
                                                }
                                            }
                                        }
                                    } 
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            Program.AddLog($"Listen Error,ex:{ex.Message}");
                        }

                        Thread.Sleep(refreshTime);
                    }
                });
            }
            catch (Exception ex)
            {
                Program.AddLog($"[Error] 初始化CDC异常,errmsg:{ex.Message}");
            }
        }

        /// <summary>
        /// 特殊数据类型处理 1. uniqueidentifier为空时,设置为NULL;2. 单引号,转成双号
        /// </summary>
        /// <param name="val"></param>
        /// <returns></returns>
        public string HandleSpecialData(string type, object val)
        {
            if (null == val) return string.Empty;

            string ret = val.ToString(); bool special = false;

            if ("uniqueidentifier" == type.ToLower())// 特殊数据类型处理
            {
                if (string.IsNullOrEmpty(ret))
                {
                    special = true;
                    ret = "NULL";
                }
            }

            if (!special)
            {
                if (ret.Contains("'"))
                {
                    ret = ret.Replace("'", "''");// 把单引号转成双引号
                }

                ret = $"'{ret}'";
            }

            return ret;
        } 
    }

    public class Table
    {
        public string Name { get; set; }
        public List<Field> LstPKField { get; set; } = new List<Field>();
        public List<Field> LstDataField { get; set; } = new List<Field>();
    }

    public class Field
    {
        public string Name { get; set; }
        public string IsPK { get; set; } 
        public string Type { get; set; }// GUID,uniqueidentifier为空时,改为NULL
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/845817.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

访问器模式(C++)

定义 表示一个作用于某对象结构中的各元素的操作。使得可以在不改变(稳定)各元素的类的前提下定义(扩展)作用于这些元素的新操作(变化)。 应用场景 在软件构建过程中&#xff0c;由于需求的改变&#xff0c;某些类层次结构中常常需要增加新的行为(方法)&#xff0c;如果直接…

分布式理论之CAP与BASE

分布式理论之CAP与BASE 一、什么是CAP1. 一致性&#xff08;Consistency&#xff09;&#xff1a;2. 可用性&#xff08;Availability&#xff09;&#xff1a;3. 分区容错性&#xff08;Partition tolerance&#xff09;&#xff1a;4. CAP小结&#xff1a; 二、AP&CP如何…

通向架构师的道路之weblogic与apache的整合与调优

一、BEAWeblogic的历史 BEA WebLogic是用于开发、集成、部署和管理大型分布式Web应用、 网络应用和数据库应 用的Java应用服务器。将Java的动态功能和Java Enterprise标准的安全性引入大型网络应用的 开发、集成、部署和管理之中。 BEA WebLogic Server拥有处理关键Web应…

气味传感器

本文将通过图文及视频的形式为各位介绍太阳诱电因应市场需求而开发的气味传感器产品。高浓度端采用QCM型&#xff0c;较低浓度端采用MEMS半导体型。另外还准备有FBAR型转换器。 太阳诱电为实现高灵敏度感应而开发了3款转换器。  高浓度端采用QCM型&#xff0c;较低浓度端采用…

python excel 操作

excel文件内容如下&#xff1a; 一、xlrd 读Excel 操作 1、打开Excel文件读取数据 filexlrd.open_workbook(filename)#文件名以及路径&#xff0c;如果路径或者文件名有中文给前面加一个 r 2、常用函数 &#xff08;1&#xff09;获取一个sheet工作表 table file.sheets(…

LeetCode 热题 100 JavaScript--234. 回文链表

function ListNode(val, next) {this.val val undefined ? 0 : val;this.next next undefined ? null : next; }var isPalindrome function (head) {if (!head || !head.next) {return true; }// 使用快慢指针法找到链表的中间节点let slow head;let fast head;while …

在win10上安装Docker desktop,并在docker中安装mysql

操作步骤: 安装docker 去 官网 下载安装软件;选择windows 2. 依次点击安装; 安装完成后无需登录,直接启动即可; 如果有提示需要更新wsl,执行以下命令更新: wsl --update wsl --status 查看状态 验证一下 适用命令:docker run hello-world 设置国内镜像 docker安装mysql…

Dockerfile部署golang,docker-compose

使用go镜像打包&#xff0c;运行在容器内 redis和mysql用外部的 项目目录结构 w1go项目&#xff1a; Dockerfile # 这种方式是docker项目加上 本地的mysql和redis环境 # go打包的容器 FROM golang:alpine AS builder# 为我们镜像设置一些必要的环境变量 ENV GO111MODULEon …

Apollo配置覆盖引发懒加载配置失效问题

背景 出现问题版本过低&#xff0c;最新版本可能已经做出调整&#xff0c;此文章只是记录 apollo-client.version 1.2.0 因为本地开发&#xff0c;想用测试环境的apollo&#xff0c;所以经常会出现本地覆盖测试环境的配置&#xff1b;一般我会新建一个local.properties来覆盖 …

Debian安装和使用Elasticsearch 8.9

命令行通过 .deb 包安装 Elasticsearch 创建一个新用户 adduser elastic --> rust # 添加sudo权限 # https://phoenixnap.com/kb/how-to-create-sudo-user-on-ubuntu usermod -aG sudo elastic groups elastic下载Elasticsearch v8.9.0 Debian 包 https://www.elastic.co/…

GATK BaseRecalibratorSpark 过程中因Too many open files终止

Error&#xff1a; GATK BaseRecalibratorSpark 过程中因Too many open files终止 执行命令&#xff1a; nohup time ./gatk --java-options "-Xmx16G" BaseRecalibratorSpark -R ../../alignment/hg38/hg38.fa -I ../../alignment/bam/P368T.sorted.markdup.bam …

【C++】C++文件操作-文本文件/二进制文件

0.前言 一、文本文件 1.写文件 代码 #include <iostream> using namespace std; #include <fstream> //头文件包含//************************************** //文本文件 写文件 void test01() {//1.包含文件 fstream//2.创建流对象ofstream ofs;//3.指导打开方式…

芒格之道——查理·芒格股东会讲话1987-2022

你越是认真生活&#xff0c;你的生活就会越美好&#xff01; 这里将读书过程划线的内容摘抄在这里&#xff0c;方便自己回顾。 书分为两部分&#xff0c;我先读了后半部分&#xff0c;而且是从后往前读&#xff0c;到了前半部分&#xff0c;我是从前往后读。书还挺贵&#xff…

使用反汇编工具IDA查看发生异常的汇编代码的上下文去辅助分析C++软件异常

目录 1、概述 2、如何使用IDA打开并查看二进制文件的汇编代码 3、在IDA中找到发生崩溃的那条汇编指令的位置 3.1、如何在IDA中找到发生异常的那条汇编指令 3.2、示例 4、阅读汇编代码上下文需要掌握一定的基础汇编知识 5、最后 VC常用功能开发汇总&#xff08;专栏文章列…

备战秋招011(20230807)

文章目录 前言一、今天学习了什么&#xff1f;二、算法----》单调栈1、介绍2、题目 总结 前言 提示&#xff1a;这里为每天自己的学习内容心情总结&#xff1b; Learn By Doing&#xff0c;Now or Never&#xff0c;Writing is organized thinking. 今天拿到了上周面试的结果…

选读SQL经典实例笔记19_Any和All

1. Any 1.1. 任意一个 1.2. 选修了任意一门课程的学生 1.2.1. 找出选修了至少一门课程的学生 1.3. 比任何火车都快的飞机 1.3.1. 找出比所有火车都快的飞机 2. All 2.1. 全部 2.2. 吃所有蔬菜的人 2.2.1. 没有任何一种蔬菜他们不吃 3. 问题12 3.1. 选修了全部课程的…

scikit-plot 使用笔记

scikit-plot是基于sklearn和Matplotlib的库&#xff0c;主要的功能是对训练好的模型进行可视化。 安装&#xff1a; pip install scikit-plot 功能1&#xff1a;评估指标可视化 scikitplot.metrics.plot_confusion_matrix快速展示模型预测结果和标签计算得到的混淆矩阵。 im…

ForkJoinPool详解

一、归并排序 1、简介 先把一个庞大的数组进行递归分解&#xff0c;把拆分的数组排好序&#xff0c;之后把拆分排好序的数组进行有序的合并&#xff0c;必须住的问题就是&#xff0c;递归拆分的阈值&#xff0c;比如当数组长度拆分到10000时候就不拆了&#xff0c;不能无限制…

TPU编程竞赛系列 | 创客北京2023·算能AI+边缘计算专项赛开始啦!

为助力北京市高精尖产业发展&#xff0c;构建大中小企业相互依存、相互促进的企业发展生态&#xff0c;打造北京市有影响力的双创服务品牌赛事&#xff0c;“创客北京”大赛组委会联合算能举办AI边缘计算方向专项赛。 1.赛题任务 本赛题基于“AI边缘计算”方向&#xff0c;针对…

21、springboot的宽松绑定及属性处理类的构造注入

springboot的宽松绑定及属性处理类的构造注入 ★ 如何使用属性处理类所读取的属性 属性处理类最终变成了Spring容器中的一个Bean组件&#xff0c;因此接下来Spring即可将该Bean组件注入任意其他组件。 这种做法的好处是&#xff1a;可以将大量的配置信息封装一个对象——所以…