0%

阿里java规范(泰山版)

手册中编程规约、异常日志、安全规约、MySQL数据库、工程结构几个章节都是目前作为初级开发人员切实要用并且一定要了解的!!

[1] 完全借鉴 Java开发手册(泰山版)

试图挑一些最普遍最强制并且我已经犯过的错误

1 编程规约

1.1 命名风格

  1. 【强制】所有编程相关的命名严禁使用拼音与英文混合的方式。注意,纯拼音命名方式更要避免采用。

    • 理解:命名就最好用英文或英文简写或者国际通用的拼音
  2. 【强制】常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。

    • 理解:常量唯一,所以命名也必须唯一,长一点好
  3. 【强制】抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类的名称开始,以 Test 结尾。

    • 我之前竟然一无所知…阔怕
  4. 【强制】包名统一使用单数小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。

  5. .【强制】杜绝完全不规范的缩写,避免望文不知义。

    • 反例:AbstractClass“缩写”命名成 AbsClass;condition“缩写”命名成 condi,此类随意缩写严重降低了代码的可阅读性。
    • 理解:能不简写就不要简写。多练练敲字,速度提起来,名字长也能敲,还能练英语
  6. 【参考】各层命名规约:

    • Service/DAO 层方法命名规约

      1) 获取单个对象的方法用 get 做前缀。

      2) 获取多个对象的方法用 list 做前缀,复数结尾,如:listObjects。

      3) 获取统计值的方法用 count 做前缀。

      4) 插入的方法用 save/insert 做前缀。

      5) 删除的方法用 remove/delete 做前缀。

      6) 修改的方法用 update 做前缀。

    • 领域模型命名规约

      1) 数据对象:xxxDO,xxx 即为数据表名。

      2) 数据传输对象:xxxDTO,xxx 为业务领域相关的名称。

      3) 展示对象:xxxVO,xxx 一般为网页名称。

      4) POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO。

1.2 常量定义

  1. 【强制】不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。

    • 理解:这个我真的用过…就是说只要是常量 你都要先定义(字符串,某个数值)
  2. 【推荐】不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。

    • 说明:大而全的常量类,杂乱无章,使用查找功能才能定位到修改的常量,不利于理解,也不利于维护。

    • 正例:缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在类 ConfigConsts 下。

    • 理解:常量类的使用,我自己涉及的少,但是项目必备的

  3. 【推荐】如果变量值仅在一个固定范围内变化用 enum 类型来定义。

    • 说明:如果存在名称之外的延伸属性应使用 enum 类型,下面正例中的数字就是延伸信息,表示一年中的第几个季节
    • 这么没看太懂哦,enum平时基本没用过,但是规范代码好像用的挺多的

1.3 代码格式

  1. 【强制】左小括号和右边相邻字符之间不出现空格;右小括号和左边相邻字符之间也不出现空格;而左大括号前需要加空格。详见第 5 条下方正例提示。
    • 正例:if (a == b)
    • 还有一条就是大括号 {} 前后必须加空格或者换行
  2. 【强制】if/for/while/switch/do 等保留字与括号之间都必须加空格。
    • 很常见的点,但之前没注意
  3. 【强制】任何二目、三目运算符的左右两边都需要加一个空格。
    • 说明:包括赋值运算符=、逻辑运算符&&、加减乘除符号等。
  4. 【强制】注释的双斜线与注释内容之间有且仅有一个空格。
  5. 【强制】在进行类型强制转换时,右括号与强制转换值之间不需要任何空格隔开。
  6. 【强制】方法参数在定义和传入时,多个参数逗号后边必须加空格。

1.4 OOP规约

  1. 【强制】避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。

    • 理解:一定要利用静态变量/方法的特性!静态的都需要用类名来访问
  2. 【强制】不能使用过时的类或方法。

    • 接口提供方既然明确是过时接口,那么有义务同时提供新的接口;
    • 作为调用方来说,有义务去考证过时方法的新实现是什么。
  3. 【强制】Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。

    • 正例:”test”.equals(object);
    • 反例:object.equals(“test”);
  4. 【强制】所有整型包装类对象之间值的比较,全部使用 equals 方法比较

    • 说明:虽然说 Integer var = ? 在-128 至 127 之间的赋值对象会复用,但还是推荐用equals方法进行判断
  5. 【强制】浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals来判断。

    • 要么指定一个误差范围比如1e-6f来比较大小
    • 要么用BigDecimal类型来比较大小
  6. 【强制】禁止使用构造方法 BigDecimal(double)的方式把 double 值转化为BigDecimal 对象。

    • 说明:BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。

    • 正例:优先推荐入参为 String 的构造方法,或使用 BigDecimal 的 valueOf 方法,此方法内部其实执行了Double 的 toString,而 Double 的 toString 按 double 的实际能表达的精度对尾数进行了截断。

  7. 关于基本数据类型与包装数据类型的使用标准如下:

    • 【强制】所有的 POJO 类属性必须使用包装数据类型。

    • 【强制】RPC 方法的返回值和参数必须使用包装数据类型。

    • 【推荐】所有的局部变量使用基本数据类型。

    • 理解:数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险(空指针异常)。

    • 理解:POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何 NPE 问题,或

      者入库检查,都由使用者来保证。

  8. 【强制】POJO 类必须写 toString 方法。使用 IDE 中的工具:source> generate toString时,如果继承了另一个 POJO 类,注意在前面加一下 super.toString。

    • 理解:在方法执行抛出异常时,可以直接调用 POJO 的 toString()方法打印其属性值,便于排查问题
  9. 【推荐】 类内方法定义的顺序依次是:公有方法或保护方法 > 私有方法 > getter / setter方法

    • 说明:公有方法是类的调用者和维护者最关心的方法,首屏展示最好;保护方法虽然只是子类关心,也可能是“模板设计模式”下的核心方法;
    • 而私有方法外部一般不需要特别关心,是一个黑盒实现;因为承载的信息价值较低,所有 Service 和 DAO 的 getter/setter 方法放在类体最后。
  10. 【推荐】类成员与方法访问控制从严:

    • 如果不允许外部直接通过 new 来创建对象,那么构造方法必须是 private
    • 工具类不允许有 public 或 default 构造方法。
    • 类 static 成员变量如果仅在本类使用,必须是 private。
    • 若是 static 成员变量,考虑是否为 final。
    • 类成员方法只供类内部调用,必须是 private
    • 类成员方法只对继承类公开,那么限制为 protected。
    • 类非 static 成员变量并且与子类共享,必须是 protected
    • 类非 static 成员变量并且仅在本类使用,必须是 private。
    • 说明:过于宽泛的访问范围,不利于模块解耦。思考:如果是一个 private 的方法,想删除就删除,可是一个 public 的 service 成员方法或成员变量,删除一下,不得手心冒点汗吗?变量像自己的小孩,尽量在自己的视线内,变量作用域太大,无限制的到处跑,那么你会担心的。

1.5 日期时间

  1. 【强制】日期格式化时,传入 pattern 中表示年份统一使用小写的 y。
    • 正例:表示日期和时间的格式如下所示:new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”)
  2. 【强制】获取当前毫秒数:System.currentTimeMillis(); 而不是 new Date().getTime()。
  3. 【强制】不要在程序中写死一年为 365 天,避免在公历闰年时出现日期转换错误或程序逻辑错误。
    • int daysOfThisYear = LocalDate.now().lengthOfYear();
  4. 【推荐】使用枚举值来指代月份。如果使用数字,注意 Date,Calendar 等日期相关类的月份month 取值在 0-11 之间。

1.6 集合处理

  1. 【强制】判断所有集合内部的元素是否为空,使用 isEmpty()方法,而不是 size()==0 的方式。

    • 说明:前者的时间复杂度为 O(1),而且可读性更好。
  2. 【强制】使用 Map 的方法 keySet()/values()/entrySet()返回集合对象时,不可以对其进行添加元素操作,否则会抛出 UnsupportedOperationException 异常。

  3. 【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为 0 的空数组。

    • 反例:直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现ClassCastException 错误。

    • 说明:使用 toArray 带参方法,数组空间大小的 length

      1) 等于 0,动态创建与 size 相同的数组,性能最好。

      2) 大于 0 但小于 size,重新创建大小等于 size 的数组,增加 GC 负担。

      3) 等于 size,在高并发情况下,数组创建完成之后,size 正在变大的情况下,负面影响与 2 相同。

      4) 大于 size,空间浪费,且在 size 处插入 null 值,存在 NPE 隐患。

  4. 14.【强制】不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。

  5. 【推荐】使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。

    • keySet 其实是遍历了 2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出 key 所对应的value。而 entrySet 只是遍历了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8,使用Map.forEach 方法。

1.7 并发处理

  1. 【强制】获取单例对象需要保证线程安全,其中的方法也要保证线程安全。

    • 说明:资源驱动类、工具类、单例工厂类都需要注意。
  2. 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯

  3. . 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。

  4. 【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

  5. 【强制】SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为 static,必须加锁,或者使用 DateUtils 工具类。

  6. 【强制】必须回收自定义的 ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用 try-finally 块进行回收。

  7. 【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。说明:尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用 RPC 方法。

  8. 【强制】对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。

  • 说明:线程一需要对表 A、B、C 依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序也必须是 A、 B、C,否则可能出现死锁。
  1. 【强制】在使用阻塞等待获取锁的方式中,必须在 try 代码块之外,并且在加锁方法与 try 代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在 finally 中无法解锁。
  • 说明一:如果在 lock 方法与 try 代码块之间的方法调用抛出异常,那么无法解锁,造成其它线程无法成功获取锁。

  • 说明二:如果 lock 方法在 try 代码块之内,可能由于其它方法抛出异常,导致在 finally 代码块中,unlock对未加锁的对象解锁,它会调用 AQS 的 tryRelease 方法(取决于具体实现类),抛出IllegalMonitorStateException 异常。

  • 说明三:在 Lock 对象的 lock 方法实现中可能抛出 unchecked 异常,产生的后果与说明二相同。

  1. 【强制】在使用尝试机制来获取锁的方式中,进入业务代码块之前,必须先判断当前线程是否持有锁。锁的释放规则与锁的阻塞等待方式相同。

11.【强制】并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用 version 作为更新依据。

  • 说明:如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于3 次

12.【强制】多线程并行处理定时任务时,Timer 运行多个 TimeTask 时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题。

1.8 控制语句

\1. 【强制】在一个 switch 块内,每个 case 要么通过 continue/break/return 等来终止,要么注释说明程序将继续执行到哪一个 case 为止;在一个 switch 块内,都必须包含一个 default语句并且放在最后,即使它什么代码也没有。

\2. 【强制】当 switch 括号内的变量类型为 String 并且此变量为外部参数时,必须先进行 null判断。

\3. 【强制】在 if/else/for/while/do 语句中必须使用大括号。

  • 说明:即使只有一行代码,禁止不采用大括号的编码方式:if (condition) statements;

\4. 【强制】三目运算符 condition? 表达式 1 : 表达式 2 中,高度注意表达式 1 和 2 在类型对齐时,可能抛出因自动拆箱导致的 NPE 异常。

  • null拆箱就会有NPE异常

\5. 【强制】在高并发场景中,避免使用”等于”判断作为中断或退出的条件。

\9. 【推荐】不要在其它表达式(尤其是条件表达式)中,插入赋值语句。

  • 说明:赋值点类似于人体的穴位,对于代码的理解至关重要,所以赋值语句需要清晰地单独成为一行。

1.9 注释规约

\1. 【强制】类、类属性、类方法的注释必须使用 Javadoc 规范,使用/*内容/格式,不得使用 // xxx 方式。

  • 在 IDE中,工程调用方法时,不进入方法即可悬浮提示方法、参数、返回值的意义,提高阅读效率。

\2. 【强制】所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释、除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。

  • 说明:对子类的实现要求,或者调用注意事项,请一并说明。

\3. 【强制】所有的类都必须添加创建者和创建日期。

  • 说明:在设置模板时,注意 IDEA 的@author 为${USER},而 eclipse 的@author 为${user},大小写有区别,而日期的设置统一为 yyyy/MM/dd 的格式。

\4. 【强制】方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释,注意与代码对齐。

==\5. 【强制】所有的枚举类型字段必须要有注释,说明每个数据项的用途。==

1.10 其他

看不太懂

5 MySQL数据库

5.1 建表规约

\1. 【强制】表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint(1 表示是,0 表示否)。

  • 说明:任何字段如果为非负数,必须是 unsigned。

  • 注意:POJO 类中的任何布尔类型的变量,都不要加 is 前缀,所以,需要在设置从 is_xxx 到Xxx 的映射关系。数据库表示是与否的值,使用 tinyint 类型,坚持 is_xxx 的命名方式是为了明确其取值含义与取值范围。

  • 正例:表达逻辑删除的字段名 is_deleted,1 表示删除,0 表示未删除。

\2. 【强制】表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。

  • 说明:MySQL 在 Windows 下不区分大小写,但在 Linux 下默认是区分大小写。因此,数据库名、表名、字段名,都不允许出现任何大写字母,避免节外生枝。

  • 正例:aliyun_admin,rdc_config,level3_name

\3. 【强制】表名不使用复数名词。

  • 说明:表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于 DO 类名也是单数形式,符合表达习惯。

\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,独立出来一张表,用主键来对应,避免影响其它字段索引效率。

  • 公司的文章表就是单独定义了一个text表,用来存放文本的数据

\9. 【强制】表必备三字段:id, gmt_create, gmt_modified。

  • 说明:其中 id 必为主键,类型为 bigint unsigned、单表时自增、步长为 1。gmt_create, gmt_modified的类型均为 datetime 类型,前者现在时表示主动式创建,后者过去分词表示被动式更新。

  • 格林尼治标准时间 Greenwich mean time (GMT)

13.【推荐】字段允许适当冗余,以提高查询性能,但必须考虑数据一致。冗余字段应遵循:

  • 1) 不是频繁修改的字段。

  • 2) 不是唯一索引的字段。

  • 3) 不是 varchar 超长字段,更不能是 text 字段。

  • 正例:各业务线经常冗余存储商品名称,避免查询时需要调用 IC 服务获取。

14.【推荐】单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。

  • 说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。

5.2 索引规约

\2. 【强制】超过三个表禁止 join。需要 join 的字段,数据类型保持绝对一致;多表关联查询时,保证被关联的字段需要有索引。

  • 说明:即使双表 join 也要注意表索引、SQL 性能。

  • 原因: 这个规则 超过三张表禁止join ,由于数据量太大的时候,mysql根本查询不出来,导致阿里出了这样一个规定。(其实如果表数据量少,10张表也不成问题,你自己可以试试)而我们公司支付系统朝着大规模高并发目标设计的,所以,遵循这个规定。

  • 解决:如果查询确实需要join更多的表,可以把更多逻辑放到应用层(进行拼接处理)。而在业务层面来讲,只写简单sql。

==\3. 【强制】在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度。==

  • 说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分度会高达 90%以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度来确定。

==\4. 【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。==

  • 说明:索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。

\9. 【推荐】建组合索引的时候,区分度最高的在最左边。

  • 正例:如果 where a=? and b=?,a 列的几乎接近于唯一值,那么只需要单建 idx_a 索引即可。

  • 说明:存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如:where c>? and d=? 那么即使 c 的区分度更高,也必须把 d 放在索引的最前列,即建立组合索引 idx_d_c。

5.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 应直接返回,避免执行后面的分页语句。

  • 分页查询举个例子看看?公司推ES数据的时候数据量太大用的就是分页查询
    • 为什么没有limit也可以分页,运行次数一不一样,出来的页面就不一样?
    • 用到了mybatis-plus
  • 如何实现count==0时直接返回?

\6. 【强制】不得使用外键与级联,一切外键概念必须在应用层解决。

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

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

  • 这点好像很难做到啊,随便删一条数据都要先查一下看有没有?

\9. 【强制】对于数据库中表记录的查询和变更,只要涉及多个表,都需要在列名前加表的别名(或表名)进行限定。

  • 说明:对多表进行查询记录、更新记录、删除记录时,如果对操作列没有限定表的别名(或表名),并且操作列在多个表中存在时,就会抛异常。

  • 正例:select t1.name from table_first as t1 , table_second as t2 where t1.id=t2.id;

10.【推荐】SQL 语句中表的别名前加 as,并且以 t1、t2、t3、…的顺序依次命名。

  • 说明:1)别名可以是表的简称,或者是根据表出现的顺序,以 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 个之内。

  • 示例:in常用于where表达式中,其作用是查询某个范围内的数据。
  • 用法:select * from table where stu_name in (value1,value2,value3,…)
  • in后面可以使记录集、字符串、数组等

5.4 ORM映射

(Object Relational Mapping对象关系映射)

\1. 【强制】在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。

  • 说明:1)增加查询分析器解析成本。2)增减字段容易与 resultMap 配置不一致。3)无用字段增加网络消耗,尤其是 text 类型的字段。

\2. 【强制】POJO 类的布尔属性不能加 is,而数据库字段必须加 is_,要求在 resultMap 中进行字段与属性之间的映射。

  • 说明:参见定义 POJO 类以及数据库字段定义规定,在 sql.xml 增加映射,是必须的。
  • 疑问:mybatis-plus注解方式写sql时,如何处理结果集映射

\3. 【强制】不要用 resultClass 当返回参数,即使所有类属性名与数据库字段一一对应,也需要定义;反过来,每一个表也必然有一个与之对应。

  • 说明:配置映射关系,使字段与 DO 类解耦,方便维护。

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

  • 具体情况还是需要区别使用,之前面经有整理

==\5. 【强制】iBATIS 自带的 queryForList(String statementName,int start,int size)不推荐使用。==

\6. 【强制】不允许直接拿 HashMap 与 Hashtable 作为查询结果集的输出。

  • 反例:某同学为避免写一个<resultMap>,直接使用 HashTable 来接收数据库返回结果,结果出现日常是把 bigint 转成 Long 值,而线上由于数据库版本不一样,解析成 BigInteger,导致线上问题。

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

  • 意思应该是要在service层程序员手动更新吧

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

  • 我之前做的功能updateArticle好像就有点这个问题

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

6 工程结构

6.1 应用分层

\1. 【推荐】图中默认上层依赖于下层,箭头关系表示可直接依赖,如:开放接口层可以依赖于Web 层,也可以直接依赖于 Service 层,依此类推:

image-20220223145624048
  • 开放接口层:可直接封装 Service 方法暴露成 RPC 接口;通过 Web 封装成 http 接口;网关控制层等。

    • 可以理解为Controller
  • 终端显示层:各个端的模板渲染并执行显示的层。当前主要是 velocity 渲染,JS 渲染,JSP 渲染,移动端展示等。

    • 可以理解为前端界面?
  • Web 层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。

    • 前后端都有参数校验,前端在页面上,后端一般就在Controller里边吧
    • 这里可以理解为SpringMVC负责的内容?
  • Service 层:相对具体的业务逻辑服务层。

  • Manager 层:通用业务处理层,它有如下特征:

    • 1) 对第三方平台封装的层,预处理返回结果及转化异常信息。
    • 2) 对 Service 层通用能力的下沉,如缓存方案、中间件通用处理。
    • 3) 与 DAO 层交互,对多个 DAO 的组合复用。
    • (实际项目中暂时不太了解这一层)
  • DAO 层:数据访问层,与底层 MySQL、Oracle、Hbase、OB 等进行数据交互。

  • 外部接口或第三方平台:包括其它部门 RPC 开放接口,基础平台,其它公司的 HTTP 接口。

\2. 【参考】(分层异常处理规约)

  • 在 DAO 层,产生的异常类型有很多,无法用细粒度的异常进行 catch,使用 catch(Exception e)方式,并 throw new DAOException(e),不需要打印日志,因为日志在 Manager/Service 层一定需要捕获并打印到日志文件中去,如果同台服务器再打日志,浪费性能和存储。
  • 在 Service 层出现异常时,必须记录出错日志到磁盘,尽可能带上参数信息,相当于保护案发现场。
  • Manager 层与 Service 同机部署,日志方式与 DAO 层处理一致,如果是单独部署,则采用与 Service 一致的处理方式。
  • Web 层绝不应该继续往上抛异常,因为已经处于顶层,如果意识到这个异常将导致页面无法正常渲染,那么就应该直接跳转到友好错误页面,尽量加上友好的错误提示信息。
  • 开放接口层要将异常处理成错误码和错误信息方式返回。

\3. 【参考】分层领域模型规约:

  • DO(Data Object):此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。 (entity,pojo)
  • DTO(Data Transfer Object):数据传输对象,Service 或 Manager 向外传输的对象。
  • BO(Business Object):业务对象,可以由 Service 层输出的封装业务逻辑的对象。
  • Query:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止使用 Map 类来传输。
  • VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。

分层模型详细介绍

参考了https://segmentfault.com/a/1190000021701718

  • DTO (Data Transger Object) 为数据传输对象,通常将底层的数据聚合传给外部系统,它通常用作 Service 和 Manager 层向上层返回的对象。需要注意的是:如果作为分布式服务的参数或返回对象,通常要实现序列化接口。

  • Query:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止使用 Map 类来传输。

  • Param 为查询参数对象,适用于各层,通常用作接受前端参数对象。(与Query对应)

  • BO (Bussiness Object) 即业务对象。该对象中通常包含业务逻辑。此对象在实际使用中有不同的理解,有的团队采用领域驱动设计,BO 含有属性和方法(具体可参考领域驱动设计的相关图书);有的团队将 BO 当做 Service 返回给上层的 “专用 DTO” 使用;而有的团队则当做 Service 层内保存中间信息数据的 “DTO” 或者上下文对象来使用(本文采用这种理解)。比如 BO 中可以保存中间状态,放一些逻辑等,这些并不适合放在 DTO。

  • VO (View Object) 为视图对象,通常作为控制层通过 JSON 返回给前端然后前端渲染或者加载页面模板在后端进行填充。

  • AO (Application Object) 应用对象。通常用在控制层和服务层之间。有些团队会将前端查询的属性和保存的属性几乎一致的对象封装为 AO,如读取用户属性传给前端,用户在前端编辑了用户属性后传回后端。这种用法将 AO 用作 Param 和 VO 或 Param 和 DTO 的组合。

查询视图:

2783533085-5e3d28879e23a

返回试图:

2172307841-5e3d285cad5c0

为什么要使用Param类和Query类来处理传递参数对象和数据查询对象?

  • Param类和Query类都用来传递参数。查询参数一般使用 Query,删除参数一般使用 Param。Param 和 Query 的出现是为了避免使用 Map 作为接收参数的对象。

  • 如果我们不使用 Query 对象而是 Map 对象来封装 DAO 的参数,设置和获取的 key 很可能因为粗心导致设置和获取时的 key 不一致而出现 BUG。

  • 如果我们不愿意定义 Param 对象,使用 Map 来接收前端的参数,获取时如果采用 JSON 反序列化,则可能出现反序列化类型丢失问题。

  • 结论:前端传参过来最后用一个XxxParam对象或者用一个XxxQuery对象封装起来(都不推荐用map类来传输)那mybatis中所谓的参数映射的意义是什么?

6.3 服务器

\1. 【推荐】高并发服务器建议调小 TCP 协议的 time_wait 超时时间。

  • 说明:操作系统默认 240 秒后,才会关闭处于 time_wait 状态的连接,在高并发访问下,服务器端会因为处于 time_wait 的连接数太多,可能无法建立新的连接,所以需要在服务器上调小此等待值。
  • 正例:在 linux 服务器上请通过变更/etc/sysctl.conf 文件去修改该缺省值(秒):net.ipv4.tcp_fin_timeout = 30

\3. 【推荐】给 JVM 环境参数设置-XX:+HeapDumpOnOutOfMemoryError 参数,让 JVM 碰到 OOM场景时输出 dump 信息。

  • 说明:OOM 的发生是有概率的,甚至相隔数月才出现一例,出错时的堆内信息对解决问题非常有帮助。

\4. 【推荐】在线上生产环境,JVM 的 Xms 和 Xmx 设置一样大小的内存容量,避免在 GC 后调整堆大小带来的压力。

  • 后面仔细看看,现在不明白

\5. 【参考】服务器内部重定向必须使用 forward;外部重定向地址必须使用 URL Broker 生成,否 则因线上采用 HTTPS 协议而导致浏览器提示“不安全“。此外,还会带来 URL 维护不一致的问题。

  • 平时访问一下网站就会出现这样的情况

7 设计规约

架构层面的理解

设计模式方面的知识用到的颇多、、

-------------感谢阅读没事常来-------------