Java开发手册——嵩山版(清幽现云山,虚静出内功)-3

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: Java开发手册——嵩山版(清幽现云山,虚静出内功)

二、异常日志

(一) 错误码

1. 【强制】 错误码的制定原则:快速溯源、沟通标准化。

说明:

错误码想得过于完美和复杂,就像康熙字典中的生僻字一样,用词似乎精准,但是字典不容易随身

携带并且简单易懂。

正例: 错误码回答的问题是谁的错?错在哪?1)错误码必须能够快速知晓错误来源,可快速判断是谁的问

题。2)错误码必须能够进行清晰地比对(代码中容易 equals)。3)错误码有利于团队快速对错误原因达

到一致认知。

2. 【强制】 错误码不体现版本号和错误等级信息。

说明: 错误码以不断追加的方式进行兼容。错误等级由日志和错误码本身的释义来决定。

3. 【强制】 全部正常,但不得不填充错误码时返回五个零:00000。

4. 【强制】 错误码为字符串类型,共 5 位,分成两个部分:错误产生来源+四位数字编号。

说明: 错误产生来源分为 A/B/C,A 表示错误来源于用户,比如参数错误,用户安装版本过低,用户支付

超时等问题;B 表示错误来源于当前系统,往往是业务逻辑出错,或程序健壮性差等问题;C 表示错误来源

于第三方服务,比如 CDN 服务出错,消息投递超时等问题;四位数字编号从 0001 到 9999,大类之间的

步长间距预留 100,参考文末 附表 3 。

5. 【强制】 编号不与公司业务架构,更不与组织架构挂钩,以先到先得的原则在统一平台上进行,

审批生效,编号即被永久固定。

6. 【强制】 错误码使用者避免随意定义新的错误码。

说明: 尽可能在原有错误码附表中找到语义相同或者相近的错误码在代码中使用即可。

7. 【强制】 错误码不能直接输出给用户作为提示信息使用。

说明: 堆栈(stack_trace)、错误信息(error_message)、错误码(error_code)、提示信息(user_tip)

是一个有效关联并互相转义的和谐整体,但是请勿互相越俎代庖。

8. 【推荐】 错误码之外的业务独特信息由 error_message 来承载,而不是让错误码本身涵盖过

多具体业务属性。

9. 【推荐】 在获取第三方服务错误码时,向上抛出允许本系统转义,由 C 转为 B,并且在错误信

息上带上原有的第三方错误码。

10. 【参考】 错误码分为一级宏观错误码、二级宏观错误码、三级宏观错误码。

说明: 在无法更加具体确定的错误场景中,可以直接使用一级宏观错误码,分别是:A0001(用户端错误)、 Java 开发手册

30/59

B0001(系统执行出错)、C0001(调用第三方服务出错)。

正例: 调用第三方服务出错是一级,中间件错误是二级,消息服务出错是三级。

11. 【参考】 错误码的后三位编号与 HTTP 状态码没有任何关系。

12. 【参考】 错误码有利于不同文化背景的开发者进行交流与代码协作。

说明: 英文单词形式的错误码不利于非英语母语国家(如阿拉伯语、希伯来语、俄罗斯语等)之间的开发

者互相协作。

13. 【参考】 错误码即人性,感性认知+口口相传,使用纯数字来进行错误码编排不利于感性记忆

和分类。

说明: 数字是一个整体,每位数字的地位和含义是相同的。

反例: 一个五位数字 12345,第 1 位是错误等级,第 2 位是错误来源,345 是编号,人的大脑不会主动地

拆开并分辨每位数字的不同含义。

(二) 异常处理

1. 【强制】 Java 类库中定义的可以通过预检查方式规避的 RuntimeException 异常不应该通过

catch 的方式来处理,比如: NullPointerException , IndexOutOfBoundsException 等等。

说明: 无法通过预检查的异常除外,比如,在解析字符串形式的数字时,可能存在数字格式错误,不得不

通过 catch NumberFormatException 来实现。

正例: if (obj != null) {...}

反例: try { obj.method(); } catch (NullPointerException e) {…}

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

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

3. 【强制】 catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。

对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理。

说明: 对大段代码进行 try - catch ,使程序无法根据不同的异常做出正确的应激反应,也不利于定位问题,

这是一种不负责任的表现。

正例: 用户注册的场景中,如果用户输入非法字符,或用户名称已存在,或用户输入密码过于简单,在程

序上作出分门别类的判断,并提示给用户。

4. 【强制】 捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请

将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的

内容。

5. 【强制】 事务场景中,抛出异常被 catch 后,如果需要回滚,一定要注意手动回滚事务。 Java 开发手册

31/59

6. 【强制】 finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。

说明: 如果 JDK7 及以上,可以使用 try-with-resources 方式。

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

说明: try 块中的 return 语句执行成功后,并不马上返回,而是继续执行 finally 块中的语句,如果此处存

在 return 语句,则在此直接返回,无情丢弃掉 try 块中的返回点。

反例:

private int x = 0 ;

public int checkReturn () {

try {

// x 等于 1,此处不返回

return ++ x ;

} finally {

// 返回的结果是 2

return ++ x ;

}

}

8. 【强制】 捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。

说明: 如果预期对方抛的是绣球,实际接到的是铅球,就会产生意外情况。

9. 【强制】 在调用 RPC 、二方包、或动态生成类的相关方法时,捕捉异常必须使用 Throwable

类来进行拦截。

说明: 通过反射机制来调用方法,如果找不到方法,抛出 NoSuchMethodException 。什么情况会抛出

NoSuchMethodError 呢?二方包在类冲突时,仲裁机制可能导致引入非预期的版本使类的方法签名不匹配,

或者在字节码修改框架(比如: ASM )动态创建或修改类时,修改了相应的方法签名。这些情况,即使代

码编译期是正确的,但在代码运行期时,会抛出 NoSuchMethodError 。

10. 【推荐】 方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分说

明什么情况下会返回 null 值。

说明: 本手册明确防止 NPE 是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也

并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回 null 的情况。

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 问题。 Java 开发手册

32/59

12. 【推荐】 定义时区分 unchecked / checked 异常,避免直接抛出 new RuntimeException(),

更不允许抛出 Exception 或者 Throwable,应使用有业务含义的自定义异常。推荐业界已定

义过的自定义异常,如:DAOException / ServiceException 等。

13. 【参考】 对于公司外的 http/api 开放接口必须使用 errorCode;而应用内部推荐异常抛出;

跨应用间 RPC 调用优先考虑使用 Result 方式,封装 isSuccess()方法、errorCode、

errorMessage;而应用内部直接抛出异常即可。

说明: 关于 RPC 方法返回方式使用 Result 方式的理由:

1)使用抛异常返回方式,调用方如果没有捕获到就会产生运行时错误。

2)如果不加栈信息,只是 new 自定义异常,加入自己的理解的 error message,对于调用端解决问题

的帮助不会太多。如果加了栈信息,在频繁调用出错的情况下,数据序列化和传输的性能损耗也是问题。

(三) 日志规约

1. 【强制】 应用中不可直接使用日志系统 (Log 4 j 、 Logback) 中的 API ,而应依赖使用日志框架

(SLF4J、JCL--Jakarta Commons Logging) 中的 API ,使用门面模式的日志框架,有利于维护和

各个类的日志处理方式统一。

说明: 日志框架(SLF4J、JCL--Jakarta Commons Logging)的使用方式(推荐使用 SLF4J)

使用 SLF4J:

import org . slf4j . Logger ;

import org . slf4j . LoggerFactory ;

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

使用 JCL:

import org . apache . commons . logging . Log ;

import org . apache . commons . logging . LogFactory ;

private static final Log log = LogFactory . getLog ( Test . class );

2. 【强制】 所有日志文件至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。对于

当天日志,以“应用名.log”来保存,保存在/home/admin/应用名/logs/目录下,过往日志

格式为: {logname}.log.{保存日期},日期格式:yyyy-MM-dd

正例: 以 aap 应用为例,日志保存在/home/admin/aapserver/logs/aap.log,历史日志名称为

aap.log.2016-08-01

3. 【强制】 根据国家法律,网络运行状态、网络安全事件、个人敏感信息操作等相关记录,留存

的日志不少于六个月,并且进行网络多机备份。

4. 【强制】 应用中的扩展日志 ( 如打点、临时监控、访问日志等 ) 命名方式:

appName_logType_logName.log。logType:日志类型,如 stats/monitor/access 等;logName:日志描

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

找。 Java 开发手册

33/59

说明: 推荐对日志进行分类,如将错误日志和业务日志分开存放,便于开发人员查看,也便于通过日志对系

统进行及时监控。

正例: mppserver 应用中单独监控时区转换异常,如:mppserver_monitor_timeZoneConvert.log

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

说明: 因为 String 字符串的拼接会使用 StringBuilder 的 append() 方式,有一定的性能损耗。使用占位符仅

是替换动作,可以有效提升性能。

正例: logger . debug ( "Processing trade with id: {} and symbol: {}" , id , symbol );

6. 【强制】 对于 trace / debug / info 级别的日志输出,必须进行日志级别的开关判断。

说明: 虽然在 debug( 参数 ) 的方法体内第一行代码 isDisabled(Level.DEBUG_INT) 为真时( Slf4j 的常见实现

Log4j 和 Logback ),就直接 return ,但是参数可能会进行字符串拼接运算。此外,如果 debug(getName())

这种参数内有 getName() 方法调用,无谓浪费方法调用的开销。

正例:

// 如果判断为真,那么可以输出 trace 和 debug 级别的日志

if ( logger . isDebugEnabled ()) {

logger . debug ( "Current ID is: {} and name is: {}" , id , getName ());

}

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

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

8. 【强制】 生产环境禁止直接使用 System.out 或 System.err 输出日志或使用

e.printStackTrace()打印异常堆栈。

说明: 标准日志输出与标准错误输出文件每次 Jboss 重启时才滚动,如果大量输出送往这两个文件,容易

造成文件大小超过操作系统大小限制。

9. 【强制】 异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过

关键字 throws 往上抛出。

正例: logger.error("inputParams:{} and errorMessage:{}", 各类参数或者对象 toString(), e.getMessage(), e);

10. 【强制】 日志打印时禁止直接用 JSON 工具将对象转换成 String。

说明: 如果对象里某些 get 方法被覆写,存在抛出异常的情况,则可能会因为打印日志而影响正常业务流

程的执行。

正例: 打印日志时仅打印出业务相关属性值或者调用其对象的 toString()方法。

11. 【推荐】 谨慎地记录日志。生产环境禁止输出 debug 日志 ; 有选择地输出 info 日志 ; 如果使用

warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑

爆,并记得及时删除这些观察日志。

说明: 大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。记录日志时请思考:这些

日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处? Java 开发手册

12. 【推荐】 可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适

从。如非必要,请不要在此场景打出 error 级别,避免频繁报警。

说明: 注意日志输出的级别,error 级别只记录系统逻辑出错、异常或者重要的错误信息。

13. 【推荐】 尽量用英文来描述日志错误信息,如果日志中的错误信息用英文描述不清楚的话使用

中文描述即可,否则容易产生歧义。

说明: 国际化团队或海外部署的服务器由于字符集问题,使用全英文来注释和描述日志错误信息。

34/59 Java 开发手册

35/59

三、单元测试

1. 【强制】 好的单元测试必须遵守 AIR 原则。

说明: 单元测试在线上运行时,感觉像空气(AIR)一样感觉不到,但在测试质量的保障上,却是非常关键

的。好的单元测试宏观上来说,具有自动化、独立性、可重复执行的特点。

⚫ A :Automatic(自动化)

⚫ I :Independent(独立性)

⚫ R :Repeatable(可重复)

2. 【强制】 单元测试应该是全自动执行的,并且非交互式的。测试用例通常是被定期执行的,执

行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。单元

测试中不准使用 System.out 来进行人肉验证,必须使用 assert 来验证。

3. 【强制】 保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试用例之间

决不能互相调用,也不能依赖执行的先后次序。

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

4. 【强制】 单元测试是可以重复执行的,不能受到外界环境的影响。

说明: 单元测试通常会被放到持续集成中,每次有代码 check in 时单元测试都会被执行。如果单测对外部

环境(网络、服务、中间件等)有依赖,容易导致持续集成机制的不可用。

正例: 为了不受外界环境影响,要求设计代码时就把 SUT 的依赖改成注入,在测试时用 spring 这样的 DI

框架注入一个本地(内存)实现或者 Mock 实现。

5. 【强制】 对于单元测试,要保证测试粒度足够小,有助于精确定位问题。单测粒度至多是类级

别,一般是方法级别。

说明: 只有测试粒度小才能在出错时尽快定位到出错位置。单测不负责检查跨类或者跨系统的交互逻辑,

那是集成测试的领域。

6. 【强制】 核心业务、核心应用、核心模块的增量代码确保单元测试通过。

说明: 新增代码及时补充单元测试,如果新增代码影响了原有单元测试,请及时修正。

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

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

8. 【推荐】 单元测试的基本目标:语句覆盖率达到 70% ;核心模块的语句覆盖率和分支覆盖率都

要达到 100%

说明: 在工程规约的应用分层中提到的 DAO 层,Manager 层,可重用度高的 Service,都应该进行单元测

试。 Java 开发手册

36/59

9. 【推荐】 编写单元测试代码遵守 BCDE 原则,以保证被测试模块的交付质量。

⚫ B :Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。

⚫ C :Correct,正确的输入,并得到预期的结果。

⚫ D :Design,与设计文档相结合,来编写单元测试。

⚫ E :Error,强制错误信息输入(如:非法数据、异常流程、业务允许外等),并得到预期的结果。

10. 【推荐】 对于数据库相关的查询,更新,删除等操作,不能假设数据库里的数据是存在的,或

者直接操作数据库把数据插入进去,请使用程序插入或者导入数据的方式来准备数据。

反例: 删除某一行数据的单元测试,在数据库中,先直接手动增加一行作为删除目标,但是这一行新增数

据并不符合业务插入规则,导致测试结果异常。

11. 【推荐】 和数据库相关的单元测试,可以设定自动回滚机制,不给数据库造成脏数据。或者对

单元测试产生的数据有明确的前后缀标识。

正例: 在阿里巴巴企业智能事业部的内部单元测试中,使用 ENTERPRISE_INTELLIGENCE _UNIT_TEST_

的前缀来标识单元测试相关代码。

12. 【推荐】 对于不可测的代码在适当的时机做必要的重构,使代码变得可测,避免为了达到测试

要求而书写不规范测试代码。

13. 【推荐】 在设计评审阶段,开发人员需要和测试人员一起确定单元测试范围,单元测试最好覆

盖所有测试用例(UC)。

14. 【推荐】 单元测试作为一种质量保障手段,在项目提测前完成单元测试,不建议项目发布后补

充单元测试用例。

15. 【参考】 为了更方便地进行单元测试,业务代码应避免以下情况:

⚫ 构造方法中做的事情过多。

⚫ 存在过多的全局变量和静态方法。

⚫ 存在过多的外部依赖。

⚫ 存在过多的条件语句。

说明: 多层条件语句建议使用卫语句、策略模式、状态模式等方式重构。

16. 【参考】 不要对单元测试存在如下误解:

⚫ 那是测试同学干的事情。本文是开发手册,凡是本文内容都是与开发同学强相关的。

⚫ 单元测试代码是多余的。系统的整体功能与各单元部件的测试正常与否是强相关的。

⚫ 单元测试代码不需要维护。一年半载后,那么单元测试几乎处于废弃状态。

⚫ 单元测试与线上故障没有辩证关系。好的单元测试能够最大限度地规避线上故障。 Java 开发手册

37/59

四、安全规约

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

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

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

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

3. 【强制】 用户输入的 SQL 参数严格使用参数绑定或者 METADATA 字段值限定,防止 SQL 注入,

禁止字符串拼接 SQL 访问数据库。

反例: 某系统签名大量被恶意修改,即是因为对于危险字符 # --没有进行转义,导致数据库更新时,where

后边的信息被注释掉,对全库进行更新。

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

说明: 忽略参数校验可能导致:

⚫ page size 过大导致内存溢出

⚫ 恶意 order by 导致数据库慢查询

⚫ 缓存击穿

⚫ SSRF

⚫ 任意重定向

⚫ SQL 注入,Shell 注入,反序列化注入

⚫ 正则输入源串拒绝服务 ReDoS

Java 代码用正则来验证客户端的输入,有些正则写法验证普通用户输入没有问题,但是如果攻击人员使用

的是特殊构造的字符串来验证,有可能导致死循环的结果。

5. 【强制】 禁止向 HTML 页面输出未经安全过滤或未正确转义的用户数据。

6. 【强制】 表单、 AJAX 提交必须执行 CSRF 安全验证。

说明: CSRF(Cross-site request forgery)跨站请求伪造是一类常见编程漏洞。对于存在 CSRF 漏洞的应用/

网站,攻击者可以事先构造好 URL,只要受害者用户一访问,后台便在用户不知情的情况下对数据库中用

户参数进行相应修改。

7. 【强制】 URL 外部重定向传入的目标地址必须执行白名单过滤。

8. 【强制】 在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放的机

制,如数量限制、疲劳度控制、验证码校验,避免被滥刷而导致资损。

说明: 如注册时发送验证码到手机,如果没有限制次数和频率,那么可以利用此功能骚扰到其它用户,并

造成短信平台资源浪费。

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

滤等风控策略。 Java 开发手册

38/59

五、MySQL 数据库

(一) 建表规约

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

(1 表示是,0 表示否)。

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

注意: POJO 类中的任何布尔类型的变量,都不要加 is 前缀,所以,需要在< 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 类型,前者现在时表示主动式创建,后者过去分词表示被动式更新。 Java 开发手册

39/59

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 次方

(二) 索引规约

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

说明: 不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,

即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。

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

保证被关联的字段需要有索引。

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

3. 【强制】 在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据

实际文本区分度决定索引长度。 Java 开发手册

40/59

说明: 索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 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. 【推荐】 防止因字段类型不同造成的隐式转换,导致索引失效。 Java 开发手册

41/59

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

1)

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

2)

吝啬索引的创建。认为索引会消耗空间、严重拖慢记录的更新以及行的新增速度。

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 ,避免出现误删除,确认无

误才能执行更新语句。 Java 开发手册

42/59

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. 【参考】 因国际化需要,所有的字符存储与表示,均采用 utf 8 字符集,那么字符计数方法需

要注意。

说明:

SELECT LENGTH("轻松工作");

返回为 12

SELECT CHARACTER_LENGTH("轻松工作");

返回为 4

如果需要存储表情,那么选择 utf8mb4 来进行存储,注意它与 utf8 编码的区别。

13. 【参考】 TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但 TRUNCATE

无事务且不触发 trigger ,有可能造成事故,故不建议在开发代码中使用此语句。

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

(四) ORM 映射

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

说明: 1)增加查询分析器解析成本。2)增减字段容易与 resultMap 配置不一致。3)无用字段增加网络

消耗,尤其是 text 类型的字段。

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

字段与属性之间的映射。

说明: 参见定义 POJO 类以及数据库字段定义规定,在 sql.xml 增加映射,是必须的。 Java 开发手册

43/59

3. 【强制】 不要用 resultClass 当返回参数,即使所有类属性名与数据库字段一一对应,也需要

定义<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>中的 compareValue 是与属性值对比的常量,一般是数字,表示相等时

带上此条件;<isNotEmpty>表示不为空且不为 null 时执行;<isNotNull>表示不为 null 值

时执行。 Java 开发手册

44/59


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
3月前
|
小程序 JavaScript Java
【资料】阿里Java开发手册
本文是关于分享阿里Java开发手册资源及促进编程规范学习的指南。作者以个人经历引入,讲述了公司领导通过细致讲解阿里Java开发手册,提升了团队对代码质量和编程规范的认识
802 0
【资料】阿里Java开发手册
|
3月前
|
存储 Java 测试技术
阿里巴巴java开发手册
这篇文章是关于阿里巴巴Java开发手册的整理,内容包括编程规约、异常日志、单元测试、安全规约、MySQL数据库使用以及工程结构等方面的详细规范和建议,旨在帮助开发者编写更加规范、高效和安全的代码。
|
6月前
|
Java
电子书阅读分享《Java开发手册(嵩山版)》
电子书阅读分享《Java开发手册(嵩山版)》
|
6月前
|
Java
Java开发手册之控制语句,2024最新Java笔经
Java开发手册之控制语句,2024最新Java笔经
|
6月前
|
Java
电子书阅读分享《Java开发手册(泰山版)》
电子书阅读分享《Java开发手册(泰山版)》
电子书阅读分享《Java开发手册(泰山版)》
白瞟党乐坏了!Alibaba内部最新Java开发手册(嵩山版)灵魂17问
Java是世界各地开发者使用最多的编程语言,无论是在用户最喜爱的编程语言排行榜、程序员薪资榜单、编程入门首选语言等榜单上都是常年占据前三的位置的一种语言,但它也是最难学的语言之一。而《Java开发手册》可以算是学习Java,规范写法的必读书目了,那么你知道为什么要按照规约来吗?
|
6月前
|
SQL Java API
《Java开发手册灵魂13问》正式上线,带你剖析阿里巴巴的开发细节
一线大厂怎么用Java?看阿里技术专家给你分析!《〈Java开发手册(泰山版)〉灵魂13问》电子书正式上线带你剖析阿里巴巴一线团队开发思维。
|
存储 缓存 Java
Java基础知识第二讲:Java开发手册/JVM/集合框架/异常体系/Java反射/语法知识/Java IO
Java基础知识第二讲:Java开发手册/JVM/集合框架/异常体系/Java反射/语法知识/Java IO
231 0
Java基础知识第二讲:Java开发手册/JVM/集合框架/异常体系/Java反射/语法知识/Java IO
|
11月前
|
Java Apache Spring
Spring BeanUtils 2、Cglib BeanCopier 3、Apache BeanUtils 4、Apache PropertyUtils 5、Dozer 那么,我们到底应该选择哪种工具类更加合适呢?为什么Java开发手册中提到禁止使用Apache BeanUtils呢
Spring BeanUtils 2、Cglib BeanCopier 3、Apache BeanUtils 4、Apache PropertyUtils 5、Dozer 那么,我们到底应该选择哪种工具类更加合适呢?为什么Java开发手册中提到禁止使用Apache BeanUtils呢
104 0
|
Java
无意中发现阿里巴巴Java开发手册「2023最新黄山版」竟然发布了
提起阿里巴巴的《Java开发手册》大家肯定都不陌生,这份手册代表这Alibaba技术团队的集体智慧结晶和内部大佬的经验总结,经历了多次打磨不断的完善,随着市面上各种版本的流出,小编无意中发现了这份【黄山版】。
7166 1