阿里巴巴Java开发手册笔记

Posted by 麦子 on Tuesday, 2020年05月26日

[TOC]

编程规范

命名风格

[强制] 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。 正例:MAX_STOCK_COUNT 反例:MAX_COUNT

[强制]抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类的名称开始,以 Test 结尾。

[强制]中括号是数组类型的一部分,数组定义如下:

String[] a

反例:

使用 String args[]的方式来定义

[强制]POJO 类中布尔类型的变量,都不要加 is,否则部分框架解析会引起序列化错误。

反例:

定义为基本数据类型 Boolean isDeleted;的属性,它的方法也是 isDeleted(),RPC框架在反向解析的时候,“以为”对应的属性名称是 deleted,导致属性获取不到,进而抛出异常。

[推荐]如果模块、接口、类、方法使用了设计模式,在命名时体现出具 说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。

正例:

public class OrderFactory;

public class LoginProxy;

public class ResourceObserver;

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

正例:

接口方法签名void f();
接口基础常量表示String COMPANY = "alibaba";

反例:

接口方法定义:

public abstract void f(); 

说明:JDK8 中接口允许有默认实现,那么这个 default 方法,是对所有实现类都有价值的默 认实现。

接口和实现类的命名有两套规则

[强制]对于 Service 和 DAO 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部 的实现类用 Impl 的后缀与接口区别。

正例:

CacheServiceImpl 实现 CacheService 接口

[推荐] 如果是形容能力的接口名称,取对应的形容词做接口名(通常是–able 的形式)

正例:

AbstractTranslator 实现 Translatable

[参考]枚举类名建议带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。

说明:枚举其实就是特殊的常量类,且构造方法被默认强制是私有。

正例:

枚举名字为 ProcessStatusEnum 的成员名称SUCCESS / UNKNOWN_REASON

[参考]各层命名规约:

A) Service/DAO 层方法命名规约

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

2 获取多个对象的方法用 list 做前缀 

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

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

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

6 修改的方法用 update 做前缀

B) 领域模型命名规约

1 数据对象xxxDOxxx 即为数据表名

2 数据传输对象xxxDTOxxx 为业务领域相关的名称 

3 展示对象xxxVOxxx 一般为网页名称 

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

常量定义

[强制]不允许任何魔法值(即未经定义的常量)直接出现在代码中。

反例:

String key = "Id#taobao_" + tradeId;
cache.put(key, value);

[强制]long 或者 Long 初始赋值时,使用大写的 L,不能是小写的 l,小写容易跟数字 1 混 淆,造成误解。

说明:

Long a = 2l; 写的是数字的 21还是 Long 型的 2?

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

说明:

大而全的常量类非得使用查找功能才能定位到修改的常量不利于理解和维护

正例:

缓存相关常量放在类 CacheConsts 下系统配置相关常量放在类 ConfigConsts 下

[推荐]如果变量值仅在一个范围内变化,且带有名称之外的延伸属性,定义为枚举类。

下面 正例中的数字就是延伸信息,表示星期几。

正例:

public Enum { 
         MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6),SUNDAY(7);
}

代码格式

[强制] 左小括号和字符之间不出现空格;同样,右小括号和字符之间也不出现空格。

反例:

if (空格a == b空格)

[强制一个空格。

正例:

// 注释内容,注意在和注释内容之间有一个空格。//

OOP 规约

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

[强制]所有的覆写方法,必须加@Override 注解。

说明:

getObject()get0bject()的问题一个是字母的 O一个是数字的 0@Override可以准确判断是否覆盖成功另外如果在抽象类中对方法签名进行修改其实现类会马上编 译报错

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

说明:

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

正例:

public User getUsers(String type, Integer... ids) {...}

[强制]所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。

说明:

对于 Integer var = ?-128 至 127 范围内的赋值Integer 对象是在IntegerCache.cache 产生会复用已有对象这个区间内的 Integer 值可以直接使用==进行判断但是这个区间之外的所有数据都会在堆上产生并不会复用已有对象这是一个大坑 推荐使用 equals 方法进行判断

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

1) [强制]所有的 POJO 类属性必须使用包装数据类型。

2) [强制]RPC 方法的返回值和参数必须使用包装数据类型。

3) [推荐]所有的局部变量使用基本数据类型。

说明:

POJO 类属性没有初值是提醒使用者在需要使用时必须自己显式地进行赋值任何 NPE 问题或者入库检查都由使用者来保证 正例数据库的查询结果可能是 null因为自动拆箱用基本数据类型接收有 NPE 风险 反例比如显示成交总额涨跌情况即正负 x%x 为基本数据类型调用的 RPC 服务调用 不成功时返回的是默认值页面显示为 0%这是不合理的应该显示成中划线所以包装 数据类型的 null能够表示额外的信息远程调用失败异常退出

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

反例:

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

[强制]序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列失败;

如果完全不兼容升级避免反序列化混乱那么请修改 serialVersionUID 值 说明注意 serialVersionUID 不一致会抛出序列化运行时异常

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

[强制]POJO 类必须写 toString 方法。

使用 IDE 的中工具source> generate toString时如果继承了另一个 POJO 类注意在前面加一下 super.toString 说明在方法执行抛出异常时可以直接调用 POJO 的 toString()方法打印其属性值便于排 查问题

[推荐]setter 方法中,参数名称与类成员变量名称一致,this.成员名 = 参数名

在getter/setter 方法中,不要增加业务逻辑,增加排查问题的难度。

反例:

  public Integer getData() {

        if (true) {
            return this.data + 100;

        } else {
            return this.data - 100;
        }
    }

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

说明:

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

反例:

String str = "start";
for (int i = 0; i < 100; i++) {
    str = str + "hello";
}

[推荐]final可以声明类、成员变量、方法、以及本地变量,下列情况使用final关键字:

1) 如果不允许外部直接通过 new 来创建对象,那么构造方法必须是 private。
2) 工具类不允许有 public 或 default 构造方法。
3) 类非 static 成员变量并且与子类共享,必须是 protected。
4) 类非 static 成员变量并且仅在本类使用,必须是 private。
5) 类 static 成员变量如果仅在本类使用,必须是 private。
6) 若是 static 成员变量,必须考虑是否为 final。
7) 类成员方法只供类内部调用,必须是 private。
8) 类成员方法只对继承类公开,那么限制为 protected。

说明:任何类、方法、参数、变量,严控访问范围。过于宽泛的访问范围,不利于模块解耦。

思考:如果是一个 private 的方法,想删除就删除,可是一个 public 的 service 方法,或者 一个 public 的成员变量,删除一下,不得手心冒点汗吗?变量像自己的小孩,尽量在自己的 视线内,变量作用域太大,无限制的到处跑,那么你会担心的。

[推荐]类成员与方法访问控制从严

1) 如果不允许外部直接通过 new 来创建对象,那么构造方法必须是 private。
2) 工具类不允许有 public 或 default 构造方法。
3) 类非 static 成员变量并且与子类共享,必须是 protected。
4) 类非 static 成员变量并且仅在本类使用,必须是 private。
5) 类 static 成员变量如果仅在本类使用,必须是 private。
6) 若是 static 成员变量,必须考虑是否为 final。
7) 类成员方法只供类内部调用,必须是 private。
8) 类成员方法只对继承类公开,那么限制为 protected。

说明:

任何类、方法、参数、变量,严控访问范围。过于宽泛的访问范围,不利于模块解耦。 思考:如果是一个 private 的方法,想删除就删除,可是一个 public 的 service 方法,或者 一个 public 的成员变量,删除一下,不得手心冒点汗吗?变量像自己的小孩,尽量在自己的 视线内,变量作用域太大,无限制的到处跑,那么你会担心的。

集合处理

[强制]关于 hashCode 和 equals 的处理,遵循如下规则:

1 只要重写 equals就必须重写 hashCode 
2 因为 Set 存储的是不重复的对象依据 hashCode 和 equals 进行判断所以 Set 存储的对象必须重写这两个方法
3 如果自定义对象做为 Map 的键那么必须重写 hashCode 和 equals

说明:

String 重写了 hashCode 和 equals 方法,所以我们可以非常愉快地使用 String 对象作为 key 来使用。

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

说明:

asList 的返回对象是一个 Arrays 内部类并没有实现集合的修改方法Arrays.asList体现的是适配器模式只是转换接口后台的数据仍是数组
String[] str = new String[] { "you", "wu" }; 
List list = Arrays.asList(str); 
第一种情况list.add("yangguanbao"); 运行时异常
第二种情况str[0] = "gujin"; 那么 list.get(0)也会随之修改

[强制]泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用 add 方 法,而<? super T>不能使用 get 方法,做为接口调用赋值时易出错。

说明:

扩展说一下 PECS(Producer Extends Consumer Super)原则第一频繁往外读取内容的适合用<? extends T>第二经常往里插入的适合用<? super T>

[强制]不要在 foreach 循环里进行元素的 remove/add 操作。

remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。

正例:

  Iterator<String> iterator = list.iterator();

        while (iterator.hasNext()) {

            String item = iterator.next();

            if (删除元素的条件) {

                iterator.remove();

            }

        }

反例:

List<String> list = new ArrayList<String>();

    list.add("1");

    list.add("2");

    for (String item : list) {

    if ("1".equals(item)) {

    list.remove(item);

    }

}

说明:以上代码的执行结果肯定会出乎大家的意料,那么试一下把“1”换成“2”,会是同样的 结果吗?

[推荐]集合初始化时,指定集合初始值大小。

说明:

HashMap 使用 HashMap(int initialCapacity) 初始化

正例:

initialCapacity=(需要存储的元素个数 / 负载因子) + 1。注意负载因子(即 loaderfactor)默认为0.75,如果暂时无法确定初始值大小,请设置为16(即默认值)。

反例:

HashMap需要放置1024个元素由于没有设置容量初始大小随着元素不断增加容量 7 次被迫扩大resize需要重建hash表严重影响性能

[推荐]使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。

说明:

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

正例:

values()返回的是 V 值集合是一个 list 集合对象keySet()返回的是 K 值集合是一个 Set 集合对象entrySet()返回的是 K-V 值组合集合

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

Xnip2020-05-26_13-53-28

反例:

由于 HashMap 的干扰很多人认为 ConcurrentHashMap 是可以置入 null而事实上 存储 null 值时会抛出 NPE 异常

并发处理

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

说明:

资源驱动类工具类单例工厂类都需要注意

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

说明:

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

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

说明:

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

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

正例:

注意线程安全使用 DateUtils亦推荐如下处理

  private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {

        @Override

        protected DateFormat initialValue() {

            return new SimpleDateFormat("yyyy-MM-dd");

        }

    };

说明:

如果是 JDK8 的应用可以使用 Instant 代替 DateLocalDateTime 代替 Calendar DateTimeFormatter 代替 SimpleDateFormat官方给出的解释simple beautiful strong immutable thread-safe

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

说明:

尽可能使加锁的代码块工作量尽可能的小避免在锁代码块中调用 RPC 方法

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

说明:

线程一需要对表 ABC 依次全部加锁后才可以进行更新操作那么线程二的加锁顺序 也必须是 ABC否则可能出现死锁

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

说明:

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

[推荐]使用 CountDownLatch 进行异步转同步操作,每个线程退出前必须调用 countDown方法,线程执行代码注意 catch 异常,确保 countDown 方法被执行到,避免主线程无法执行 至 await 方法,直到超时才返回结果。

说明:

注意子线程抛出异常堆栈不能在主线程 try-catch

[推荐]避免 Random 实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed 导致的性能下降。

说明:

Random 实例包括 java.util.Random 的实例或者 Math.random()的方式 

正例:

在 JDK7 之后可以直接使用 API ThreadLocalRandom而在 JDK7 之前需要编码保证每个线程持有一个实例

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

如果是 count++操作,使用如下类实现:

AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 

如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)。

[参考]ThreadLocal 无法解决共享对象的更新问题,ThreadLocal 对象建议使用 static修饰。这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享 此静态变量 ,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只 要是这个线程内定义的)都可以操控这个变量。

注释规约

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

说明:

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

[强制]所有的类都必须添加创建者和创建日期。

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

其它

[强制]velocity 调用 POJO 类的属性时,建议直接使用属性名取值即可,模板引擎会自动按 规范调用 POJO 的 getXxx(),如果是 boolean 基本数据类型变量(boolean 命名不需要加 is前缀),会自动调用 isXxx()方法。

说明:

注意如果是 Boolean 包装类对象优先调用 getXxx()的方法

[强制]获取当前毫秒数 System.currentTimeMillis(); 而不是 new Date().get

说明:

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

[推荐]任何数据结构的构造或初始化,都应指定大小,避免数据结构无限增长吃光内存。

异常日志

异常处理

[强制]异常不要用来做流程控制,条件控制,因为异常的处理效率比条件分支低。

[强制]对大段代码进行 try-catch,这是不负责任的表现。catch 时请分清稳定代码和非稳 定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的 catch 尽可能进行区分 异常类型,再做对应的异常处理。

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

[强制]有 try 块放到了事务代码中,catch 异常后,如果需要回滚事务,一定要注意手动回 滚事务。

[强制]不能在 finally 块中使用 return,finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句。

[参考]在代码中使用“抛异常”还是“返回错误码”,对于公司外的 http/api 开放接口必须 使用“错误码”;而应用内部推荐异常抛出;跨应用间 RPC 调用优先考虑使用 Result 方式,封 装 isSuccess()方法、“错误码”、“错误简短信息”。

说明:

关于 RPC 方法返回方式使用 Result 方式的理由
1使用抛异常返回方式调用方如果没有捕获到就会产生运行时错误
2如果不加栈信息只是 new 自定义异常加入自己的理解的 error message对于调用 端解决问题的帮助不会太多如  果加了栈信息在频繁调用出错的情况下数据序列化和传输 的性能损耗也是问题

[参考]避免出现重复的代码(Don’t Repeat Yourself),即 DRY 原则。

说明:

随意复制和粘贴代码必然会导致代码的重复在以后需要修改时需要修改所有的副 本容易遗漏必要时抽取共性方法或者抽象公共类甚至是组件化

正例:

一个类中有多个 public 方法都需要进行数行相同的参数校验操作这个时候请抽取
private boolean checkParam(DTO dto) {...}

日志规约

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

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

private static final Logger logger = LoggerFactory.getLogger(Abc.class);

[强制]应用中的扩展日志(如打点、临时监控、访问日志等)命名方式: appName_logType_logName.log。logType:日志类型,推荐分类有stats/desc/monitor/visit 等;logName:日志描述。

这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。

正例:

mppserver 应用中单独监控时区转换异常 mppserver_monitor_timeZoneConvert.log

说明:

推荐对日志进行分类如将错误日志和业务日志分开存放便于开发人员查看也便于 通过日志对系统进行及时监控

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

正例:

<logger name="com.taobao.dubbo.config" additivity="false">

[强制]异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么 关键字 throws 往上抛出。

正例:

logger.error(各类参数或者对象 toString + "_" + e.getMessage(), e);

[推荐]谨慎地记录日志。生产环境禁止输出 debug 日志;有选择地输出 info 日志;如果使 用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘 撑爆,并记得及时删除这些观察日志。

说明:

大量地输出无效日志不利于系统性能提升也不利于快速定位错误点记录日志时请 思考这些日志真的有人看吗看到这条日志你能做什么能不能给问题排查带来好处

[强制]表单、AJAX 提交必须执行 CSRF 安全过滤。 说明:CSRF(Cross-site request forgery)跨站请求伪造是一类常见编程漏洞。对于存在CSRF 漏洞的应用/网站,攻击者可以事先构造好 URL,只要受害者用户一访问,后台便在用户 不知情情况下对数据库中用户参数进行相应修改。

[强制]在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放限制, 如数量限制、疲劳度控制、验证码校验,避免被滥刷、资损。 说明:如注册时发送验证码到手机,如果没有限制次数和频率,那么可以利用此功能骚扰到其 它用户,并造成短信平台资源浪费。

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

MySQL 数据库

建表规约

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

说明:

任何字段如果为非负数必须是 unsigned

正例:

表达逻辑删除的字段名is_deleted1 表示删除0 表示未删除

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

说明:

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

正例:

aliyun_adminrdc_configlevel3_name 反例AliyunAdminrdcConfiglevel_3_name

[强制]主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。 \

说明:

pk_ 即 primary keyuk_ 即 unique keyidx_ 即 index 的简称

[强制]小数类型为 decimal,禁止使用 float 和 double。

说明:

floatdouble 在存储的时候存在精度损失的问题很可能在值的比较时得到不 正确的结果如果存储的数据范围超过 decimal 的范围建议将数据拆成整数和小数分开存储

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

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

[强制]表必备三字段:id , gmt_create, gmt_modified

说明:

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

[推荐]表的命名最好是加上“业务名称_表的作用”。

正例:

alipay_task / force_project / trade_config

[推荐]库名与应用名称尽量一致。

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

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

[参考]合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检 索速度。

正例:

如下表,其中无符号值可以避免误存负数,且扩大了表示范围。

Xnip2020-05-26_14-23-13

索引规约

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

说明:

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

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

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

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

说明:

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

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

说明:

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

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

正例:

where a=? and b=? order by c; 索引:a_b_c

反例:

索引中有范围查找那么索引有序性无法利用WHERE a>10 ORDER BY b; 索引 a_b 无法排序

[推荐]利用覆盖索引来进行查询操作,避免回表。

说明:

如果一本书需要知道第 11 章是什么标题会翻开第 11 章对应的那一页吗目录浏览 一下就好这个目录就是起到覆盖索引的作用 

正例:

能够建立索引的种类主键索引唯一索引普通索引而覆盖索引是一种查询的一种 效果用 explain 的结果extra 列会出现using index

[推荐]利用延迟关联或者子查询优化超多分页场景。

说明:

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

[推荐]建组合索引的时候,区分度最高的在最左边。

正例:

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

说明:

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

[参考]创建索引时避免有如下极端误解:

1宁滥勿缺认为一个查询就需要建一个索引

2宁缺勿滥认为索引会消耗空间严重拖慢更新和新增速度 

3抵制惟一索引认为业务的惟一性一律需要在应用层通过先查后插方式解决

SQL 语句

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

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

[强制]当某一列的值全是 NULL 时,count(col)的返回结果为 0,但 sum(col)的返回结果为NULL,因此使用 sum()时需注意 NPE 问题。

正例:

可以使用如下方式来避免 sum  NPE 问题:SELECT IF(ISNULL(SUM(g)),0,SUM(g)) FROM table;

[强制]使用 ISNULL()来判断是否为 NULL 值。

说明:

NULL 与任何值的直接比较都为 NULL 
1 NULL<>NULL 的返回结果是 NULL,而不是 false
2 NULL=NULL 的返回结果是 NULL,而不是 true 
3 NULL<>1 的返回结果是 NULL,而不是 true

[强制]不得使用外键与级联,一切外键概念必须在应用层解决。

说明:

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

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

[强制]数据订正时,删除和修改记录时,要先 select,避免出现误删除,确 行更新语句。

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

[参考]TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但 TRUNCATE无事务且不触发 trigger,有可能造成事故,故不建议在开发代码中使用此语句。

说明:

TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同。

ORM 映射

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

说明:

1)增加查询分析器解析成本。
2)增减字段容易与 resultMap 配置不一致。

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

说明:

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

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

说明:

配置映射关系使字段与 DO 类解耦方便维护

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

[强制]不允许直接拿 HashMap 与 Hashtable 作为查询结果集的输出。

说明:

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

[强制]更新数据表记录时,必须同时更新记录对应的 gmt_modified 字段值为当前时间。

[推荐]不要写一个大而全的数据更新接口。

传入为 POJO 类,不管是不是自己的目标更新字 段,都进行

update table set c1=value1,c2=value2,c3=value3; 

这是不对的。执行 SQL时,不要更新无改动的字段,一是易出错;二是效率低;三是增加 binlog 存储。

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

工程结构

应用分层

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

Xnip2020-05-26_14-39-30

**开放接口层:**可直接封装 Service 方法暴露成 RPC 接口;通过 Web 封装成 http 接口;进行 网关安全控制、流量控制等。

**终端显示层:**各个端的模板渲染并执行显示的层。当前主要是 velocity 渲染,JS 渲染, JSP 渲染,移动端展示等。

**Web 层:**主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。

**Service 层:**相对具体的业务逻辑服务层。

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

​ 1) 对第三方平台封装的层,预处理返回结果及转化异常信息;

​ 2) 对 Service 层通用能力的下沉,如缓存方案、中间件通用处理;

​ 3) 与 DAO 层交互,对多个 DAO 的组合复用。

**DAO 层:**数据访问层,与底层 MySQL、Oracle、Hbase 等进行数据交互。

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

[参考] (分层异常处理规约)在 DAO 层,产生的异常类型有很多,无法用 行 catch,使用 catch(Exception e)方式,并 throw new DAOException(e),不需要打印日志,因为日志在 Manager/Service 层一定需要捕获并打到日志文件中去,

如果同台服务器 再打日志,浪费性能和存储。在 Service 层出现异常时,必须记录出错日志到磁盘,尽可能带 上参数信息,相当于保护案发现场。

如果 Manager 层与 Service 同机部署,日志方式与 DAO 层处理一致,如果是单独部署,则采用与 Service 一致的处理方式。Web 层绝不应该继续往上抛异常,因为已经处于顶层,如果意识到这个异常将导致页面无法正常渲染,那么就应该直接 跳转到友好错误页面,加上用户容易理解的错误提示信息。开放接口层要将异常处理成错误码 和错误信息方式返回。

[参考]分层领域模型规约:

  • DO(Data Object):与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。

  • **DTO(Data Transfer Object):**数据传输对象,Service 或 Manager 向外传输的对象。

  • **BO(Business Object):**业务对象。由 Service 层输出的封装业务逻辑的对象。

  • **VO(View Object):**显示层对象,通常是 Web 向模板渲染引擎层传输的对象。

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

  • **AO(Application Object):**应用对象。在 Web 层与 Service 层之间抽象的复用对象模型,

    极为贴近展示层,复用度不高。

服务器

[推荐]高并发服务器建议调小 TCP 协议的 time_wait 超时时间

说明:

操作系统默认 240 秒后,才会关闭处于 time_wait 状态的连接,在高并发访问下,服 务器端会因为处于 time_wait 的连接数太多,可能无法建立新的连接,所以需要在服务器上 调小此等待值。 

正例:

在 linux 服务器上请通过变更/etc/sysctl.conf 文件去修改该缺省值(秒): net.ipv4.tcp_fin_timeout = 30

[推荐]调大服务器所支持的最大文件句柄数(File Descriptor,简写为 fd)。

说明:

主流操作系统的设计是将 TCP/UDP 连接采用与文件一样的方式去管理,即一个连接对 应于一个 fd。主流的 linux 服务器默认所支持最大 fd 数量为 1024,当并发连接数很大时很 容易因为 fd 不足而出现“open too many files”错误,导致新的连接无法建立。 建议将 linux 服务器所支持的最大句柄数调高数倍(与服务器的内存数量相关)。

[推荐]给 JVM 设置-XX:+HeapDumpOnOutOfMemoryError 参数,让 JVM 碰到 OOM 场景时输出 dump 信息。

说明:

OOM 的发生是有概率的甚至有规律地相隔数月才出现一例出现时的现场信息对查错 非常有价值

[推荐]在线上生产环境,JVM 的 Xms 和 Xmx 设置一样大小的内存容量,避免在 GC 后 大小带来的压力。

「真诚赞赏,手留余香」

真诚赞赏,手留余香

使用微信扫描二维码完成支付