DDD领域驱动设计落地实践(十分钟看完,半小时落地) - 只会一点java - 博客园

mikel阅读(854)

来源: DDD领域驱动设计落地实践(十分钟看完,半小时落地) – 只会一点java – 博客园

一、引子

不知今年吹了什么风,忽然DDD领域驱动设计进入大家视野。该思想源于2003年 Eric Evans编写的“Domain-Driven Design领域驱动设计”简称DDD,Evans DDD是一套综合软件系统分析和设计的面向对象建模方法。刚好公司领导强力推荐这个,抱着学习的心态,耗时5个月,体验了一把:“DDD从入门到弃坑”。

二、思想

学习网站:https://www.jdon.com/ddd.html

书:领域驱动设计

2.1 服务器后端发展三个阶段

 

服务器后端发展三个阶段:

  1. 面向过程脚本:初始简单,业务复杂后,维护难度指数上升。–>基本不为主流使用
  2. 面向数据库表:初始难度中,业务复杂后,维护难度延迟后再指数上升。—>目前市面上主流
  3. 面向业务模型:DDD+SOA微服务的事件驱动的CQRS读写分离架构:应付复杂业务逻辑,以聚合模型替代数据表模型,以并发的事件驱动替代串联的消息驱动。真正实现以业务实体为核心的灵活拓展。初始难度高,业务复杂后,维护难度线性上升(已很不错)

2.2 DDD最大特点

DDD革命性在于:领域模型准确反映了业务语言,而传统微服务数据对象除了简单setter/getter方法外,没有任何业务方法,即失血模型,那么DDD领域模型就是充血模型(业务方法定义在实体对象中)

 

三、落地

3.1 领域模型设计

以渠道中心(一个微服务)作为例子来做领域模型设计,核心就是设计2个图,一个是战略设计图(宏观)  ,一个是战术设计图(细节)。

1.领域战略设计图

战略设计图是从一个限界上下文的角度出发去分析业务场景。主要是宏观上的核心域、子域、实体关系图。demo如下图:

2.领域战术设计图

战术设计图是从一个限界上下文的角度出发去分析业务场景。细化到核心业务字段、领域实体、值对象、领域服务、领域事件等等。基本上这个图画完,代码已经知道怎么写了。demo如下图:

3.2 技术实现

整体项目框架分层图如下所示:

如上图,4层典型DDD分层结构,

1.展现层:controller层。无业务逻辑

2.应用服务层:此层可以包含查询逻辑,但核心业务逻辑必须下沉到领域层。

3.领域服务层:业务在这里组装。仓储(资源库)接口在此层定义。

4.基础设施层:仓储(资源库)实现层+PO持久化层。

注:

1.简单查询不涉及业务,是可以直接从应用层直接穿透到PO查询,不需要经过domain层。如下图所示,DDD本身是不限制非业务类操作跨层调用的。

 

2.DTO是不能存在于domain层的,DDD设计不认为DTO是业务对象,entity才是。或者传值简单数据类型也是可以的。

3.2.1 服务调用问题

1.域内调用

领域内调用,随便调用,丝般顺滑。至于实现,可以由一个核心域的仓储实现层(第四层)去实现多个Repository接口。(比如这里A是核心域的实体名,B是支撑域、通用域等)

 2.跨域调用

 

 

跨域分为

  • 1.同上下文跨域:ACL层->Adapter适配器层→调用其它域的repository。—>不得已才使用,不推荐使用。
  • 推荐:1.使用领域事件 eventbus来做解耦(nest-eventbus使用

               2.考虑是否有可能合并为一个领域.

  • 2.跨上下文(肯定跨域):ACL层->Adapter适配器层->feign调用

3.2.2 包结构

包结构如下:

 展开包结构如下:

展现层:Controller,仅做接口的入口定义和编排转发,不做任何的业务处理;

应用服务层:application,负责接口参数DTO的简单校验,以及DTO和实体值对象的数据转换,对于简单的业务,也可以在应用层加载实体直接执行实体行为方法;

领域层:

  • 模型:根据领域模型分析领域内各实体、聚合、聚合根、值对象等,这些对象在*.domain.model定义,实体内的行为方法只负责维护实体自身的生命周期和状态;
  • 行为:领域内各实体、聚合、聚合根等,会有相应的行为,在*.domain.model包下定义行为方法;
  • 领域服务:领域提供的接口服务,需要定义在*.domain.service包下,业务相关的前置业务判断、多个实体或值对象的行为逻辑处理等,都在领域服务中实现,需要注意的是并不是每个实体都有一个对应的领域服务,但是依赖多个实体的行为方法,最好根据这个业务模块是建立一个领域服务;
  • 仓储:领域服务或上层应用服务需要使用到的基础设施层,包括DB、Feign调用等,定义在*.domain.repository下,在*.infrastructure.repository下实现;

适配层:在acl包下的feign定义依赖外部的接口,并在acl的adapter包编写转换,由仓储层操作实体时调用;

持久层:与常用DAO定义一致,由仓储层操作实体时调用。

 3.2.3 技术架构

目前业内没有标杆,github开源地址:https://github.com/jovezhao/nest  。作者不是本人哈,这个项目可以练手DDD。

 

四、总结

DDD可以尝试,但不建议主流业务硬上。建议浅尝即止。(据我所知,业内连阿里巴巴都不敢上。)

开源框架 - 新 代码生成器 WebFirst / .NET Core - 果糖大数据科技 - 博客园

mikel阅读(1094)

来源: 开源框架 – 新 代码生成器 WebFirst / .NET Core – 果糖大数据科技 – 博客园

框架描述

WebFirst  是一新代的 代码生成器,用法简单,功能强大,支持多种数据库 ,具体功能如下:

一、 建库、CodeFirst方式在线建表,没用到CodeFirst的用户可以用工具轻松体验,支持公共字段

二、导出EXCEL文档,把每个表的数据导出来

三、模版管理 可以自个添加修改模版,使用的是Razor模版引擎对C#程序员更加友好

四、方案管理,可以创建自已的生成方案,修改方案

五、支持扩展模版属性,支持生成更加丰富的前端代码

六、支持生成解决方案

七、支持生成附加文件,支持文件后缀

八、支持视图

九、支持自定义数据类型

十、支持多种数据库 MYSQL PGSQL SQLITE SQLSERVE  ORCLE  达梦

代码生成器的使用场景

一、提高工作效率

我们都知道一个CRUD其实如果有代码生成器的话只需要几分钟就可以把完整的功能做完,如果用手去写那么可能就要花掉一整天的时间,多出来的这些时间学学技术多好

二、大量重复有共性的功能

使用代码生成器最重要的就是减少大量重复的工作,比如我们有 数据库model 有表单model 有Grid绑定的 model  光写这些MODEL就很头痛,要写各种特性

 

 WebFirst管理和配置功能介绍

 方案管理

  方案是生成的一些配置比如用哪个模版、生成路径、文件名、命名空间等等进行分类和存储 方便下次使用

  内置自带的方案可供学习

 

 

  模版管理

模版是方案的一个属性,创建方案必须选择模版,在WebFirst中我们使用的是Razor模版引擎,功能非常强大,带有智能提示

 

WebFirst专门提供了一个模版编写的小程序让你有智能提示的 点点点,把模版轻松写出来

特色功能

1、在线版CodeFirst

比手动建实体生成表速度更快,只需界面操作选择C#类型(类型可以自定义配置)就能把100个字段的表在几分钟建出来,并且生成表,这种效率手写类根本做不到

2. 快速生成文档

生成的excel文档,比一般的软件生成出来的可能更友好点吧,虽然功能比较一般不过,作为代码生成器+快速开发还是不错的功能

3、支持数据源建类

有的时候可能需要,通过算法或者写个SQL把实体建出来

 

 4、支持生成的时候追附加文件

比如我们要生成项目文件,仓储 ,或者JSON文件等

 

 

5、自定义实体属性配置

这个功能就比较强大了,比如我要给字段设置数据源,控件类型,这样就需要自定义扩展属性进行配置,并且在模版中可以使用这些自定义的特性,每个字段支持多个自定义属性

 

6、自带方案用例

可以先用自带的用方案生成出来看看效果如何,然后改改路径、改改模版就能用到自个的项目中了

 

7、支持备份和还原

将EXE目录下的文件 database文件夹里面的数据库复制进行备份

 

粘贴到生成的项目中

 

我们把EXE目录里面database中的库替换成备份库,这样就实现了还原功能

 

 

下载地址

GITHUB  https://github.com/donet5/WebFirst   实实在在的干货 幸苦开发2个月,感兴趣的可以GITHUB 点个赞

另外我们的另一个开源作品【SqlSugar】 也更新了联表查询语法

1
2
3
4
5
6
var query5 = db.Queryable<Order>()
            .LeftJoin<Custom>((o, cus) => o.CustomId == cus.Id)
            .Where(o => o.Id == 1) 
            .Select((o, cus) => new  { o,cus })
            .ToList(); //现在的语法可以说是相当美观,并且你用where的时候 可以只写一个 o而不是所有都要加上 (o,cus)
//其它框架基本上要  <br>//db.Queryable<Order,Custom>().LeftJoin<Custom><br>//比较下来SqlSugar是不是会更简洁一些呢  

要另外SqlSugar将于10月底支持自动化分表功能5年多的沉淀和设计 最简、最好用、最实用,SqlSugar在2021年和2020可以说不是一框架,

以前用过认为不好用或者没用的也可以关注一下,因为2021开发时间可以等于以前几年开发时间,坚持创新 完美

GITHUB: https://github.com/donet5/SqlSugar   也可以关注一下

MongoDB查询转对象时出错 Element '_id' does not match any field or property of class - 狼窝窝 - 博客园

mikel阅读(735)

来源: MongoDB查询转对象时出错 Element ‘_id’ does not match any field or property of class – 狼窝窝 – 博客园

参考:https://www.cnblogs.com/94cool/p/6230202.html

解决方法:

1、在实体类加:[BsonIgnoreExtraElements]

2、或者定义public ObjectId _id { get; set; }

例子:

[BsonIgnoreExtraElements]

public class BaseData
{
//public ObjectId _id { get; set; }

public string cNo { get; set; }

public string customer { get; set; }

public long batchNo { get; set; }

public DateTime mDate { get; set; }

public string mUser { get; set; }
}

 

顺便保存下数据库帮助类

NuGet需要引用:MongoDB.Driver;

数据库管理工具可以用Robo 3T或者Studio 3T

复制代码
public class MongoDBHelper
    {
        #region 构造函数(初始化集合,子类需重写集合名)
        /// <summary>
        /// 集合
        /// </summary>
        public string _collName { get; set; }
        public MongoDBHelper(string collName)
        {
            this._collName = collName;
        }
        #endregion

        #region 连接配置
        /// <summary>
        /// 链接
        /// </summary>
        private static readonly string conneStr = "mongodb://127.0.0.1:27017";
        /// <summary>
        /// 数据库
        /// </summary>
        private static readonly string dbName = "Testdb";
        #endregion

        #region 单例创建链接
        private static IMongoClient _mongoclient { get; set; }
        private static IMongoClient CreateClient()
        {
            if (_mongoclient == null)
            {
                _mongoclient = new MongoClient(conneStr);
            }
            return _mongoclient;
        }
        #endregion

        #region 获取链接和数据库

        private IMongoClient client = CreateClient();
        public IMongoDatabase _database { get { return _mongoclient.GetDatabase(dbName); } }

        public IMongoDatabase GetDatabase()
        {
            return _database;
        }
        public IMongoCollection<T> GetClient<T>() where T : class, new()
        {
            return _database.GetCollection<T>(_collName);
        }
        #endregion

        #region +InsertMany 批量插入
        /// <summary>
        /// 批量插入
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="t">实体集合</param>
        /// <returns></returns>
        public int InsertMany<T>(List<T> t) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                client.InsertMany(t);
                return 1;
            }
            catch (Exception ex)
            {
                return 0;
            }
        }
        #endregion

        #region +InsertManyAsync 异步批量插入
        /// <summary>
        /// 异步批量插入
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="t">实体集合</param>
        /// <returns></returns>
        public async Task<int> InsertManyAsync<T>(List<T> t) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                await client.InsertManyAsync(t);
                return 1;
            }
            catch
            {
                return 0;
            }
        }
        #endregion
        
        #region +Add 添加一条数据
        /// <summary>
        /// 添加一条数据
        /// </summary>
        /// <param name="t">添加的实体</param>
        /// <param name="host">mongodb连接信息</param>
        /// <returns></returns>
        public int Add<T>(T t) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                client.InsertOne(t);
                return 1;
            }
            catch (Exception ex)
            {
                return 0;
            }
        }
        #endregion

        #region +AddAsync 异步添加一条数据
        /// <summary>
        /// 异步添加一条数据
        /// </summary>
        /// <param name="t">添加的实体</param>
        /// <param name="host">mongodb连接信息</param>
        /// <returns></returns>
        public async Task<int> AddAsync<T>(T t) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                await client.InsertOneAsync(t);
                return 1;
            }
            catch
            {
                return 0;
            }
        }
        #endregion

        #region +Update 修改一条数据
        /// <summary>
        /// 修改一条数据
        /// </summary>
        /// <param name="t">添加的实体</param>
        /// <param name="host">mongodb连接信息</param>
        /// <returns></returns>
        public UpdateResult Update<T>(T t, string id, bool isObjectId = true) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                //修改条件
                FilterDefinition<T> filter;
                if (isObjectId)
                {
                    filter = Builders<T>.Filter.Eq("_id", new ObjectId(id));
                }
                else
                {
                    filter = Builders<T>.Filter.Eq("_id", id);
                }
                //要修改的字段
                var list = new List<UpdateDefinition<T>>();
                foreach (var item in t.GetType().GetProperties())
                {
                    if (item.Name.ToLower() == "id") continue;
                    list.Add(Builders<T>.Update.Set(item.Name, item.GetValue(t)));
                }
                var updatefilter = Builders<T>.Update.Combine(list);
                return client.UpdateOne(filter, updatefilter);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region +UpdateAsync 异步修改一条数据
        /// <summary>
        /// 异步修改一条数据
        /// </summary>
        /// <param name="t">添加的实体</param>
        /// <param name="host">mongodb连接信息</param>
        /// <returns></returns>
        public async Task<UpdateResult> UpdateAsync<T>(T t, string id, bool isObjectId) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                //修改条件
                FilterDefinition<T> filter;
                if (isObjectId)
                {
                    filter = Builders<T>.Filter.Eq("_id", new ObjectId(id));
                }
                else
                {
                    filter = Builders<T>.Filter.Eq("_id", id);
                }
                //要修改的字段
                var list = new List<UpdateDefinition<T>>();
                foreach (var item in t.GetType().GetProperties())
                {
                    if (item.Name.ToLower() == "id") continue;
                    list.Add(Builders<T>.Update.Set(item.Name, item.GetValue(t)));
                }
                var updatefilter = Builders<T>.Update.Combine(list);
                return await client.UpdateOneAsync(filter, updatefilter);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region +UpdateManay 批量修改数据
        /// <summary>
        /// 批量修改数据
        /// </summary>
        /// <param name="dic">要修改的字段</param>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="filter">修改条件</param>
        /// <returns></returns>
        public UpdateResult UpdateManay<T>(Dictionary<string, string> dic, FilterDefinition<T> filter) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                T t = new T();
                //要修改的字段
                var list = new List<UpdateDefinition<T>>();
                foreach (var item in t.GetType().GetProperties())
                {
                    if (!dic.ContainsKey(item.Name)) continue;
                    var value = dic[item.Name];
                    list.Add(Builders<T>.Update.Set(item.Name, value));
                }
                var updatefilter = Builders<T>.Update.Combine(list);
                return client.UpdateMany(filter, updatefilter);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region +UpdateManayAsync 异步批量修改数据
        /// <summary>
        /// 异步批量修改数据
        /// </summary>
        /// <param name="dic">要修改的字段</param>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="filter">修改条件</param>
        /// <returns></returns>
        public async Task<UpdateResult> UpdateManayAsync<T>(Dictionary<string, string> dic, FilterDefinition<T> filter) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                T t = new T();
                //要修改的字段
                var list = new List<UpdateDefinition<T>>();
                foreach (var item in t.GetType().GetProperties())
                {
                    if (!dic.ContainsKey(item.Name)) continue;
                    var value = dic[item.Name];
                    list.Add(Builders<T>.Update.Set(item.Name, value));
                }
                var updatefilter = Builders<T>.Update.Combine(list);
                return await client.UpdateManyAsync(filter, updatefilter);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region Delete 删除一条数据
        /// <summary>
        /// 删除一条数据
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="id">objectId</param>
        /// <returns></returns>
        public DeleteResult Delete<T>(string id, bool isObjectId = true) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                FilterDefinition<T> filter;
                if (isObjectId)
                {
                    filter = Builders<T>.Filter.Eq("_id", new ObjectId(id));
                }
                else
                {
                    filter = Builders<T>.Filter.Eq("_id", id);
                }
                return client.DeleteOne(filter);
            }
            catch (Exception ex)
            {
                throw ex;
            }

        }
        #endregion

        #region DeleteAsync 异步删除一条数据
        /// <summary>
        /// 异步删除一条数据
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="id">objectId</param>
        /// <returns></returns>
        public async Task<DeleteResult> DeleteAsync<T>(string id, bool isObjectId = true) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                //修改条件
                FilterDefinition<T> filter;
                if (isObjectId)
                {
                    filter = Builders<T>.Filter.Eq("_id", new ObjectId(id));
                }
                else
                {
                    filter = Builders<T>.Filter.Eq("_id", id);
                }
                return await client.DeleteOneAsync(filter);
            }
            catch (Exception ex)
            {
                throw ex;
            }

        }
        #endregion

        #region DeleteMany 删除多条数据
        /// <summary>
        /// 删除一条数据
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="filter">删除的条件</param>
        /// <returns></returns>
        public DeleteResult DeleteMany<T>(FilterDefinition<T> filter) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                return client.DeleteMany(filter);
            }
            catch (Exception ex)
            {
                throw ex;
            }

        }
        #endregion

        #region DeleteManyAsync 异步删除多条数据
        /// <summary>
        /// 异步删除多条数据
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="filter">删除的条件</param>
        /// <returns></returns>
        public async Task<DeleteResult> DeleteManyAsync<T>(FilterDefinition<T> filter) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                return await client.DeleteManyAsync(filter);
            }
            catch (Exception ex)
            {
                throw ex;
            }

        }
        #endregion

        #region FindOne 根据id查询一条数据
        /// <summary>
        /// 根据id查询一条数据
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="id">objectid</param>
        /// <param name="field">要查询的字段,不写时查询全部</param>
        /// <returns></returns>
        public T FindOne<T>(string id, bool isObjectId = true, string[] field = null) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                FilterDefinition<T> filter;
                if (isObjectId)
                {
                    filter = Builders<T>.Filter.Eq("_id", new ObjectId(id));  //默认id为objectid类型
                }
                else
                {
                    filter = Builders<T>.Filter.Eq("_id", id);
                }
                //不指定查询字段
                if (field == null || field.Length == 0)
                {
                    return client.Find(filter).FirstOrDefault<T>();
                }

                //制定查询字段
                var fieldList = new List<ProjectionDefinition<T>>();
                for (int i = 0; i < field.Length; i++)
                {
                    fieldList.Add(Builders<T>.Projection.Include(field[i].ToString()));
                }
                var projection = Builders<T>.Projection.Combine(fieldList);
                fieldList?.Clear();
                return client.Find(filter).Project<T>(projection).FirstOrDefault<T>();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region FindOneAsync 异步根据id查询一条数据
        /// <summary>
        /// 异步根据id查询一条数据
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="id">objectid</param>
        /// <returns></returns>
        public async Task<T> FindOneAsync<T>(string id, bool isObjectId = true, string[] field = null) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                FilterDefinition<T> filter;
                if (isObjectId)
                {
                    filter = Builders<T>.Filter.Eq("_id", new ObjectId(id));
                }
                else
                {
                    filter = Builders<T>.Filter.Eq("_id", id);
                }

                //不指定查询字段
                if (field == null || field.Length == 0)
                {
                    return await client.Find(filter).FirstOrDefaultAsync();
                }

                //制定查询字段
                var fieldList = new List<ProjectionDefinition<T>>();
                for (int i = 0; i < field.Length; i++)
                {
                    fieldList.Add(Builders<T>.Projection.Include(field[i].ToString()));
                }
                var projection = Builders<T>.Projection.Combine(fieldList);
                fieldList?.Clear();
                return await client.Find(filter).Project<T>(projection).FirstOrDefaultAsync();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region FindList 查询集合
        /// <summary>
        /// 查询集合
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="filter">查询条件</param>
        /// <param name="field">要查询的字段,不写时查询全部</param>
        /// <param name="sort">要排序的字段</param>
        /// <returns></returns>
        public List<T> FindList<T>(FilterDefinition<T> filter = null, string[] field = null, SortDefinition<T> sort = null) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);

                //不指定查询字段
                if (field == null || field.Length == 0)
                {
                    if (sort == null)
                        return client.Find(filter).ToList();
                    //进行排序
                    return client.Find(filter).Sort(sort).ToList();
                }

                //制定查询字段
                var fieldList = new List<ProjectionDefinition<T>>();
                for (int i = 0; i < field.Length; i++)
                {
                    fieldList.Add(Builders<T>.Projection.Include(field[i].ToString()));
                }
                var projection = Builders<T>.Projection.Combine(fieldList);
                fieldList?.Clear();
                if (sort == null) return client.Find(filter).Project<T>(projection).ToList();
                //排序查询
                return client.Find(filter).Sort(sort).Project<T>(projection).ToList();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region FindListAsync 异步查询集合
        /// <summary>
        /// 异步查询集合
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="filter">查询条件</param>
        /// <param name="field">要查询的字段,不写时查询全部</param>
        /// <param name="sort">要排序的字段</param>
        /// <returns></returns>
        public async Task<List<T>> FindListAsync<T>(FilterDefinition<T> filter, string[] field = null, SortDefinition<T> sort = null) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                //不指定查询字段
                if (field == null || field.Length == 0)
                {
                    if (sort == null) return await client.Find(filter).ToListAsync();
                    return await client.Find(filter).Sort(sort).ToListAsync();
                }

                //制定查询字段
                var fieldList = new List<ProjectionDefinition<T>>();
                for (int i = 0; i < field.Length; i++)
                {
                    fieldList.Add(Builders<T>.Projection.Include(field[i].ToString()));
                }
                var projection = Builders<T>.Projection.Combine(fieldList);
                fieldList?.Clear();
                if (sort == null) return await client.Find(filter).Project<T>(projection).ToListAsync();
                //排序查询
                return await client.Find(filter).Sort(sort).Project<T>(projection).ToListAsync();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region FindListByPage 分页查询集合
        /// <summary>
        /// 分页查询集合
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="filter">查询条件</param>
        /// <param name="pageIndex">当前页</param>
        /// <param name="pageSize">页容量</param>
        /// <param name="count">总条数</param>
        /// <param name="field">要查询的字段,不写时查询全部</param>
        /// <param name="sort">要排序的字段</param>
        /// <returns></returns>
        public List<T> FindListByPage<T>(FilterDefinition<T> filter, int pageIndex, int pageSize, out long count, string[] field = null, SortDefinition<T> sort = null) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                count = client.CountDocuments(filter);
                //不指定查询字段
                if (field == null || field.Length == 0)
                {
                    if (sort == null) return client.Find(filter).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToList();
                    //进行排序
                    return client.Find(filter).Sort(sort).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToList();
                }

                //制定查询字段
                var fieldList = new List<ProjectionDefinition<T>>();
                for (int i = 0; i < field.Length; i++)
                {
                    fieldList.Add(Builders<T>.Projection.Include(field[i].ToString()));
                }
                var projection = Builders<T>.Projection.Combine(fieldList);
                fieldList?.Clear();

                //不排序
                if (sort == null) return client.Find(filter).Project<T>(projection).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToList();

                //排序查询
                return client.Find(filter).Sort(sort).Project<T>(projection).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToList();

            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region FindListByPageAsync 异步分页查询集合
        /// <summary>
        /// 异步分页查询集合
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="filter">查询条件</param>
        /// <param name="pageIndex">当前页</param>
        /// <param name="pageSize">页容量</param>
        /// <param name="field">要查询的字段,不写时查询全部</param>
        /// <param name="sort">要排序的字段</param>
        /// <returns></returns>
        public async Task<List<T>> FindListByPageAsync<T>(FilterDefinition<T> filter, int pageIndex, int pageSize, string[] field = null, SortDefinition<T> sort = null) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                //不指定查询字段
                if (field == null || field.Length == 0)
                {
                    if (sort == null) return await client.Find(filter).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();
                    //进行排序
                    return await client.Find(filter).Sort(sort).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();
                }

                //制定查询字段
                var fieldList = new List<ProjectionDefinition<T>>();
                for (int i = 0; i < field.Length; i++)
                {
                    fieldList.Add(Builders<T>.Projection.Include(field[i].ToString()));
                }
                var projection = Builders<T>.Projection.Combine(fieldList);
                fieldList?.Clear();

                //不排序
                if (sort == null) return await client.Find(filter).Project<T>(projection).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();

                //排序查询
                return await client.Find(filter).Sort(sort).Project<T>(projection).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();

            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region Count 根据条件获取总数
        /// <summary>
        /// 根据条件获取总数
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="filter">条件</param>
        /// <returns></returns>
        public long Count<T>(FilterDefinition<T> filter) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                return client.CountDocuments(filter);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region CountAsync 异步根据条件获取总数
        /// <summary>
        /// 异步根据条件获取总数
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="filter">条件</param>
        /// <returns></returns>
        public async Task<long> CountAsync<T>(FilterDefinition<T> filter) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                return await client.CountDocumentsAsync(filter);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion
    }
复制代码

 

代码地址:

https://github.com/bill1411/mybase/tree/master/Solution/Mongo

从Vehicle-ReId到AI换脸,应有尽有,解你所惑 - 周见智 - 博客园

mikel阅读(838)

来源: 从Vehicle-ReId到AI换脸,应有尽有,解你所惑 – 周见智 – 博客园

最近在做视频搜索的技术调研,已经初步有了一些成果输出,算法准确性还可以接受,基本达到了调研的预期。现将该技术调研过程中涉及到的内容总结一篇文章分享出来,内容比较多,初看起来可能关系不大,但是如果接触面稍微广一些,就会发现其实原理都是差不多的。

先描述一下我要解决的问题:上传任意一个车辆截图,需要从海量的监控视频中(高速监控)找到该车辆目标历史经过点位的历史视频录像。这个问题本质上其实就是图像检索或者叫Object-ReId问题,唯一不同的是,找到车辆目标后需要定位到视频录像,后者其实很简单,只需要事先建立好图片和录像片段之间的索引关系即可,跟我们今天要讨论的内容关系不大。(本文图片点击查看更清楚)

 

图像检索的本质

首先要清楚的是,机器是无法直接理解图像或者声音这种多媒体数据的,甚至也包括一些复杂的结构化数据(比如数据库中的表数据)。传统机器学习中一个常见的概念是“特征工程”,说的是从原始的、复杂的数据中提取出有一定代表意义的特征数据(简单表示,比如用多维向量),这些特征数据相比原数据要简单得多!然后再用算法去分析、学习这些特征数据,得出规律。基于神经网络的深度学习中已经慢慢弱化了“特征工程”这一概念,因为深度学习主流的方式基本都是端到端的流程,输入直接产生输出,特征提取的过程已经在神经网络中的某个部分帮你做完了。

那么现在图片检索的问题,其实已经被转变成“特征数据检索的问题”了。原来需要进行图像比对,现在只需要进行特征比对,而显然机器更擅长后者。

 

Object-ReId/Person-ReId/Vehicle-ReId的原理

ReId技术一般用于多摄像机目标重识别的场合,目标经过多个点位被多个摄像机拍摄录像存储,输入该目标的一张截图,可以利用ReId的技术将该目标经过的点位找出来,用于后续的运行轨迹分析,该技术一般用于安防/公安领域,目标一般可以是行人(Person-ReId)和车辆(Vehicle-ReId)。ReId的核心就是前面提到的图像检索技术,从海量图片中(视频由图片组成)检索指定的图片,那么这个检索的准确性就依赖于前面提到的特征比对算法准确性了。

上图描述了Vehicle-ReId的一个完整流程,我们可以看到特征比对只是其中的一个环节,完整的流程还要包括车辆目标提取(目标检测)、特征提取、索引建立。

 

图像特征提取

前面已经知道了图像检索的本质其实就是特征的比对,那么这个特征应该如何提取得到呢?

传统的机器学习可能需要手工设计算法去提取特征,提取的方式有多种多样,拿图像而言,可以从颜色角度入手,提取图像的颜色特征。比如大部分人可能比较熟悉的颜色直方图,这个算法就可以用来计算图像的“像素组成”(比如各种颜色分别占比多少),像素组成确实可以在一定程度上代表原始图片。在某些数据集中,像素组成相似的原始图片也比较相似。然后拿像素组成数据去做分类/聚类,基本就可以搞定这种机器学习任务。

现在流行的深度学习已经抛弃了人工提取特征的做法,取而代之的是直接上神经网络。还是拿图像而言,直接用卷积网络无脑卷一卷,就可以得到对应的图像特征。这种方式提取到的特征是unreadable的,不像像素组成,它确实可以被人理解。卷积网络最后提取到的特征人工无法直观理解,它可能仅仅是一个高维向量,不做处理的话,你都无法在二维/三维空间中显示出来。所以很多人说深度学习(神经网络)是不可解释的,在某种程度上它确实无法被解释。

由前面的内容我们不难发现,特征提取是非常重要的一步,直接关系到后面基于特征的一切应用的准确性。特征是对原始数据的一种表达,是计算机容易识别的一种理想格式。理想情况下,特征之间的特性和规律可以直接反应原始数据之间的特性和规律。传统机器学习过程中,如何找到合适的特征提取方法是一项非常难的事情,现在主流的深度学习过程中,已经简化了该步骤。

需要注意的是,一些论文、博客、文章中对特征的称呼不尽相同,比如Features(特征)/Representation(表达或表示)/Embedding(嵌入)/Encoding(编码)等等基本都是一个意思(注意中文翻译可能不太准确)。其实从这些英文单词不难看出,不管用什么词,人们想要表达的意思大概都是差不多的,即特征是对原数据的简要表达。

上图是深度学习中利用神经网络来提取特征,原始神经网络是一个多分类网络,我们可以使用分类数据集去拟合该神经网络中的参数,待训练完毕后,去掉最上(最右)用于分类的网络层,倒数第二层即可输出128维的特征数据。基于这个128维的特征数据,我们可以做很多事情:

1、原网络做的分类任务。例子中原网络本身就是一个分类网络,对这些特征数据进行分类,推理出原输入图片的类型。看看是巩俐还是奥巴马;

2、本文的重点。特征数据比对,用于图像检索、人脸识别、Vehicle-ReId等;

3、用于无监督学习。先对一堆没有标签的图片数据集合进行特征提取,基于这些特征数据利用K-Means或DBSCAN等算法自动将这些图片分成若干类,类似Iphone相册自动分类功能(比如相同的人脸归为一类)。

总之,特征数据非常有用,是一切机器学习(深度学习)任务中的重中之重。

 

图像特征比对

前面已经多次提到特征比对,那么特征比对的方式有哪些呢?二维空间中的2个点,我们可以通过计算之间的直线距离来判断它们是否相似(越小越相似,为零表示完全相同。反之亦然);三维空间中的2个点,我们照样可以通过计算之间的直线距离来判断它们是否相似(越小越相似,为零表示完全相同。反之亦然)。那么对于更高维的点呢?照样可以用这种方式去做比较!

这里需要说的是,直线距离只是手段之一,还有其他距离可以计算。比如不太常见的余弦距离,它代表两个点到坐标原点线段之间的夹角余弦值,角度越小代表2点距离越近。余弦距离跟直线距离不同,一个是用角度大小衡量、一个是用线段长短衡量。

我们可以看到,直线距离(欧氏距离)关注点是2个特征的各个维度具体数值,而余弦距离关注点是2个特征的维度分布。直线距离为零,代表2个特征的各个维度数值完全相同;而余弦距离为零,代表2个特征的维度分布完全相同。(1, 1, 1)和(2, 2, 2)两个特征的直线距离不为零,因为它们各个维度的数值不同,但是它们的余弦距离为零,因为它们的维度分布是完全一样的,都是1:1:1。

举一个实际的例子,张三的语数外三科的成绩为(80, 80, 80),李四的语数外三科的成绩为(90, 90, 90),这两的直线距离不为零,李四的三科成绩明显跟张三不同。但是这两的余弦距离为零,可以理解为李四的三科平衡程度跟张三一致,都不偏科。所以不同的距离代表含义不同,直线距离可以用来衡量他们的成绩是否差不多,而余弦距离则可以用来衡量他们偏科程度是否差不多。两个距离,视角不一样。

 

高维特征降维和可视化

前面举例子用的是二维或者三维数据,其实特征数据大部分时候都是高维的,比如128维或1024维等等。在不做任何处理的情况下,我们无法直观看到这些高维数据之间的关系,因为它既不是二维的我们可以画到平面坐标系中、也不是三维的我们可以画在立体坐标系中。如果想要直观看到数据之间的关系,我们需要对这些特征再次进行降维处理,将之前的高维数据一一映射到二维或者三维空间。比如现在提取到了1000张图片的特征数据,每个数据都是128维,我们如果想要在二维或三维空间观察这1000个特征数据之间的关系(比如特征数据之间的紧密程度),从而判断原始图片之间的关系、或已经知道原始图片之间的关系我们需要验证提取到的特征数据是否合理。

值得高兴的是,已经有非常成熟的降维技术可以使用,比如常见的PCA和t-SNE算法,直接可以将高维数据降到二维或者三维,而依然保留原始数据的特性。通过这些手段我们可以直观看到高维特征数据在二维/三维空间中的呈现,从而观察原数据之间的关系。下图是我提取高速公路视频画面中车辆目标的特征数据,原始特征是128维,然后利用t-SNE算法进行降维处理,最后得到的二维格式数据并在二维坐标系中将原始图片一一对应绘制出来。

我们可以看到,外观相似的车辆(这些图片是随机抽取的,并没有标签数据)聚集在一起,用前面讲到的距离来说,就是越相似的图片特征距离越近。这个可视化的过程基本可以证明我前面设计的特征提取网络是合理的,这个网络用于提取其他类似车辆图片的特征也是OK的。

看到这里的朋友其实可能已经注意到,机器学习(或深度学习)的主要工作其实说白了就是一个不断对数据进行降维的过程,我们可以将原始非结构化数据诸如文字/图片/音频看成是一个维度很高(超高维)的数据格式,然后设计算法将这些超高维数据降到低维格式,再去应用。前面讲到的特征提取也算是降维的一种。

 

自编码器

谈到降维技术,这里我想介绍一个超级牛逼的结构,学名叫auto-encoder(翻译过来就是自编码器)。我刚开始接触这个东西的时候就感叹于它的神奇强大,因为它结构相当简单,而且理解起来并不费劲,但是起到的效果惊人。它的结构是对称的,前面半部分主要对输入(一般指图片)进行编码,其实就是特征提取,比如提取得到一个128维的特征数据。后半部分马上对该特征进行解码,还原成原来的图片。前半部分叫编码器,后半部分叫生成器。这个东西可以由两个神经网络组成,大概结构类似如下图:

如上图这种结构的神经网络训练也相当容易,你的训练数据集不需要提前标注,因为网络的输出就是网络的输入,换句话说,你可以把它当作无监督学习!有人可能就要问了,一编一解到底想要干什么呢?这样操作的主要目的是得到中间的特征数据(论文术语叫space representation),没错,用这种方式训练出来的前半部分可以当作一种特征提取器(原定义叫编码器),将它作用在其他类似图片数据上,就可以得到对应的特征数据,起到的作用跟前面介绍的其他特征提取方式差不多。

这种自编码器的一大优势是训练它的数据集合不需要标注,训练是一个无监督学习过程。它不像前面提到的那些特征提取方法,大部分都是基于监督学习的。也就是虽然我们的目的是训练一个特征提取的网络(网络输出是高维特征数据),但是往往需要提前准备带有标签的训练数据(如分类数据)。当然,除了这里提到的自编码器之外,还有其他的一些特征提取结构,也属于无监督学习的范畴,比如孪生网络、或者采用triplet loss训练时,这些都是无监督学习的例子。

 

AI换脸技术

这个话题其实跟今天谈到的特征数据(提取/比对)关系不是特别大,只是前面我已经提到了自编码器,知道了这个结构的前半部分能够应用于特征提取的任务,而刚才没说的是,它的后半部分(生成器)是可以用于AI换脸的,之前火爆全网的AI换脸可以采用类似技术实现。

其实AI换脸原理也非常简单,自编码器的前半部分用于人脸编码(特征数据,下同),它的后半部分基于该编码进行人脸还原(图像生成),这个过程即是我们进行网络训练的过程:一个人脸输入,不断拟合网络让它输出同一个人脸。如果我们在应用该网络结构的时候稍微改变一下:将A人脸输入到它的编码器,得到它的人脸编码后,不要使用对应的生成器去还原人脸,而是改用另外B人脸的生成器去还原人脸!那么会得到什么呢?答案是:得到一张A的脸部轮廓+B的五官细节。下图显示AI换脸的技术原理:

如上图可知,编码器输出的人脸编码在某种意义上可以看作是脸部轮廓的表示,生成器基于该轮廓进行五官细节恢复,最终得到一个合成后的人脸。下面是一个将赵本山五官换到杨澜脸部的例子(完整视频链接):

通过AI换脸的这个例子我们可以得知,特征提取相当重要,整个流程能够正常work(或work得很好)大部分依靠中间生成的特征数据(人脸编码)。神经网络的神奇之处就在于,有些东西你无法解释,但是就是凑效。

 

其他常见的无监督学习

既然提到了AI换脸,索性就将本篇文章的主题扯远一些。自编码器的训练过程属于无监督学习的范畴,根据相关大神的名言:无监督学习才是真正的人工智能。确实没错,监督学习在某些场合有非常多的局限性。那么除了上面提到的自编码器训练属于无监督学习,机器学习领域还有哪些无监督学习的例子呢?

1、类似K-Means这些聚类算法,算法可以自动从给定的数据(特征数据)寻找规律,无需事先提供参考样例

2、类似t-SNE这种降维算法,算法可以自动从给定的数据(特征数据)寻找规律,无需事先提供参考样例

3、类似上面提到的自编码器,以及其他一些生成型网络,包括GAN相关技术,都属于无监督学习

4、类似采取triplet loss等技术直接操控特征数据的网络训练方式(基于特征数据计算loss),也属于无监督学习

只要在训练过程中无需事先提供参考样例(标注样本)的机器学习过程全部都可以看作是无监督学习,无监督学习跟算法并没什么直接关系,传统机器学习、现在主流基于神经网络的深度学习都可以有无监督学习方式。

好了,本篇文章到这里结束了。由于时间原因,以及查资料验证费时间,前前后后花了半个月功夫。其实主要目的是为了说明特征数据在机器学习(深度学习)领域的重要性,这个领域基本所有的东西全部围绕它展开的,所有的原始非结构化数据/结构化数据都需要先转成特征数据,再被机器学习算法(深度学习神经网络)学习。

肢体识别与应用 - 周见智 - 博客园

mikel阅读(882)

来源: 肢体识别与应用 – 周见智 – 博客园

肢体识别与应用

肢体识别本质上还是分类任务,该技术有很多应用场景,比如手势识别控制类应用、动作检测类应用、动作评测类应用、以及一些移动设备AR视频合成类应用。对于纯粹的检测类应用,可以直接对RGB源图像进行分类,当前基于CNN的图像分类技术已经非常成熟和稳定,准确性也完全达到了实用标准。而对于另外涉及到肢体评测、AR合成类的应用,单靠图像分类技术已经不太够了,需要用到关键点检测技术,它能检测出肢体关键点(比如四肢、面部纹理、手指等),然后基于检测到的关键点做进一步处理。关键点检测的原理其实跟分类技术原理差不多,只是神经网络的输出不太一样。

图像分类

在大部分深度学习入门教程中,图像分类基本就是hello world级别存在的,最常见的是识别猫还是狗。ImageNet比赛比的是1000分类,基本涵盖了大部分常见物体。图像分类的流程很简单,输入RGB图像,输出各分类的概率值,1000分类任务就输出1000个概率值。图像分类是深度学习技术在CV领域最基础的应用,主要原因是它涵盖了深度学习最重要的环节:特征自动提取。我们刚学习CNN时接触到的Alexnet、GoogleNet或者Resnet之类的,全部都是特征自动提取网络,如果需要对输入分类,网络后面再接特征分类层即可。

那么具体到肢体识别任务上,也可以将它看作一个图像分类任务,这里以‘剪刀石头布识别’为例子,是一个3分类的分类任务:

上面这张图大部分接触过深度学习的人应该很清楚,最基础的分类任务。图像分类起到的作用很有限,仅仅是对肢体做一个大概的分类,无法满足更细致的需求,接下来介绍关键点检测。

 

关键点检测

关键点检测的技术当前也非常成熟了,github上最早的OpenPose项目非常火爆,有几万星星。通过训练后,模型能够实时推理检测出人体关键点(四肢和面部,具体技术原理跟其他采用神经网络的深度学习技术相似,可以参考网络资料)。

关键点检测出来之后,可以进一步对这些关键点进行分析。分析结果就不仅仅是肢体识别了,还可以通过这些关键点相互之间的关系来判断肢体动作的协调程度、或者像一些AR应用在人体上叠加一些其他内容(类似抖音视频合成)。

 接下来介绍两种关键点分析方法,一种是基于传统机器学习,先从关键点中人工提取特征、然后再对特征进行分析(基于常见的机器学习算法去做分类、聚类或者回归之类的);另外一种是基于深度学习端到端的处理方式,关键点直接作为神经网络的输入,输出对应想要的结果。从这两种不同的处理方式中可以了解到传统机器学习和深度学习的区别,关于两者的区别可以看一下。

 

关键点+机器学习方式

机器学习的一大特点是需要人工提取原数据的特征,这个过程叫特征工程。如果需要对关键点进行分析,先要从这些关键点中提取高质量特征数据,为什么要高质量?因为提取特征的方式或者说维度有很多,要保证提取到的特征数据最具代表性、能够充分表达原数据的特点。现以‘石头剪刀布手势识别’为例子,来说明如何人工提取特征数据。我们知道,石头剪刀布3种手势之间手指的形状差异很大,手指和手指关节之间的直线距离差异很明显,那么我们可以将关节之间直线距离当作关键点的特征数据,并将其提取出来。

 使用提取到的特征数据(特征向量,Feature Vector),训练K-Means或者DBSCAN等机器学习分类算法模型,并将其泛用到其他新数据(新特征)。

 

关键点+深度学习方式

跟机器学习不同,深度学习的一大特点就是神经网络能够自动提取特征、自动寻找源数据内部的规律。至于它如何提取、或者根据什么原理去提取,很难解释。你只需要将源数据(关键点坐标)输入神经网络,直接可以输出对应结果,如果是前面提到的‘石头剪刀布手势识别’任务,网络可以输出三种手势分别对应的概率。我们可以看到,机器学习和深度学习两种方式的输入是不一样的,前者的输入是人工提取的特征数据(某些关节点之间的直线距离值集合),后者输入可以直接是关键点坐标值(关键点XY值集合)。正因为深度学习这种端到端的处理方式、省去了繁琐的特征工程,才能使得其大规模应用在复杂的非结构化数据上,比如CV中的CNN网络,它直接接收RGB格式图像作为输入,它处理的对象是像素值,而无需人工做过多的干预。关于这块详细参见之前的一篇文章。

我们用关键点数据训练最简单的神经网络,将训练得到的模型应用到真实数据上。本文主要介绍了肢体识别的几种方式,以及采用关键点检测时,如何处理关键点数据。同时提到了机器学习和深度学习不同的工作流程,以及各自的特点。有问题的朋友可以留言交流。

强烈推荐 | 阿里开源的这11个神级项目 - 苏三说技术 - 博客园

mikel阅读(801)

来源: 强烈推荐 | 阿里开源的这11个神级项目 – 苏三说技术 – 博客园

前言

最近趁着国庆节放假休息,特地整理了一下,阿里巴巴开源的10款神级项目。

这些开源项目中的绝大多数,我都在实际工作中用过,或者有同事用过。确实挺不错,挺有价值的,现在推荐给大家。

1. Druid

Druid自称是Java语言中最好的数据库连接池,它能够提供强大的监控和扩展功能。监控后台如下图所示:

Druid的主要优点如下:

  • 它能监控数据库访问性能。
  • 它提供了WallFilter,它是基于SQL语义分析来实现防御SQL注入攻击的。
  • 它提供了多种监测连接泄漏的手段。
  • 它提供了数据库密码加密的功能。
  • 它能打印SQL执行日志。

github地址: https://github.com/alibaba/druid

maven中央仓库: https://mvnrepository.com/artifact/com.alibaba/druid

配置maven依赖:

<dependency>
		<groupId>com.alibaba</groupId>
		<artifactId>druid</artifactId>
		<version>${druid-version}</version>
</dependency>

2. fastjson

fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。

fastjson的主要优点如下:

  • 速度快,fastjson相对其他JSON库的特点是快,从2011年fastjson发布1.1.x版本之后,其性能从未被其他Java实现的JSON库超越。
  • 使用广泛,fastjson在阿里巴巴大规模使用,在数万台服务器上部署,fastjson在业界被广泛接受。在2012年被开源中国评选为最受欢迎的国产开源软件之一。
  • 使用简单,fastjson的API十分简洁。
//序列化
String text = JSON.toJSONString(obj); 
//反序列化
VO vo = JSON.parseObject("{...}", VO.class); 
  • 功能完备,支持泛型,支持流处理超大文本,支持枚举,支持序列化和反序列化扩展。

github地址: https://github.com/alibaba/fastjson

maven中央仓库: https://mvnrepository.com/artifact/com.alibaba/fastjson

配置maven依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.76</version>
</dependency>

3. Dubbo

Apache Dubbo 是一款微服务开发框架,它提供了 RPC通信 与 微服务治理 两大关键能力。这意味着,使用 Dubbo 开发的微服务,将具备相互之间的远程发现与通信能力, 同时利用 Dubbo 提供的丰富服务治理能力,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。

同时 Dubbo 是高度可扩展的,用户几乎可以在任意功能点去定制自己的实现,以改变框架的默认行为来满足自己的业务需求。它目前已交给Apache管理和维护。

架构图如下:

Dubbo的主要优点如下:

  • 基于透明接口的RPC
  • 智能负载均衡
  • 自动服务注册和发现
  • 高扩展性
  • 运行时流量路由
  • 可视化服务治理
  • 云原生友好

github地址: https://github.com/apache/dubbo

maven中央仓库: https://mvnrepository.com/artifact/com.alibaba/dubbo/

配置maven依赖:

<properties>
    <dubbo.version>3.0.3</dubbo.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo</artifactId>
        <version>${dubbo.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-dependencies-zookeeper</artifactId>
        <version>${dubbo.version}</version>
        <type>pom</type>
    </dependency>
</dependencies>

4. Rocketmq

Apache RocketMQ是一个分布式消息和流媒体平台,具有低延迟、高性能和可靠性、万亿级容量和灵活的可扩展性。

它提供了多种功能:

  • 消息传递模式,包括发布/订阅、请求/回复和流媒体
  • 金融级交易消息
  • 基于DLedger的内置容错和高可用配置选项
  • 多种跨语言客户端,如Java、C/C++、Python、Go
  • 可插拔传输协议,例如 TCP、SSL、AIO
  • 内置消息追踪能力,也支持opentracing
  • 多功能大数据和流媒体生态系统集成
  • 按时间或偏移量的消息追溯
  • 可靠的 FIFO 和同一队列中的严格有序消息传递
  • 高效的拉推式消费模式
  • 单个队列百万级消息累积能力
  • 多种消息传递协议,如 JMS 和 OpenMessaging
  • 灵活的分布式横向扩展部署架构
  • 闪电般的批量消息交换系统
  • 各种消息过滤机制,例如 SQL 和 Tag
  • 用于隔离测试和云隔离集群的 Docker 镜像
  • 用于配置、指标和监控的功能丰富的管理仪表板
  • 认证和授权
  • 免费的开源连接器,用于源和接收器

rocketmq后台管理界面:

github地址: https://github.com/apache/rocketmq

maven中央仓库: https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-spring-boot-starter

rocketmq包含:服务端和客户端,在我们的项目中主要关注客户端的代码即可。

配置maven依赖:

<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-client</artifactId>
    <version>4.3.0</version>
</dependency>

5. Arthas

Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱。

当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:

  • 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
  • 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
  • 遇到问题无法在线上 Debug,难道只能通过加日志再重新发布吗?
  • 线上遇到某个用户的数据处理有问题,但线上同样无法 Debug,线下无法重现!
  • 是否有一个全局视角来查看系统的运行状况?
  • 有什么办法可以监控到JVM的实时运行状态?
  • 怎么快速定位应用的热点,生成火焰图?
  • 怎样直接从JVM内查找某个类的实例?

分析代码消耗时间:

Arthas支持JDK 6+,能够运行在多种操作系统上,比如:Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。

github地址: https://alibaba.github.io/arthas/

maven中央仓库: https://mvnrepository.com/artifact/com.taobao.arthas/arthas-spring-boot-starter

在目标机器执行如下命令即可启动arthas:

curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

6. Nacos

Nacos是一个易于使用的平台,专为动态服务发现和配置以及服务管理而设计。它可以帮助您轻松构建云原生应用程序和微服务平台。

服务是Nacos的一等公民。Nacos 支持几乎所有类型的服务,例如Dubbo/gRPC 服务、Spring Cloud RESTFul 服务或Kubernetes 服务。

Nacos 提供了四大功能。

  • 服务发现和服务健康检查。Nacos 使服务通过 DNS 或 HTTP 接口注册自己和发现其他服务变得简单。Nacos 还提供服务的实时健康检查,以防止向不健康的主机或服务实例发送请求。
  • 动态配置管理。动态配置服务允许您在所有环境中以集中和动态的方式管理所有服务的配置。Nacos 无需在更新配置时重新部署应用程序和服务,这使得配置更改更加高效和敏捷。
  • 动态 DNS 服务。Nacos 支持加权路由,让您更容易在数据中心内的生产环境中实现中层负载均衡、灵活的路由策略、流量控制和简单的 DNS 解析服务。它可以帮助您轻松实现基于 DNS 的服务发现,并防止应用程序耦合到特定于供应商的服务发现 API。
  • 服务和元数据管理。Nacos 提供了一个易于使用的服务仪表板,帮助您管理您的服务元数据、配置、kubernetes DNS、服务健康和指标统计。

Nacos 地图:

Nacos 生态图:

github地址: https://github.com/alibaba/nacos

maven中央仓库: https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-nacos-discovery

7. easyexcel

Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。

easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便。

64M内存1分钟内读取75M(46W行25列)的Excel

github地址: https://github.com/alibaba/easyexcel

maven中央仓库: https://mvnrepository.com/artifact/com.alibaba/easyexcel

配置maven依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.2.6</version>
</dependency>

8. Sentinel

随着分布式系统变得越来越流行,服务之间的可靠性变得比以往任何时候都更加重要。

Sentinel以“流量”为切入点,在流量控制、 流量整形、熔断、系统自适应保护等多个领域开展工作,保障微服务的可靠性和弹性。

Sentinel具有以下特点:

  • 丰富的适用场景:Sentinel在阿里巴巴得到了广泛的应用,几乎覆盖了近10年双11(11.11)购物节的所有核心场景,比如需要限制突发流量的“秒杀”满足系统容量、消息削峰填谷、下游不可靠业务断路、集群流量控制等。
  • 实时监控:Sentinel 还提供实时监控能力。可以实时查看单台机器的运行时信息,以及500个节点以下集群的运行时信息汇总。
  • 广泛的开源生态系统:Sentinel 提供与 Spring Cloud、Dubbo 和 gRPC 等常用框架和库的开箱即用集成。您只需将适配器依赖项添加到您的服务即可轻松使用 Sentinel。
  • 多语言支持:Sentinel 为 Java、Go和C++提供了本机支持。
  • 丰富的SPI扩展:Sentinel提供简单易用的SPI扩展接口,可以让您快速自定义逻辑,例如自定义规则管理、适配数据源等。

功能概述:

生态系统景观:

github地址: https://github.com/alibaba/Sentinel

maven中央仓库: https://mvnrepository.com/artifact/com.alibaba.csp/sentinel-core

配置maven依赖:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.8.2</version>
</dependency>

9. otter

阿里巴巴B2B公司,因为业务的特性,卖家主要集中在国内,买家主要集中在国外,所以衍生出了杭州和美国异地机房的需求,同时为了提升用户体验,整个机房的架构为双A,两边均可写,由此诞生了otter这样一个产品。

otter第一版本可追溯到04~05年,此次外部开源的版本为第4版,开发时间从2011年7月份一直持续到现在,目前阿里巴巴B2B内部的本地/异地机房的同步需求基本全上了otte4。

目前同步规模:

  • 同步数据量6亿
  • 文件同步1.5TB(2000w张图片)
  • 涉及200+个数据库实例之间的同步
  • 80+台机器的集群规模

otter能解决什么?

  • 异构库同步。 mysql -> mysql/oracle. (目前开源版本只支持mysql增量,目标库可以是mysql或者oracle,取决于canal的功能)
  • 单机房同步 (数据库之间RTT < 1ms)

    a. 数据库版本升级

    b. 数据表迁移

    c. 异步二级索引

  • 异地机房同步 (比如阿里巴巴国际站就是杭州和美国机房的数据库同步,RTT > 200ms,亮点)

    a. 机房容灾

  • 双向同步

    a. 避免回环算法 (通用的解决方案,支持大部分关系型数据库)

    b. 数据一致性算法 (保证双A机房模式下,数据保证最终一致性,亮点)

  • 文件同步
    站点镜像 (进行数据复制的同时,复制关联的图片,比如复制产品数据,同时复制产品图片).

工作原理图:

单机房复制示意图:

异地机房复制示意图:

github地址: https://github.com/alibaba/otter

maven中央仓库: https://mvnrepository.com/artifact/com.alibaba.otter/canal.client

10. P3C

P3C插件呈现了阿里巴巴 Java 编码指南,它整合了阿里巴巴集团技术团队多年来的最佳编程实践。由于我们鼓励重用和更好地理解彼此的程序,因此大量 Java 编程团队对跨项目的代码质量提出了苛刻的要求。

阿里巴巴过去见过很多编程问题。例如,有缺陷的数据库表结构和索引设计可能会导致软件架构缺陷和性能风险。另一个例子是混乱的代码结构难以维护。此外,未经身份验证的易受攻击的代码容易受到黑客的攻击。为了解决这些问题,我们为阿里巴巴的Java开发人员编写了这份文档。

更多信息请参考阿里巴巴Java编码指南:

  • 中文版:阿里巴巴Java开发手册
  • 英文版:Alibaba Java Coding Guidelines

该项目由3部分组成:

  • PMD 实现
  • IntelliJ IDEA 插件
  • Eclipse 插件

四十九条规则是基于PMD实现的,更多详细信息请参考P3C-PMD文档。IDE 插件(IDEA 和 Eclipse)中实现的四个规则如下:

  • [Mandatory]禁止使用已弃用的类或方法。
    注意:例如,应该使用 decode(String source, String encode) 而不是不推荐使用的方法 decode(String encodeStr)。一旦接口被弃用,接口提供者就有义务提供一个新的接口。同时,客户端程序员有义务检查它的新实现是什么。
  • [Mandatory]来自接口或抽象类的重写方法必须用 @Override 注释标记。反例:对于 getObject() 和 get0bject(),第一个是字母“O”,第二个是数字“0”。为了准确判断覆盖是否成功,需要一个@Override注解。同时,一旦抽象类中的方法签名发生变化,实现类将立即报告编译时错误。
  • [Mandatory] 静态字段或方法应直接通过其类名而不是其对应的对象名来引用。
  • [Mandatory] hashCode 和 equals 的用法应该遵循:
  1. 如果 equals 被覆盖,则覆盖 hashCode。
  2. 这两个方法必须为 Set 重写,因为它们用于确保不会在 Set 中插入重复的对象。
  3. 如果使用自定义对象作为 Map 的键,则必须覆盖这两个方法。注意:String 可以用作 Map 的键,因为这两个方法已经被重写。

使用p3c插件的效果:

最新版阿里巴巴Java开发手册下载地址:
https://github.com/alibaba/p3c/blob/master/Java开发手册(嵩山版).pdf

github地址:https://github.com/alibaba/p3c/tree/master/idea-plugin

11. Spring Cloud Alibaba

Spring Cloud Alibaba 为分布式应用开发提供一站式解决方案。它包含开发分布式应用程序所需的所有组件,使您可以轻松地使用 Spring Cloud 开发应用程序。

使用Spring Cloud Alibaba,您只需添加一些注解和少量配置,即可将Spring Cloud应用连接到阿里巴巴的分布式解决方案,并通过阿里巴巴中间件构建分布式应用系统。

主要功能如下:

  • 流量控制和服务降级:默认支持 HTTP 服务的流量控制。您还可以使用注释自定义流量控制和服务降级规则。规则可以动态更改。
  • 服务注册和发现:可以注册服务,客户端可以使用 Spring 管理的 bean,自动集成 Ribbon 来发现实例。
  • 分布式配置:支持分布式系统中的外化配置,配置变化时自动刷新。
  • 事件驱动:支持构建与共享消息系统连接的高度可扩展的事件驱动微服务。
  • 分布式事务:支持高性能、易用的分布式事务解决方案。
  • 阿里云对象存储:海量、安全、低成本、高可靠的云存储服务。支持随时随地在任何应用程序中存储和访问任何类型的数据。
  • 阿里云SchedulerX:精准、高可靠、高可用的定时作业调度服务,响应时间秒级。
  • 阿里云短信:覆盖全球的短信服务,阿里短信提供便捷、高效、智能的通讯能力,帮助企业快速联系客户。

主要包含如下组件:

  • Sentinel:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
  • Nacos:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
  • RocketMQ:一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。
  • Dubbo:Apache Dubbo™ 是一款高性能 Java RPC 框架。
  • Seata:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。
  • Alibaba Cloud OSS: 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。
  • Alibaba Cloud SchedulerX: 阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。
  • Alibaba Cloud SMS: 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。

github地址: https://github.com/alibaba/spring-cloud-alibaba

配置maven依赖:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2.2.6.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙扫描下发二维码关注一下,您的支持是我坚持写作最大的动力。

求一键三连:点赞、转发、在看。

关注公众号:【苏三说技术】,在公众号中回复:面试、代码神器、开发手册、时间管理有超赞的粉丝福利,另外回复:加群,可以跟很多BAT大厂的前辈交流和学习。

SQL Server 创建链接服务器(SSMS可以访问的有效) - 听风吹雨 - 博客园

mikel阅读(1054)

来源: SQL Server 创建链接服务器 – 听风吹雨 – 博客园

适用场景:对远程的DB进行操作。

2000与2005对比:SQL Server 2000版本中也有链接远程DB的SQL,但是功能比较弱,扩展性差,支持的查询比较简单。而SQL Server 2005版本的SSMS中已经有了 服务器对象->链接服务器 的功能点,用户首先创建一个远程DB的链接对象,之后就可以像本地表一样执行表的DML了。

创建步骤:在SQL Server 2005版本打开SSMS,服务器对象->链接服务器->右击 新建链接服务器,在图2中是一种设置方式,也有其它的设置方式,比如:[图解]sqlserver中创建链接服务器,图3是安全性选项中设置远程数据库的账号和密码。

(图1:新建链接)

(图2:设置链接)

(图3:设置帐号)

注意事项: 在MSSQL2005中Rpc的默认设置如图4所示, 需要把它设置为图5, 右键点击远程链接->属性->服务器选项->Rpc和Rpc Out,这两个值需要设置为True。

(图4: 默认设置)

(图5: 正确设置)

但在MSSQL2008下不能直接修改链接服务器 ‘ETV2_LINK’ 的RPC配置成TURE,可以通过语句修改如下:

USE [master]
GO
EXEC master.dbo.sp_serveroption @server=N’ETV2_LINK’, @optname=N’rpc’, @optvalue=N’true’
GO
EXEC master.dbo.sp_serveroption @server=N’ETV2_LINK’, @optname=N’rpc out’, @optvalue=N’true’
GO

生成脚本:如果已经通过操作界面生成了 ‘ETV2_LINK’ 的链接服务器,那么我们如果需要把它移植到其它数据库(部署、更新)的时候,就可以通过下面的方法来生产SQL脚本,你也可以通过修改SQL脚本来快速新建或修改链接服务器,比如修改@server链接服务器名称,修改@datasrc远程链接的数据库对象。

(图6: 生成SQL脚本)

SQL Server 2005生成远程链接对象的SQL脚本:

/****** 对象:  LinkedServer [ETV2_LINK]    脚本日期: 09/08/2010 17:36:11 ******/
EXEC master.dbo.sp_addlinkedserver @server = N’ETV2_LINK’, @srvproduct=N’ETV2_LINK’, @provider=N’SQLNCLI’, @datasrc=N’BWA035\BWA035_2K5′
GO
EXEC master.dbo.sp_serveroption @server=N’ETV2_LINK’, @optname=N’collation compatible’, @optvalue=N’false’
GO
EXEC master.dbo.sp_serveroption @server=N’ETV2_LINK’, @optname=N’data access’, @optvalue=N’true’
GO
EXEC master.dbo.sp_serveroption @server=N’ETV2_LINK’, @optname=N’dist’, @optvalue=N’false’
GO
EXEC master.dbo.sp_serveroption @server=N’ETV2_LINK’, @optname=N’pub’, @optvalue=N’false’
GO
EXEC master.dbo.sp_serveroption @server=N’ETV2_LINK’, @optname=N’rpc’, @optvalue=N’true’
GO
EXEC master.dbo.sp_serveroption @server=N’ETV2_LINK’, @optname=N’rpc out’, @optvalue=N’true’
GO
EXEC master.dbo.sp_serveroption @server=N’ETV2_LINK’, @optname=N’sub’, @optvalue=N’false’
GO
EXEC master.dbo.sp_serveroption @server=N’ETV2_LINK’, @optname=N’connect timeout’, @optvalue=N’0′
GO
EXEC master.dbo.sp_serveroption @server=N’ETV2_LINK’, @optname=N’collation name’, @optvalue=null
GO
EXEC master.dbo.sp_serveroption @server=N’ETV2_LINK’, @optname=N’lazy schema validation’, @optvalue=N’false’
GO
EXEC master.dbo.sp_serveroption @server=N’ETV2_LINK’, @optname=N’query timeout’, @optvalue=N’0′
GO
EXEC master.dbo.sp_serveroption @server=N’ETV2_LINK’, @optname=N’use remote collation’, @optvalue=N’true’

使用假设已经创建了名为ETV2_LINK的远程链接对象,那么你就可以像下面的方式来使用这个对象操作远程DB。

使用场景1: 查询ETV2_LINK这个远程链接对象的[etV2_Online]数据库中VisiteLog_20100629表的数据。模板形如:Select * From [链接服务器名].[远程数据库名].[所有者].[表名]

–查询远程DB表TableName
select * from ETV2_LINK.[etV2_Online].dbo.VisiteLog_20100629

使用场景2: 判断ETV2_LINK这个远程链接对象的[etV2_Online]数据库中是否存在名为VisiteLog_20100629的表。

–注意:是sys.objects不是sysobjects
–判断远程用户是否存在某张表
IF EXISTS (SELECT * FROM ETV2_LINK.[etV2_Online].sys.objects WHERE name = N’VisiteLog_20100629′ AND type in (N’U’))
BEGIN
–逻辑处理
print ‘存在表’
END

使用场景3: 判断远程DB的[etV2_Online]数据库中是否存在名为VisiteLog_20100629的表。只不过这个表名是参数化的,可以通过传入的参数进行判断。这里只是简单的设置变量的值并使用OUT来返回变量。

–判断远程用户是否存在某张表(参数化表名),返回变量
DECLARE @IsExistTable VARCHAR(10)
DECLARE @Tablename VARCHAR(50)
DECLARE @sqlString NVARCHAR(4000)
SET @IsExistTable = ‘False’
SET @Tablename = ‘VisiteLog_’+convert(varchar(9),getdate()-1,112) –例如VisiteLog_20100629
SET @sqlString =
‘IF EXISTS (SELECT * FROM ETV2_LINK.[etV2_Online].sys.objects WHERE name = N”’+@Tablename+”’ AND type in (N”U”))
set @IsExistTableOUT =”True”’
EXEC sp_executesql @sqlString,N’@IsExistTableOUT varchar(10) OUTPUT’,@IsExistTableOUT=@IsExistTable OUTPUT

IF (@IsExistTable = ‘True’)–存在
BEGIN
–逻辑处理
print ‘存在表’
END

补充: SQL Server 2000版本连接远程服务器的SQL脚本,更多相关脚步可以参考:在T-SQL语句中访问远程数据库(openrowset/opendatasource/openquery)

–方法1:
select *  from openrowset(‘SQLOLEDB’,’server=192.168.0.67;uid=sa;pwd=password’,’SELECT * FROM BCM2.dbo.tbAppl’)

–方法2:
select *  from openrowset(‘SQLOLEDB’,’192.168.0.67′;’sa’;’password’,’SELECT * FROM BCM2.dbo.tbAppl’)

链接服务器"(null)"的 OLE DB 访问接口 "SQLNCLI10" 返回了消息 "Cannot start more transactions on this session." - 潇湘隐者 - 博客园

mikel阅读(953)

来源: 链接服务器”(null)”的 OLE DB 访问接口 “SQLNCLI10” 返回了消息 “Cannot start more transactions on this session.” – 潇湘隐者 – 博客园

开发同事反馈一个SQL Server存储过程执行的时候,报“链接服务器”(null)”的 OLE DB 访问接口 “SQLNCLI10” 返回了消息 “Cannot start more transactions on this session.”。这个存储过程,个人做了一个精简和脱敏处理后如下:

BEGIN TRY    BEGIN TRANSACTION                            INSERT INTO OPENDATASOURCE(‘SQLOLEDB’, ‘DATA SOURCE=XXX.XXX.XX.XXX;USER ID=XXX;PASSWORD=XXX).XXX.DBO.XXX     (……..)    SELECT ….. FROM …..    COMMIT TRANSACTIONEND TRYBEGIN CATCH      IF @@TRANCOUNT>0        ROLLBACK         ………END CATCH

 

对于这种问题,是因为:对于大多数 OLE DB 访问接口(包括 SQL Server),必须将隐式或显示事务中的数据修改语句中的 XACT_ABORT 设置为 ON。 唯一不需要该选项的情况是在提供程序支持嵌套事务时。具体参考官方文档:

XACT_ABORT must be set ON for data modification statements in an implicit or explicit transaction against most OLE DB providers, including SQL Server. The only case where this option is not required is if the provider supports nested transactions.”

在存储过程里面的BEGIN TRANSACTION前设置SET XACT_ABORT ON;后问题即可解决。

参考资料:

https://docs.microsoft.com/zh-cn/sql/t-sql/statements/set-xact-abort-transact-sql?view=sql-server-2017

SQL Server 2016 配置链接服务器及数据还原【图文】_高文龙_51CTO博客

mikel阅读(904)

来源: SQL Server 2016 配置链接服务器及数据还原【图文】_高文龙_51CTO博客

SQL Server 2016 配置链接服务器及数据还原

说到SQL Server的管理,管理员应该是非常有压力的,需要对数据进行备份及监控,当然备份就比较简单了,通过SSMS创建计划任务就可以对指定的数据库进行定期备份了,但是有的时候需要对数据库中指定的数据进行恢复,也需要了解一定的知识才可以,比如通过SQL的FULL备份还原指定一张表,那如何做呢,当然有很多工具可以完成,但是如果通过SSMS进行管理操作呢,今天我们就说说,通过创建链接服务器来还原操作及管理远程服务器。

环境介绍:我们需要通过本地的SSMS管理远程的SQL Server,远程的SQL Server:192.168.5.16。对于我本地来说远程的SQL Server就叫链接服务器。

我们首先在本地的SSMS展开—服务器对象—-链接服务器—-新建链接服务器

我们输入远程SQL Server的IP地址,然后选择服务器类型—SQL Server,根据自己的环境来选择

然后在安全性菜单—-选择建立连接的范式,我们使用远程登录及密码进行验证;

我们需要输入远程SQL Server的登录账户及密码。然后单击确认

此时我们就可以看见远程链接服务器创建成功了,远程链接服务器上的数据库及表信息都可以看见

此时链接服务器就可以用了,具体格式见下:

Select * From [链接服务器名].[远程数据库名].[所有者].[表名]

比如

select * from [192.168.5.16].[DB1].[dbo].info
1.

我们在远程本地插入几条数据(当然也可以通过远程服务器进行插入)

然后在本地查看链接服务器的数据库信息

select * from [192.168.5.16].[DB1].[dbo].info
1.

我们现在需要将远程SQL Server (链接服务器)上的DB1数据库下的INFO表的所有数据拷贝一份到本地SQL Server服务器的DB2数据库下,然后新建一张info表(本地的info表是不存在的,其实叫新建一张表。)。

我们使用下列命令,将链接服务器DB1库下的info表中所有的数据导入到DB2.dbo.info中,

Select * into DB2.dbo.info From [192.168.5.16].[DB1].[dbo].info
1.

刷新后,我们发现DB2这个数据库多了一个表

我们通过查询本地数据确实已经导入成功了。

导入成功后,如果环境需要的话,我们可以将原来的表删除,然后重名即可。

drop tables xxx
———————————–
©著作权归作者所有:来自51CTO博客作者高文龙的原创作品,谢绝转载,否则将追究法律责任
SQL Server 2016 配置链接服务器及数据还原
https://blog.51cto.com/gaowenlong/1868529

如何使用SQL Server链接服务器访问DB2 Server - 寒枫 - 博客园

mikel阅读(1018)

来源: 如何使用SQL Server链接服务器访问DB2 Server – 寒枫 – 博客园

首先,需要安装Microsoft OLE DB Provider for DB2

下载地址:http://download.microsoft.com/download/B/B/2/BB22098A-C071-415F-9269-2EB26CEFB562/DB2OLEDB_CN.exe
安装以后,菜单多出一项:

接下来,运行“数据访问工具”详细进行配置。

假设DB2的配置信息如下所示

可访问的数据库地址:jdbc:db2://10.18.50.149:50000/BPMDB
用户名:db2admin
密码:db2admin

说明下第五步的字符集:

主机 CCSID 可以通过以下命令获悉。

db2 connect to BPMDB user db2admin using db2admin
db2 get db cfg for BPMDB
查询结果:
C:\IBM\BPM\v8.5\db2\BIN>db2 connect to BPMDB user db2admin using db2admin

数据库连接信息

数据库服务器         = DB2/NT64 10.1.1
SQL 授权标识         = DB2ADMIN
本地数据库别名       = BPMDB

C:\IBM\BPM\v8.5\db2\BIN>db2 get db cfg for BPMDB

数据库 BPMDB 的数据库配置

数据库配置发行版级别                                    = 0x0f00
数据库发行版级别                                        = 0x0f00

数据库地域                                              = US
 数据库代码页                                            = 1208
 数据库代码集                                            = UTF-8
数据库国家/地区代码                                     = 1
数据库整理顺序                                          = IDENTITY
备用整理顺序                              (ALT_COLLATE) =
数字兼容性                                              = OFF

PC 代码页,可以通过以下操作获悉。

第12步的字符串是需要用到的。

Provider=DB2OLEDB;User ID=db2admin;Password=db2admin;Initial Catalog=BPMDB;Network Transport Library=TCP;Host CCSID=1208;PC Code Page=936;Network Address=10.18.50.149;Network Port=50000;Package Collection=BPMDB;Process Binary as Character=False;Units of Work=RUW;DBMS Platform=DB2/MVS;Defer Prepare=False;Rowset Cache Size=0;Persist Security Info=True;Connection Pooling=False;Derive Parameters=False;

 

终于到了配置链接服务器的时候了,继续走起。

 

添加成功,下图能看到链接服务器 HEATHERNET

 

测试链接服务器查询数据

查询正常,能显示中文字段。

 

提示:如果第5步字符集配置不当将会导致中文字段名无法显示正常且一直会提示该字段不存在。

 

消息 7339,级别 16,状态 1,第 1 行
OLE DB provider ‘DB2OLEDB’ for linked server ‘HEATHERNET’ returned invalid data for column ‘[HEATHERNET].[BPMDB].[DB2ADMIN].[V_USER_MAPPING].POSITION_NAME’.

以下是配置正确的查询结果

至此,DB2的链接服务器搭建完毕。