前言

《Java开发手册》是阿里巴巴集团技术团队的集体智慧结晶和经验总结,经历了多次大规模一线实战的检验及不断完善,公开到业界后,众多社区开发者踊跃参与,共同打磨完善,系统化地整理成册。现代软件行业的高速发展对开发者的综合素质要求越来越高,因为不仅是编程知识点,其它维度的知识点也会影响到软件的最终交付质量。比如:数据库的表结构和索引设计缺陷可能带来软件上的架构缺陷或性能风险;工程结构混乱导致后续维护艰难;没有鉴权的漏洞代码易被黑客攻击等等。所以本手册以Java开发者为中心视角,划分为编程规约、异常日志、单元测试、安全规约、MySQL数据库、工程结构、设计规约七个维度,再根据内容特征,细分成若干二级子目录。另外,依据约束力强弱及故障敏感性,规约依次分为强制、推荐、参考三大类。在延伸信息中,说明”对规约做了适当扩展和解释;“正例”提倡什么样的编码和实现方式;“反例”说明需要提防的雷区,以及真实的错误案例。

       手册的愿景是码出高效,码出质量。现代软件架构的复杂性需要协同开发完成,如何高效地协同呢?无规矩不成方圆,无规范难以协同,比如,制订交通法规表面上是要限制行车权,实际上是保障公众的人身安全,试想如果没有限速,没有红绿灯,谁还敢上路行驶?对软件来说,适当的规范和标准绝不是消灭代码内容的创造性、优雅性,而是限制过度个性化,以一种普遍认可的统一方式一起做事,提升协作效率,降低沟通成本。代码的字里行间流淌的是软件系统的血液,质量的提升是尽可能少踩坑,杜绝踩重复的坑,切实提升系统稳定性,码出质量。

这些规范你遵守了么?

一、编程规约

(一)命名规范

2.【强制】代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。

  • 反例:DaZhePromotion[打折]/getPingfenByName()[评分]

8.【强制】POJO类中布尔类型变量都不要加is前缀,否则部分框架解析会引起序列化错误。

  • 反例:Boolean isDeleted;

12.【推荐】为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词组合来表达其意。

  • 反例:inta的随意命名方式。

13.【推荐】在常量与变量的命名时,表示类型的名词放在词尾,以提升辨识度。

  • 正例:startTime /workQueue /nameList /TERMINATED_THREAD_COUNT
  • 反例:startedAt /QueueOfWork /listName /COUNT_TERMINATED_THREAD

15.【推荐】接口类中的方法和属性不要加任何修饰符号(public也不要加),保持代码的简洁性,并加上有效的Javadoc注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。

  • 正例:接口方法签名voidcommit(); 接口基础常量StringCOMPANY="alibaba";
  • 反例:接口方法定义publicabstractvoidf();

(二) 常量定义

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

  • 反例:String key = "Id#taobao_" + tradeId;

2. 【强制】在 long 或者 Long 赋值时,数值后使用大写的 L,不能是小写的 l,小写容易跟数 字 1 混淆,造成误解。

  • 说明:Long a = 2l; 写的是数字的 21,还是 Long 型的 2。

3. 【推荐】不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。

(三) 代码格式

2. 【强制】左小括号和字符之间不出现空格;同样,右小括号和字符之间也不出现空格;而左 大括号前需要空格。

  • 反例:if (空格 a == b 空格)

3. 【强制】if/for/while/switch/do 等保留字与括号之间都必须加空格。

4. 【强制】任何二目、三目运算符的左右两边都需要加一个空格。

  • 说明:运算符包括赋值运算符=、逻辑运算符&&、加减乘除符号等。

5. 【强制】采用 4 个空格缩进,禁止使用 tab 字符。

6. 【强制】注释的双斜线与注释内容之间有且仅有一个空格。

  • 正例: // 这是示例注释,请注意在双斜线之后有一个空格

8. 【强制】单行字符数限制不超过 120 个,超出需要换行,换行时遵循如下原则:

  • 1)第二行相对第一行缩进 4 个空格,从第三行开始,不再继续缩进,参考示例。
  • 2)运算符与下文一起换行。
  • 3)方法调用的点符号与下文一起换行。
  • 4)方法调用中的多个参数需要换行时,在逗号后进行。
  • 5)在括号前不要换行,如sb.append("Jack").append("Ma")...append
    ("alibaba"); // 超过 120 个字符的情况下,不要在括号前换行

9. 【强制】方法参数在定义和传入时,多个参数逗号后边必须加空格。

  • 正例:method(args1, args2, args3);

(四) OOP 规约

3. 【强制】相同参数类型,相同业务含义,才可以使用 Java 的可变参数,避免使用 Object。

  • 说明:可变参数必须放置在参数列表的最后。(提倡同学们尽量不用可变参数编程)

5. 【强制】不能使用过时的类或方法。

6. 【强制】Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。

  • 正例:"test".equals(object);
  • 反例:object.equals("test");

7. 【强制】所有整型包装类对象之间值的比较,全部使用 equals 方法比较。

8. 【强制】浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。

  • 反例:2019072918403133.png
  • 正例:20190729184153858.png

10.【强制】为了防止精度损失,禁止使用构造方法 BigDecimal(double)的方式把 double 值转 化为 BigDecimal 对象。

  • 说明:BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。
  • 正例:优先推荐入参为 String 的构造方法,或使用 BigDecimal 的 valueOf 方法,此方法内部其实执行了Double 的 toString,而 Double 的 toString 按 double 的实际能表达的精度对尾数进行了截断。           
    BigDecimal recommend1 = new BigDecimal("0.1");                             

BigDecimal recommend2 = BigDecimal.valueOf(0.1);

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

  • 1) 【强制】所有的 POJO 类属性必须使用包装数据类型。
  • 2) 【强制】RPC 方法的返回值和参数必须使用包装数据类型。
  • 3) 【推荐】所有的局部变量使用基本数据类型。

12.【强制】定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值。

  • 反例:POJO 类的 createTime 默认值为 new Date(),但是这个属性在数据提取时并没有置入具体值,在 更新其它字段时又附带更新了此字段,导致创建时间被修改成当前时间。

14.【强制】构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中。

16.【强制】禁止在 POJO 类中,同时存在对应属性 xxx 的 isXxx()和 getXxx()方法。

17.【推荐】使用索引访问用 String 的 split 方法得到的数组时,需做最后一个分隔符后有无内 容的检查,否则会有抛 IndexOutOfBoundsException 的风险。

  • 说明:20190729185824357.png

20.【推荐】setter 方法中,参数名称与类成员变量名称一致,this.成员名 = 参数名。在 getter/setter 方法中,不要增加业务逻辑,增加排查问题的难度。

21.【推荐】循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展。

  • 说明:下例中,反编译出的字节码文件显示每次循环都会 new 出一个 StringBuilder 对象,然后进行 append 操作,最后通过 toString 方法返回 String 对象,造成内存资源浪费。
  • 反例:20190730093852715.png

24.【推荐】类成员与方法访问控制从严:

  • 1) 如果不允许外部直接通过 new 来创建对象,那么构造方法必须是 private。
  • 2) 工具类不允许有 public 或 default 构造方法。
  • 3) 类非 static 成员变量并且与子类共享,必须是 protected。
  • 4) 类非 static 成员变量并且仅在本类使用,必须是 private。
  • 5) 类 static 成员变量如果仅在本类使用,必须是 private。
  • 6) 若是 static 成员变量,考虑是否为 final。
  • 7) 类成员方法只供类内部调用,必须是 private。
  • 8) 类成员方法只对继承类公开,那么限制为 protected。
  • 说明:任何类、方法、参数、变量,严控访问范围。过于宽泛的访问范围,不利于模块解耦。

(五) 集合处理

1. 【强制】关于 hashCode 和 equals 的处理,遵循如下规则:

  • 1) 只要覆写 equals,就必须覆写 hashCode。
  • 2) 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须覆写这两个方法。
  • 3) 如果自定义对象作为 Map 的键,那么必须覆写 hashCode 和 equals。
  • 说明:String 已覆写 hashCode 和 equals 方法,所以我们可以愉快地使用 String 对象作为 key 来使用。

3. 【强制】使用 Map 的方法 keySet()/values()/entrySet()返回集合对象时,不可以对其进行添 加元素操作,否则会抛出 UnsupportedOperationException 异常。

array),传入的是类型完全一 致、长度为 0 的空数组。" show="true"]

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

7. 【强制】在使用 Collection 接口任何实现类的 addAll()方法时,都要对输入的集合参数进行 NPE 判断。

  • 说明:在 ArrayList#addAll 方法的第一行代码即 Object[] a = c.toArray(); 其中 c 为输入集合参数,如果 为 null,则直接抛出异常。

8. 【强制】使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方 法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。

  • 说明:asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList 体现的是适 配器模式,只是转换接口,后台的数据仍是数组。

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

  • 反例:20190730095157891.png

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

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

16.【推荐】高度注意 Map 类集合 K/V 能不能存储 null 值的情况,如下表格:

集合类KeyValueSuper说明
Hashtable不允许为 null不允许为 nullDictionary线程安全
ConcurrentHashMap不允许为 null不允许为 nullAbstractMap锁分段技术(JDK8:CAS)
TreeMap不允许为 null允许为 nullAbstractMap线程不安全
HashMap允许为 null允许为 nullAbstractMap线程不安全

(六) 并发处理

2. 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。

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

  • 说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问 题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

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

  • 说明:Executors 返回的线程池对象的弊端如下:
  • 1) FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
  • 2) CachedThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

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

  • 说明:如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar, DateTimeFormatter 代替 SimpleDateFormat,官方给出的解释:simple beautiful strong immutable thread-safe。

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

7. 【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁; 能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。

  • 说明:尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用 RPC 方法。

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

  • 说明:线程一需要对表 A、B、C 依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序也必须是 A、B、C,否则可能出现死锁。

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

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

13.【推荐】资金相关的金融敏感信息,使用悲观锁策略。

17.【参考】volatile 解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但 是如果多写,同样无法解决线程安全问题。

  • 说明:如果是 count++操作,使用如下类实现:AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观 锁的重试次数)。

18.【参考】HashMap 在容量不够进行 resize 时由于高并发可能出现死链,导致 CPU 飙升,在 开发过程中可以使用其它数据结构或加锁来规避此风险。

(七) 控制语句

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

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

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

  • 说明:如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,使用大于或小于的区间判断条件 来代替。
  • 反例:判断剩余奖品数量等于 0 时,终止发放奖品,但因为并发处理错误导致奖品数量瞬间变成了负数, 这样的话,活动无法终止。

5. 【推荐】表达异常的分支时,少用 if-else 方式,这种方式可以改写成:

20190730105537731.png

  • 说明:如果非使用 if()...else if()...else...方式表达逻辑,避免后续代码维护困难,【强制】请勿超过 3 层。

6. 【推荐】除常用方法(如 getXxx/isXxx)等外,不要在条件判断中执行其它复杂的语句,将复 杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。

  • 说明:很多 if 语句内的逻辑表达式相当复杂,与、或、取反混合运算,甚至各种方法纵深调用,理解成 本非常高。如果赋值一个非常好理解的布尔变量名字,则是件令人爽心悦目的事情。
  • 正例:
// 伪代码如下
final boolean existed = (file.open(fileName, "w") != null) && (...) || (...);
if (existed) {
...
}
  • 反例:
public final void acquire(long arg) {
if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
    selfInterrupt();
} }

9. 【推荐】避免采用取反逻辑运算符。

  • 说明:取反逻辑不利于快速理解,并且取反逻辑写法必然存在对应的正向逻辑写法。
  • 正例:使用 if (x < 628) 来表达 x 小于 628。
  • 反例:使用 if (!(x >= 628)) 来表达 x 小于 628。

(八) 注释规约

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

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

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

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

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

8. 【参考】谨慎注释掉代码。在上方详细说明,而不是简单地注释掉。如果无用,则删除。

  • 说明:代码被注释掉有两种可能性:1)后续会恢复此段代码逻辑。2)永久不用。前者如果没有备注信 息,难以知晓注释动机。后者建议直接删掉(代码仓库已然保存了历史代码)。

(九) 其它

1. 【强制】在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。

  • 说明:不要在方法体内定义:Pattern pattern = Pattern.compile(“规则”);

5. 【强制】获取当前毫秒数 System.currentTimeMillis(); 而不是 new Date().getTime();

  • 说明:如果想获取更加精确的纳秒级时间值,使用 System.nanoTime()的方式。在 JDK8 中,针对统计时 间等场景,推荐使用 Instant 类。

二、异常日志

(一) 异常处理

2. 【强制】异常不要用来做流程控制,条件控制。

  • 说明:异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多。

4. 【强制】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它, 请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理 解的内容。

7. 【强制】不要在 finally 块中使用 return。

  • 说明:try 块中的 return 语句执行成功后,并不马上返回,而是继续执行 finally 块中的语句,如果此处存 在 return 语句,则在此直接返回,无情丢弃掉 try 块中的返回点。

11.【推荐】防止 NPE,是程序员的基本修养,注意 NPE 产生的场景:

  • 1) 返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。                                                  反例:public int f() { return Integer 对象}, 如果为 null,自动解箱抛 NPE。
  • 2) 数据库的查询结果可能为 null。
  • 3) 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。
  • 4) 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。
  • 5) 对于 Session 中获取的数据,建议进行 NPE 检查,避免空指针。
  • 6) 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。   - 正例:使用 JDK8 的 Optional 类来防止 NPE 问题。

(二) 日志规约

1. 【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架 SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。

20190730111252342.png

4. 【强制】在日志输出时,字符串变量之间的拼接使用占位符的方式。

  • 说明:因为 String 字符串的拼接会使用 StringBuilder 的 append()方式,有一定的性能损耗。使用占位符 仅是替换动作,可以有效提升性能。
  • 正例:logger.debug("Processing trade with id: {} and symbol: {}", id, symbol);

6. 【强制】避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。

三、单元测试

2. 【强制】单元测试应该是全自动执行的,并且非交互式的。测试用例通常是被定期执行的, 执行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。 单元测试中不准使用 System.out 来进行人肉验证,必须使用 assert 来验证。

3. 【强制】保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试用例之 间决不能互相调用,也不能依赖执行的先后次序。

  • 反例:method2 需要依赖 method1 的执行,将执行结果作为 method2 的输入。

7. 【强制】单元测试代码必须写在如下工程目录:src/test/java,不允许写在业务代码目录下。

  • 说明:源码编译时会跳过此目录,而单元测试框架默认是扫描此目录。

10.【推荐】对于数据库相关的查询,更新,删除等操作,不能假设数据库里的数据是存在的, 或者直接操作数据库把数据插入进去,请使用程序插入或者导入数据的方式来准备数据。

  • 反例:删除某一行数据的单元测试,在数据库中,先直接手动增加一行作为删除目标,但是这一行新增数 据并不符合业务插入规则,导致测试结果异常。

11.【推荐】和数据库相关的单元测试,可以设定自动回滚机制,不给数据库造成脏数据。或者 对单元测试产生的数据有明确的前后缀标识

四、安全规约

1. 【强制】隶属于用户个人的页面或者功能必须进行权限控制校验。

  • 说明:防止没有做水平权限校验就可随意访问、修改、删除别人的数据,比如查看他人的私信内容、修改 他人的订单。

2. 【强制】用户敏感数据禁止直接展示,必须对展示数据进行脱敏。

  • 说明:中国大陆个人手机号码显示为:137**0969,隐藏中间 4 位,防止隐私泄露。

4. 【强制】用户请求传入的任何参数必须做有效性验证。

8. 【推荐】发贴、评论、发送即时消息等用户生成内容的场景必须实现防刷、文本内容违禁词 过滤等风控策略。

五、MySQL 数据库

(一) 建表规约

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

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

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

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

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 类型。

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

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

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

(二) 索引规约

1. 【强制】业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。

  • 说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外, 即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。

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

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

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

5. 【推荐】如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合 索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。

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

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

7. 【推荐】利用延迟关联或者子查询优化超多分页场景。

  • 说明:MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写。
  • 正例:先快速定位需要获取的 id 段,然后再关联:                             SELECT a.* FROM 表 1 a, (select id from 表 1 where 条件 LIMIT 100000,20 ) b where a.id=b.id

(三) 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 值。

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

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

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

(四) ORM 映射

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

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

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

  • 说明:参见定义 POJO 类以及数据库字段定义规定,在<resultMap>中增加映射,是必须的。在 MyBatis Generator 生成的代码中,需要进行对应的修改。

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

  • 说明:resultClass=”Hashtable”,会置入字段名和属性值,但是值的类型不可控。

观阅原文件可免费下载java开发手册(阿里巴巴)


版权声明:文章转载请注明来源,如有侵权请联系博主删除!
最后修改:2019 年 12 月 25 日 11 : 53 AM
如果觉得我的文章对你有用,请随意赞赏