# 系统研发技术栈介绍

author:韩皖

createTime:2022-05-14 16:00

# 1 系统架构

# 1.1 罗万智联平台系统架构

  • 终端设备层:用于提供标准或私有协议连接物理设备的SDK,负责南向设备的数据采集和指令控制,基于SDK可实现驱动的快速开发
  • 通讯层:负责为设备接入罗万智联平台提供通讯服务保障,LW-IoT的通讯层分为两个通讯服务:LoRa核心网,ACCESSOR非LoRa通讯方式服务端
  • 影子设备层:提供标准或私有协议招抄和主动上报的数据解析,对所有物理设备提供服务端镜像设备的映射
  • 罗万智联平台控制中心:分为两部分:第一部分为物联网底层控制中心,主要为物联网通讯和影子设备提供设备和协议的数据维护;第二部分为物联网上层应用模块,主要为罗万智联平台用户提供应用服务

# 1.2 AMI&HES系统架构

AMI&HES集抄系统,整体从系统层面分为四层:

  • AMI集抄系统,主要包含三块内容:AMI&HES系统主站功能业务、HES设备档案&抄读业务对接、 基于CIM协议与计量主站的对外接口对接;

  • HES物联网平台,物联网平台层分为三层架构设计,从上至下分别为:HES计划调度模块(HES-PLAN)、影子设备模块(HES-SHADOW)、通讯服务模块(HES-ACCESSOR) ;

  • 网络通讯层,依据市场需求,目前我们满足NB-IoT通讯方式和4G通讯方式,两种通讯方式的终端设备为client端和server端均支持;

  • 采集感知层,采集层我们提供4G采集模块、NB-IoT采集模块;

# 2 开发技术栈

# 2.1 JAVA技术栈

# 2.1.1 开发框架:
  1. Spring Boot 2.5.x (opens new window)
  2. Druid 1.2.8 (opens new window) 数据库连接池
  3. Mybatis 2.2.x持久层框架
  4. Apache Shiro (opens new window) 权限框架、安全框架
  5. Netty4.x (opens new window)(https://netty.io/)高性能网络编程框架
  6. Caffeine (opens new window) 本地缓存框架
  7. logback 日志打印
  8. actor
  9. 周坤补充P011技术框架

# 2.2 前端技术栈

# 2.2.1 开发框架:
  1. Vue 2.x (opens new window),Vuex (opens new window),Vue Router (opens new window)

  2. Axios (opens new window)

  3. ant-design-vue (opens new window)

  4. 地图组件高德地图 (opens new window)百度地图 (opens new window)必应地图 (opens new window)

  5. app开发(小程序) uni-app (opens new window)

  6. echarts (opens new window)

  7. mock (opens new window)

# 2.3 golang技术栈

  1. 廖宝众补充

# 2.4 第三方组件

  1. ELK-ElasticSearch (opens new window) 全文检索,日志,时序数据存储
  2. ELK-Logstash (opens new window)收集、解析和转换日志
  3. ELK-Kibana (opens new window)数据的探索、可视化和分析
  4. Redis (opens new window) Redis,设备配置,状态管理,缓存.
  5. MySQL (opens new window) 关系型数据管理系统
  6. kafka (opens new window)分布式发布订阅消息系统
  7. emqx (opens new window)消息中间件,即时通讯框架
  8. Mongodb (opens new window)分布式文件存储的数据库、IoT元数据存储

# 2.5 系统运维

1.Prometheus (opens new window)开源监控告警解决方案

2.kubernetes、jenkins、docker自动化运维部署

# 2.6 业务协议相关

​ 1.DLMS

​ 2.DL/T645、DL/T698协议

​ 3.Modbus协议

​ 4.传感器协议

# 3 前后端开发接口管理工具

# 3.1 RAP2说明

  • 系统研发部采用rap2作为项目的API接口可视化管理工具

  • 地址:http://192.168.20.161/repository 新员工需要自行注册

  • 功能介绍说明 https://github.com/thx/rap2-delos/wiki/user_manual

注:注册时姓名请填写真实姓名

# 3.2 前后端使用介绍

服务端:定义接口可视化定义,系统研发部不止用该文档定义前后端交互,服务端通过中间件进行交互的数据结构也需要在接口管理工具中进行定义

前端:面向数据接口编程,最主要的是提供mock数据功能,这样通过在封装的ajax请求中通过配置智能切换联调服务器和mock数据服务器(rap2搭建服务器)。当然为了切换省事,我们将可以启动不同的npm script脚本来切换。

前后端分离:前后端分离完全按照定义的字段和数据格式,并且api服务器有日志功能,可以及时收到更改记录,这样避免了wiki化的接口文档在后端更改参数后没有及时通知前端导致联调过程中一些不必要的问题发生。前端完全可以根据与后端协商的数据格式去mock数据,这里的mock数据走的就是内网搭建的api服务器,这样等后端开发完接口部署到联调服务器上时,我们只需要将mock服务器的ip地址换成联调服务器地址,这样我们就可以做到完美切换联调服务器,毕竟其他请求参数和响应数据都是一样的

# 3.3 前端Mock功能

  1. 前后端分离 让前端攻城师独立于后端进行开发。
  2. 增加单元测试的真实性 通过随机数据,模拟各种场景。
  3. 开发无侵入 不需要修改既有代码,就可以拦截 Ajax 请求,返回模拟的响应数据。
  4. 用法简单 符合直觉的接口。
  5. 数据类型丰富 支持生成随机的文本、数字、布尔值、日期、邮箱、链接、图片、颜色等。
  6. 方便扩展 支持支持扩展更多数据类型,支持自定义函数和正则。

# 3.4 前后端规约(以阿里开发手册 第十章节 为准)

1.【强制】前后端交互的 API,需要明确协议、域名、路径、请求方法、请求内容、状态码、响应体。

说明: 1)协议:生产环境必须使用 HTTPS。 2)路径:每一个 API 需对应一个路径,表示API 具体的请求地址: a)代表一种资源,只能为名词,推荐使用复数,不能为动词,请求方法已经表达动作意义。 b)URL 路径不能使用大写,单词如果需要分隔,统一使用下划线。 c)路径禁止携带表示请求内容类型的后缀,比如".json",".xml",通过 accept 头表达即可。 3)请求方法:对具体操作的定义,常见的请求方法如下: a)GET:从服务器取出资源。 b)POST:在服务器新建一个资源。c) PUT:在服务器更新资源。 d) DELETE:从服务器删除资源。 4)请求内容:URL 带的参数必须无敏感信息或符合安全要求;body 里带参数时必须设置 Content-Type。5) 响应体:响应体 body 可放置多种数据类型,由 Content-Type 头来确定。

2.【强制】前后端数据列表相关的接口返回,如果为空,则返回空数组[]或空集合{}。

说明:此条约定有利于数据层面上的协作更加高效,减少前端很多琐碎的 null 判断。

3.【强制】服务端发生错误时,返回给前端的响应信息必须包含 HTTP 状态码,errorCode、

errorMessage、用户提示信息四个部分。 说明:四个部分的涉众对象分别是浏览器、前端开发、错误排查人员、用户。其中输出给用户的提示信息要求:简短清晰、提示友好,引导用户进行下一步操作或解释错误原因,提示信息可以包括错误原因、上下文环境、推荐操作等。 正例:常见的 HTTP 状态码如下 1)200 OK: 表明该请求被成功地完成,所请求的资源发送到客户端。 2)401 Unauthorized: 请求要求身份验证,常见对于需要登录而用户未登录的情况。 3)403 Forbidden:服务器拒绝请求,常见于机密信息或复制其它登录用户链接访问服务器的情况。 4)404 Not Found: 服务器无法取得所请求的网页,请求资源不存在。 5)500 Internal Server Error: 服务器内部错误。

4.【强制】在前后端交互的 JSON 格式数据中,所有的 key 必须为小写字母开始的

lowerCamelCase 风格,符合英文表达习惯,且表意完整。 正例:errorCode / errorMessage / assetStatus / menuList / orderList / configFlag 反例:ERRORCODE / ERROR_CODE / error_message / error-message / errormessage / ErrorMessage / msg

5.【强制】errorMessage 是前后端错误追踪机制的体现,可以在前端输出到 type="hidden"

文字类控件中,或者用户端的日志中,帮助我们快速地定位出问题。

6.【强制】对于需要使用超大整数的场景,服务端一律使用 String 字符串类型返回,禁止使用

Long 类型。 说明:Java 服务端如果直接返回 Long 整型数据给前端,JS 会自动转换为 Number 类型(注:此类型为双精度浮点数,表示原理与取值范围等同于 Java 中的 Double)。Long 类型能表示的最大值是 2 的 63 次方 -1,在取值范围之内,超过 2 的 53 次方 (9007199254740992)的数值转化为 JS 的 Number 时,有些数 值会有精度损失。扩展说明,在 Long 取值范围内,任何 2 的指数次整数都是绝对不会存在精度损失的,所以说精度损失是一个概率问题。若浮点数尾数位与指数位空间不限,则可以精确表示任何整数,但很不幸, 双精度浮点数的尾数位只有 52 位。 反例:通常在订单号或交易号大于等于 16 位,大概率会出现前后端单据不一致的情况,比如,"orderId": 362909601374617692,前端拿到的值却是: 362909601374617660。

7.【强制】HTTP 请求通过 URL 传递参数时,不能超过 2048 字节。

说明:不同浏览器对于 URL 的最大长度限制略有不同,并且对超出最大长度的处理逻辑也有差异,2048 字节是取所有浏览器的最小值。

反例:某业务将退货的商品 id 列表放在 URL 中作为参数传递,当一次退货商品数量过多时,URL 参数超长, 传递到后端的参数被截断,导致部分商品未能正确退货。

8.【强制】HTTP 请求通过 body 传递内容时,必须控制长度,超出最大长度后,后端解析会出错。

说明:nginx 默认限制是 1MB,tomcat 默认限制为 2MB,当确实有业务需要传较大内容时,可以通过调大服务器端的限制。

9.【强制】在翻页场景中,用户输入参数的小于 1,则前端返回第一页参数给后端;后端发现用户输入的参数大于总页数,直接返回最后一页。

10.【强制】服务器内部重定向必须使用 forward;外部重定向地址必须使用 URL 统一代理模块生成,否则会因线上采用 HTTPS 协议而导致浏览器提示“不安全”,并且还会带来 URL 维护不一致的问题。

11.【推荐】服务器返回信息必须被标记是否可以缓存,如果缓存,客户端可能会重用之前的请求结果。

说明:缓存有利于减少交互次数,减少交互的平均延迟。 正例:http 1.1 中,s-maxage 告诉服务器进行缓存,时间单位为秒,用法如下, response.setHeader("Cache-Control", "s-maxage=" + cacheSeconds);

12.【推荐】服务端返回的数据,使用 JSON 格式而非 XML。

说明:尽管 HTTP 支持使用不同的输出格式,例如纯文本,JSON,CSV,XML,RSS 甚至 HTML。如果我们使用的面向用户的服务,应该选择 JSON 作为通信中使用的标准数据交换格式,包括请求和响应。此外, application/JSON 是一种通用的 MIME 类型,具有实用、精简、易读的特点。

13.【推荐】前后端的时间格式统一为"yyyy-MM-dd HH:mm:ss",统一为 GMT。

14.【参考】在接口路径中不要加入版本号,版本控制在 HTTP 头信息中体现,有利于向前兼容。

说明:当用户在低版本与高版本之间反复切换工作时,会导致迁移复杂度升高,存在数据错乱风险。

# 4 数据库设计管理说明

注:开发过程中,应用系统开发中详细设计文档和数据库设计文档不可缺少

  • 新项目开发时,数据库设计文档【参考】之前项目文档,进行数据库文档编写
  • 项目需求迭代时,需要先进行数据库设计,修改内容采用红色标注
  • 新项目开发和项目需求迭代涉及到数据库设计部分,需要进行数据库评审,原则上与项目相关的服务端和前端开发都需要参加
  • 数据库设计规范以 《阿里开发手册 第五章节 MySQL数据库 规约为准》

# 4.1 建表规约

1.【强制】表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint (1 表示是,0 表示否)。 说明:任何字段如果为非负数,必须是 unsigned。 注意:POJO 类中的任何布尔类型的变量,都不要加 is 前缀,所以,需要在<resultMap></resultMap>设置从 is_xxx 到Xxx 的映射关系。数据库表示是与否的值,使用 tinyint 类型,坚持 is_xxx 的命名方式是为了明确其取值含义与取值范围。 正例:表达逻辑删除的字段名 is_deleted,1 表示删除,0 表示未删除。

2.【强制】表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。说明:MySQL 在 Windows 下不区分大小写,但在 Linux 下默认是区分大小写。因此,数据库名、表名、字段名,都不允许出现任何大写字母,避免节外生枝。 正例:aliyun_admin,rdc_config,level3_name 反例:AliyunAdmin,rdcConfig,level_3_name

3.【强制】表名不使用复数名词。 说明:表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于 DO 类名也是单数形式,符合表达习惯。

4.【强制】禁用保留字,如 desc、range、match、delayed 等,请【参考】 MySQL 官方保留字。

5.【强制】主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。 说明:pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的简称。

6.【强制】小数类型为 decimal,禁止使用 float 和 double。 说明:在存储的时候,float 和 double 都存在精度损失的问题,很可能在比较值的时候,得到不正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数并分开存储。

7.【强制】如果存储的字符串长度几乎相等,使用 char 定长字符串类型。

8.【强制】varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。

9.【强制】表必备三字段:id, create_time, update_time。 说明:其中 id 必为主键,类型为bigint unsigned、单表时自增、步长为 1。create_time, update_time 的类型均为 datetime 类型,前者现在时表示主动式创建,后者过去分词表示被动式更新。

10.【推荐】表的命名最好是遵循“业务名称_表的作用”。 正例:alipay_task / force_project / trade_config

11.【推荐】库名与应用名称尽量一致。

12.【推荐】如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。

13.【推荐】字段允许适当冗余,以提高查询性能,但必须考虑数据一致。冗余字段应遵循: 1)不是频繁修改的字段。 2)不是唯一索引的字段。 3)不是 varchar 超长字段,更不能是text 字段。 正例:各业务线经常冗余存储商品名称,避免查询时需要调用 IC 服务获取。

14.【推荐】单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。 说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。

15.【参考】合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。 正例:无符号值可以避免误存负数,且扩大了表示范围。

对象 年龄区间 字节 类型 表示范围
150 岁之内 tinyint unsigned 1 无符号值:0 到 255
数百岁 smallint unsigned 2 无符号值:0 到 65535
恐龙化石 数千万年 int unsigned 4 无符号值:0 到约 43 亿
太阳 约 50 亿年 bigint unsigned 8 无符号值:0 到约 10 的 19 次方

# 4.2 索引规约

1.【强制】业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。 说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外, 即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。

2.【强制】超过三个表禁止 join。需要 join 的字段,数据类型保持绝对一致;多表关联查询时, 保证被关联的字段需要有索引。 说明:即使双表 join 也要注意表索引、SQL 性能。

3.【强制】在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度。 说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分度会高达 90% 以上,可以使用 count(distinct left(列名, 索引长度))/count()的区分度来确定。 4.【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。 说明:索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。

5.【推荐】如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。 正例:where a=? and b=? order by c; 索引:a_b_c 反例:索引如果存在范围查询,那么索引有序性无法利用,如:WHERE a>10 ORDER BY b; 索引 a_b 无法排序。

6.【推荐】利用覆盖索引来进行查询操作,避免回表。 说明:如果一本书需要知道第 11 章是什么标题,会翻开第 11 章对应的那一页吗?目录浏览一下就好,这个目录就是起到覆盖索引的作用。 正例:能够建立索引的种类分为主键索引、唯一索引、普通索引三种,而覆盖索引只是一种查询的一种效果,用 explain 的结果,extra 列会出现:using index。

7.【推荐】利用延迟关联或者子查询优化超多分页场景。 说明:MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N 行,那当offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写。 正例:先快速定位需要获取的 id 段,然后再关联: SELECT t1.*

FROM 表 1 as t1, (select id from 表 1 where 条 件 LIMIT 100000,20 ) as t2 where t1.id=t2.id

8.【推荐】SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好。 说明: 1)consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。 2)ref 指的是使用普通的索引(normal index)。3) range 对索引进行范围检索。 反例:explain 表的结果,type=index,索引物理文件全扫描,速度非常慢,这个 index 级别比较 range 还低,与全表扫描是小巫见大巫。

9.【推荐】建组合索引的时候,区分度最高的在最左边。 正例:如果 where a=? and b=?,a 列的几乎接近于唯一值,那么只需要单建 idx_a 索引即可。 说明:存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如:where c>? and d=? 那么即使 c 的区分度更高,也必须把d 放在索引的最前列,即建立组合索引 idx_d_c。

10.【推荐】防止因字段类型不同造成的隐式转换,导致索引失效。

11.【参考】创建索引时避免有如下极端误解:

1)索引宁滥勿缺。认为一个查询就需要建一个索引。 2)吝啬索引的创建。认为索引会消耗空间、严重拖慢记录的更新以及行的新增速度。 3)抵制惟一索引。认为惟一索引一律需要在应用层通过“先查后插”方式解决。

# 4.3 SQL 语句

1.【强制】不要使用 count(列名)或 count(常量)来替代 count(),count()是 SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。 说明:count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。

2.【强制】count(distinct col) 计算该列除NULL 之外的不重复行数,注意 count(distinct col1, col2) 如果其中一列全为 NULL,那么即使另一列有不同的值,也返回为 0。

3.【强制】当某一列的值全是 NULL 时,count(col)的返回结果为 0,但 sum(col)的返回结果为NULL,因此使用 sum()时需注意 NPE 问题。 正例:可以使用如下方式来避免 sum 的 NPE 问题:SELECT IFNULL(SUM(column), 0) FROM table;

4.【强制】使用 ISNULL()来判断是否为 NULL 值。 说明:NULL 与任何值的直接比较都为 NULL。 1)NULL<>NULL 的返回结果是 NULL,而不是 false。 2)NULL=NULL 的返回结果是 NULL,而不是 true。 3)NULL<>1 的返回结果是 NULL,而不是 true。 反例:在 SQL 语句中,如果在 null 前换行,影响可读性。select * from table where column1 is null and column3 is not null; 而ISNULL(column)是一个整体,简洁易懂。从性能数据上分析,ISNULL(column) 执行效率更快一些。

5.【强制】代码中写分页查询逻辑时,若 count 为 0 应直接返回,避免执行后面的分页语句。

6.【强制】不得使用外键与级联,一切外键概念必须在应用层解决。 说明:(概念解释)学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。

7.【强制】禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。

8.【强制】数据订正(特别是删除或修改记录操作)时,要先 select,避免出现误删除,确认无误才能执行更新语句。

9.【强制】对于数据库中表记录的查询和变更,只要涉及多个表,都需要在列名前加表的别名(或表名)进行限定。 说明:对多表进行查询记录、更新记录、删除记录时,如果对操作列没有限定表的别名(或表名),并且操作列在多个表中存在时,就会抛异常。 正例:select t1.name from table_first as t1 , table_second as t2 where t1.id=t2.id; 反例:在某业务中,由于多表关联查询语句没有加表的别名(或表名)的限制,正常运行两年后,最近在某个表中增加一个同名字段,在预发布环境做数据库变更后,线上查询语句出现出 1052 异常:Column 'name' in field list is ambiguous。

10.【推荐】SQL 语句中表的别名前加 as,并且以 t1、t2、t3、...的顺序依次命名。 说明:1)别名可以是表的简称,或者是依照表在 SQL 语句中出现的顺序,以 t1、t2、t3 的方式命名。2) 别名前加 as 使别名更容易识别。 正例:select t1.name from table_first as t1, table_second as t2 where t1.id=t2.id;

11.【推荐】in 操作能避免则避免,若实在避免不了,需要仔细评估 in 后边的集合元素数量,控制在 1000 个之内。

12.【参考】因国际化需要,所有的字符存储与表示,均采用 utf8 字符集,那么字符计数方法需要注意说明: SELECT LENGTH("轻松工作"); 返回为 12 SELECT CHARACTER_LENGTH("轻松工作"); 返回为 4 如果需要存储表情,那么选择 utf8mb4 来进行存储,注意它与 utf8 编码的区别。

13.【参考】TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但 TRUNCATE 无事务且不触发 trigger,有可能造成事故,故不建议在开发代码中使用此语句。 说明:TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同。

# 4.4 ORM 映射

1.【强制】在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。说明:1)增加查询分析器解析成本。2)增减字段容易与 resultMap 配置不一致。3)无用字段增加网络消耗,尤其是 text 类型的字段。

2.【强制】POJO 类的布尔属性不能加 is,而数据库字段必须加 is_,要求在 resultMap 中进行字段与属性之间的映射。 说明:参见定义 POJO 类以及数据库字段定义规定,在 sql.xml 增加映射,是必须的。

3.【强制】不要用 resultClass 当返回参数,即使所有类属性名与数据库字段一一对应,也需要定义<resultMap></resultMap>;反过来,每一个表也必然有一个<resultMap></resultMap>与之对应。 说明:配置映射关系,使字段与 DO 类解耦,方便维护。

4.【强制】sql.xml 配置参数使用:#{},#param# 不要使用${} 此种方式容易出现 SQL 注入。

5.【强制】iBATIS 自带的 queryForList(String statementName,int start,int size)不推荐使用。说明:其实现方式是在数据库取到 statementName 对应的 SQL 语句的所有记录,再通过 subList 取start,size 的子集合。 正例: Map<String, Object> map = new HashMap<>(16); map.put("start", start); map.put("size", size);

6.【强制】不允许直接拿 HashMap 与 Hashtable 作为查询结果集的输出。 反例:某同学为避免写一个<resultMap>xxx</resultMap>,直接使用 HashTable 来接收数据库返回结果,结果出现日常是把 bigint 转成 Long 值,而线上由于数据库版本不一样,解析成 BigInteger,导致线上问题。

7.【强制】更新数据表记录时,必须同时更新记录对应的 update_time 字段值为当前时间。

8.【推荐】不要写一个大而全的数据更新接口。传入为 POJO 类,不管是不是自己的目标更新字段,都进行 update table set c1=value1,c2=value2,c3=value3; 这是不对的。执行 SQL 时, 不要更新无改动的字段,一是易出错;二是效率低;三是增加 binlog 存储。

9.【参考】@Transactional 事务不要滥用。事务会影响数据库的 QPS,另外使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。

10.【参考】<isEqual></isEqual>中的 compareValue 是与属性值对比的常量,一般是数字,表示相等时带上此条件;<isNotEmpty></isNotEmpty>表示不为空且不为 null 时执行;<isNotNull></isNotNull>表示不为 null 值时执行。