Java开发手册1.5读书笔记
码出高效,码出质量。
记录一些现阶段对自己有用、能吸收学习的规范。
序号为《Java开发手册1.5》对应内容的序号。
一、编程规约
(一)命名风格
-
5.常量命名全部大写,单词间用下划线隔开。
-
6.抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类的名称开始,以 Test 结尾。
-
8.POJO 类中 Boolean 类型变量都不要加 is 前缀,否则部分框架解析会引起序列化错误。
反例:定义为基本数据类型 Boolean isDeleted 的属性,它的方法也是 isDeleted(),RPC 框架在反向解析的时候,“误以为”对应的属性名称是 deleted,导致属性获取不到,进而抛出异常。
-
14.如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式,有利于阅读者快速理解架构设计理念。
正例:
public class OrderFactory;
public class LoginProxy;
public class ResourceObserver; -
15.接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性,并加上有效的 Javadoc注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。
正例:接口方法签名 void commit();
接口基础常量 String COMPANY = "alibaba";
反例:接口方法定义 public abstract void f();
说明:JDK8 中接口允许有默认实现,那么这个 default 方法,是对所有实现类都有价值的默认实现。 -
16.接口和实现类的命名有两套规则:
-
对于 Service 和 DAO 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部的实现类用 Impl 的后缀与接口区别。
正例:CacheServiceImpl 实现 CacheService 接口。
-
如果是形容能力的接口名称,取对应的形容词为接口名(通常是–able 的形容词)。
正例:AbstractTranslator 实现 Translatable 接口。
-
-
17.枚举类名带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。
说明:枚举其实就是特殊的类,域成员均为常量,且构造方法被默认强制是私有。
正例:枚举名字为 ProcessStatusEnum 的成员名称:SUCCESS / UNKNOWN_REASON。
-
18.各层命名规约:
- Service / DAO 层方法命名规约
- 获取单个对象的方法用 get 做前缀。
- 获取多个对象的方法用 list 做前缀,复数形式结尾。
- 获取统计值的方法用 count 做前缀。
- 插入的方法用 save / insert 做前缀。
- 删除的方法用 remove / delete 做前缀。
- 修改的方法用 update 做前缀。
- 领域模型命名规约
- 数据对象:xxxDO,xxx为数据表名。
- 数据传输对象:xxxDTO,xxx为业务领域相关的名称。
- 展示对象:xxxVO,xxx一般为网页名称。
- POJO 是 DO / DTO / BO / VO 的统称,禁止命名成 xxxPOJO。
- Service / DAO 层方法命名规约
(二)常量定义
-
3.不要使用一个常量类来维护所有的常量,用按常量功能分类,分开维护。
正例:缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在类 ConfigConsts 下。
-
4.常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量。
-
跨应用共享常量:放置二方库中,通常是 client.jar 中的 constant 目录下。
-
应用内共享常量:放置一方库中,通常是子模块中的 constant 目录下。
-
子工程内共享常量:即在当前子工程的 constant 目录下。
-
包内共享常量:即在当前包下单独的 constant 目录下。
-
类内共享常量:直接在类内部 private static final 定义。
-
-
5.如果变量值仅在一个固定范围内变化用 enum 类型来定义。
说明:如果存在名称之外的延伸属性应使用 enum 类型,下面正例中的数字就是延伸信息,表示一年中的第几个季节。
// 正例: public enum SeasonEnum { SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4); private int seq; SeasonEnum(int seq) { this.seq = seq; } public int getSeq() { return seq; } }
(三)代码格式
public static void main(String[] args) {
// 正例:
// 这是示例注释,请注意在双斜线之后有一个空格
String param = new String();
// 缩进 4 个空格
String say = "hello";
// 运算符的左右必须有一个空格
int flag = 0;
// 关键词 if 与括号之间必须有一个空格,括号内的 f 与左括号,0 与右括号不需要空格
if (flag == 0) {
System.out.println(say);
}
// 左大括号前加空格且不换行;左大括号后换行
if (flag == 1) {
System.out.println("world");
// 右大括号前换行,右大括号后有 else,不用换行
} else {
System.out.println("ok");
// 在右大括号后直接结束,则必须换行
}
long first = 1000000000000L;
// 在进行类型强制转换时,右括号与强制转换值之间不需要任何空格隔开。
int second = (int)first + 2;
StringBuilder sb = new StringBuilder();
// 超过 120 个字符的情况下,换行缩进 4 个空格,点号和方法名称一起换行
sb.append("Jack").append("Ma")...
.append("alibaba")...
.append("alibaba")...
.append("alibaba");
// 下例中实参的 args1,后边必须要有一个空格。
method(args1, args2, args3);
// 反例:
// 超过 120 个字符的情况下,不要在括号前换行
sb.append("Jack").append("Ma")...append
("alibaba");
// 参数很多的方法调用可能超过 120 个字符,不要在逗号前换行
method(args1, args2, args3, ...
, argsX);
}
-
如果使用 tab 缩进,必须设置 1 个 tab 为 4 个空格。IDEA 设置 tab 为 4 个空格时,请勿勾选 Use tab character;而在 eclipse 中,必须勾选 insert spaces for tabs。
-
IDE 的 text file encoding 设置为 UTF-8; IDE 中文件的换行符使用 Unix 格式,不要使用 Windows 格式。
(四)OOP规约
-
2.所有重写的方法,必须加 @Override 注解。
说明:getObject()与 get0bject()的问题。一个是字母的 O,一个是数字的 0,加 @Override 可以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名进行修改,其实现类会马上编译报错。
-
3.尽量避免使用可变参数,若要使用,必须放参数列表的最后。
正例:public List
listUsers(String type, Long... ids) -
6.Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals 方法,推荐使用 java.util.Objects 中的 equals 方法。
-
7.所有整型包装类对象之间值的比较,全部使用 equals 方法比较。
说明:对于 Integer 包装类,在 -128 至 127 的赋值,Integer 对象是在 IntegerCache.cache 产生的,会复用已有对象,这个区间内的 Integer 值用 == 进行判断也可以,但区间以外的值,都会在堆上产生,不会复用对象,只能用 equals 方法判断。
-
8.浮点数值,无论是基本类型还是包装类型,都不能直接判断比较,即不能使用 == 和 equals 判断。
说明:浮点数采用“尾数+阶码”的编码格式,二进制无法精确表示大部分的十进制小数,精度不准确。
// 正例: // (1) 指定一个误差范围,两个浮点数的差值在此范围之内,则认为是相等的。 float a = 1.0f - 0.9f; float b = 0.9f - 0.8f; float diff = 1e-6f; if (Math.abs(a - b) < diff) { System.out.println("true"); } // (2) 使用 BigDecimal 来定义值,再进行浮点数的运算操作。 BigDecimal a = new BigDecimal("1.0"); BigDecimal b = new BigDecimal("0.9"); BigDecimal c = new BigDecimal("0.8"); BigDecimal x = a.subtract(b); BigDecimal y = b.subtract(c); if (x.equals(y)) { System.out.println("true"); }
-
10.为了防止精度缺失,使用 BigDecimal 时,不能使用 BigDecimal(double) 的构造方法,应使用 BigDecimal(String) 或 valueOf 方法。
-
11.基本数据类型和包装数据类型使用标准如下:
-
所有的 POJO 类属性必须使用包装数据类型。
-
RPC 方法的返回值和参数必须使用包装数据类型。
-
所有的局部变量使用基本数据类型。
-
-
13.序列化类新增属性,不用修改 serialVersionUID 值,避免反序列化失败;如果完全不兼容升级,避免反序列化混乱,应修改 serialVersionUID 值。
说明: serialVersionUID 不一致会抛出序列化运行时异常。
-
18&19.类内方法定义的顺序依次是:公有方法 > 保护方法 > 私有方法 > getter / setter 方法;多个同名方法应放在一起,即使访问权限不一样。
说明:公有方法是类的调用者和维护者最关心的方法,首屏展示最好;保护方法虽然只是子类关心,也可能是“模板设计模式”下的核心方法;而私有方法外部一般不需要特别关心,是一个黑盒实现;因为承载的信息价值较低,所有 Service 和 DAO 的 getter / setter 方法放在类体最后。
-
21.循坏体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展。
说明:下例中,反编译出的字节码文件显示每次循环都会 new 出一个 StringBuilder 对象,然后进行 append 操作,最后通过 toString 方法返回 String 对象,造成内存资源浪费。
// 反例: String str = "start"; for (int i = 0; i < 100; i++) { str = str + "hello"; }
-
23.慎用 Object 的 clone 方法 来拷贝对象。
说明:对象 clone 方法默认是浅拷贝,若想实现深拷贝需重写 clone 方法。
-
24.类成员和方法访问控制从严:
-
如果不允许外部直接通过 new 来创建对象,那么构造方法必须是 private。
-
工具类不允许有 public 和 default 构造方法。
-
类非 static 成员变量并且与子类共享,必须是 protected。
-
类非 static 成员变量仅在本类使用,必须是 private。
-
类 static 成员变量仅在本类使用,必须是 private。
-
若是 static 成员变量,考虑是否为 final。
-
类成员方法只供类内部调用,必须是 private。
-
类成员方法只对继承类公开,限制为 protected。
说明:任何类、方法、参数、变量,严控访问范围。过于宽泛的访问范围,不利于模块解耦。
-
(五)集合处理
-
1.关于 hashCode 和 equals 的处理,遵循以下规则:
- 只要重写 equals,就必须重写 hashCode。
- 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法。
- 作为 Map 的 key 的对象也必须重写这两个方法。
-
2.ArrayList 的 subList 结果不可强转成 ArrayList,否则会抛出 ClassCastException 异常,即 java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。
说明:subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList 而是 ArrayList 的一个视图,对于 SubList 子列表的所有操作最终会反映到原列表上。
-
3.使用 Map 的方法 keySet() / values() / entrySet() 返回集合对象时,不可以对其进行添加元素操作,否则会抛出 UnsupportedOperationException 异常。
-
5.在 subList 场景中,高度注意对原集合元素的增加或删除,均会导致子列表的遍历、增加、删除产生 ConcurrentModificationException 异常。
-
6.使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为 0 的空数组。
// 反例:直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现 ClassCastException 错误。 // 正例: List<String> list = new ArrayList<>(2); list.add("guan"); list.add("bao"); String[] array = list.toArray(new String[0]);
说明:使用 toArray 带参方法,数组空间大小的 length:
1) 等于 0,动态创建与 size 相同的数组,性能最好。
2) 大于 0 但小于 size,重新创建大小等于 size 的数组,增加 GC 负担。
3) 等于 size,在高并发情况下,数组创建完成之后,size 正在变大的情况下,负面影响与上相同。
4) 大于 size,空间浪费,且在 size 处插入 null 值,存在 NPE 隐患。
-
7.在使用 Collection 接口任何实现类的 addAll()方法时,都要对输入的集合参数进行 NPE 判断。
说明:在 ArrayList#addAll 方法的第一行代码即 Object[] a = c.toArray(); 其中 c 为输入集合参数,如果为 null,则直接抛出异常。
-
8.使用工具类 Arrays.asList() 把数组转换为集合时,不能使用其修改集合的方法,如 add / remove / clear 方法,会抛出 UnsupportedOperationException 异常。
说明:asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList 体现的是适配器模式,只是转换接口,后台数据仍是数组。
String[] str = new String[] { "yang", "hao" }; List list = Arrays.asList(str); // 运行时异常 list.add("yangguanbao"); // 集合也会随之修改,反之亦然 str[0] = "changed"; // 这样能创建出真正的集合 List<String> list = new ArrayList(Arrays.asList(str));
-
11.不要在 foreach 循环中进行元素的 remove / add 操作。 remove 元素请使用 Iterator 方式。如果是并发操作,需要对 Iterator 对象加锁。
// 正例: List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String item = iterator.next(); if (删除元素的条件) { iterator.remove(); } } // 反例: for (String item : list) { if ("1".equals(item)) { list.remove(item); } }
-
13.集合泛型定义时,在 JDK7 及以上,使用 diamond 语法或全省略。
// diamond 方式,即<> HashMap<String, String> userCache = new HashMap<>(16); // 全省略方式 ArrayList<User> users = new ArrayList(10);
-
14.集合初始化时,最好指定集合初始容量。
-
15.遍历 Map 类集合应使用 entrySet,而不是 keySet。
说明:keySet 其实是遍历了 2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出 key 所对应的 value。而 entrySet 只是遍历了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8,使用 Map.forEach 方法。
-
16.高度注意 Map 类集合 K/V 能不能存储 null 值的情况。
- 18.对集合进行去重操作应使用 Set 的特性,而不是使用 List 的 contains 方法。
(六)并发控制
-
1.获取单例对象需要保证线程安全,其中的方法也要保证线程安全。
-
3.线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
-
4.线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
-
6.必须回收自定义的 ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用 try-finally 块进行回收。
// 正例: objectThreadLocal.set(userInfo); try { // ... } finally { objectThreadLocal.remove(); }
-
7.高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
说明:尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用 RPC 方法。
-
8.对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。
-
9.在使用阻塞等待获取锁的方式中,必须在 try 代码块之外,并且在加锁方法与 try 代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在 finally 中无法解锁。
// 正例: Lock lock = new XxxLock(); // ... lock.lock(); try { doSomething(); doOthers(); } finally { lock.unlock(); } // 反例: Lock lock = new XxxLock(); // ... try { // 如果此处抛出异常,则直接执行 finally 代码块 doSomething(); // 无论加锁是否成功,finally 代码块都会执行 lock.lock(); doOthers(); } finally { lock.unlock(); }
-
10.在使用尝试机制来获取锁的方式中,进入业务代码块之前,必须先判断当前线程是否持有锁。锁的释放规则与锁的阻塞等待方式相同。
// 正例: Lock lock = new XxxLock(); // ... boolean isLocked = lock.tryLock(); if (isLocked) { try { doSomething(); doOthers(); } finally { lock.unlock(); } }
-
15.避免 Random 实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一 seed 导致的性能下降。
说明:Random 实例包括 java.util.Random 的实例或者 Math.random() 的方式。
正例:在 JDK7 之后,可以直接使用 API ThreadLocalRandom,而在 JDK7 之前,需要编码保证每个线程持有一个实例。
(七)控制语句
-
1.在一个 switch 块内,每个 case 要么通过 continue / break / return 等来终止,要么注释说明程序将继续执行到哪一个 case 为止;在一个 switch 块内,都必须包含一个 default 语句并且放在最后,即使它什么代码也没有。
-
2.当 switch 括号内的变量类型为 String 并且此变量为外部参数时,必须先进行 null 判断。
-
3.在 if / else / for / while / do 语句中必须使用大括号。
-
5.在表达特殊情况的分支时,少用 if - else 方式。
说明:如果非使用 if()...else if()...else...方式表达逻辑,请勿超过 3 层。
正例:超过 3 层的 if-else 的逻辑判断代码可以使用卫语句、策略模式、状态模式等来实现,其中卫语句即代码逻辑先考虑失败、异常、中断、退出等直接返回的情况,以方法多个出口的方式,解决代码中判断分支嵌套的问题,这是逆向思维的体现。
public void findBoyfriend(Man man) { if (man.isUgly()) { System.out.println("本姑娘是外貌协会的资深会员"); return; } if (man.isPoor()) { System.out.println("贫贱夫妻百事哀"); return; } if (man.isBadTemper()) { System.out.println("银河有多远,你就给我滚多远"); return; } System.out.println("可以先交往一段时间看看"); }
(八)注释规约
- 1.类、类属性、类方法的注释必须使用 Javadoc 规范,使用/**内容*/格式,不得使用 // xxx 方式。
- 2.所有的抽象方法(接口中的方法)必须要用 Javadoc 注释、除了返回值、参数、异常说明外,还必须指出该方法做什么事,实现什么功能。
- 3.所有的类都必须添加创建者和创建日期。
- 4.方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释,注意与代码对齐。
- 5.所有的枚举类型字段必须要有注释,说明每个数据项的用途。
(九)其它
-
1.在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。
-
3.后台输送给页面的变量必须加$!——中间的感叹号。
说明:如果 var 等于 null 或者不存在,那么${var}会直接显示在页面上。
-
6.日期格式化时,注意大小写。
表示日期和时间的格式:new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 小写 h 为 12 小时制。
-
7.不要在视图模板加入任何复杂的逻辑,视图的职责是展示,不要抢模型和控制器的活。
二、异常日志
(一)异常处理
-
1.可以通过预检查方式规避的 RuntimeException 异常不应该通过 catch 的方式处理,如:NullPointerException,IndexOutOfBoundsException 等等。
-
5.有 try 块放到了事务代码中,catch 异常后,如果需要回滚事务,一定要注意手动回滚事务。
-
7.不要在 finally 块中使用 return。
说明:try 块中的 return 语句执行成功后,并不马上返回,而是继续执行 finally 块中的语句,如果此处存在 return 语句,则在此直接返回,无情丢弃掉 try 块中的返回点。
(二)日志规约
-
1.应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架 SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
-
3.应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:appName_logType_logName.log
logType:日志类型,如 stats / monitor / access 等;
logName:日志描述。
这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。
-
4.在日志输出时,字符串变量之间的拼接使用占位符的方式。
说明:因为 String 字符串的拼接会使用 StringBuilder 的 append() 方式,有一定的性能损耗。使用占位符仅是替换动作,可以有效提升性能。
正例:logger.debug("Processing trade with id: {} and symbol: {}", id, symbol);
-
5.对于 trace / debug / info 级别的日志输出,必须进行日志级别的开关判断。
-
6.避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。
三、单元测试
-
1.好的单元测试必须遵守 AIR 原则。
说明:单元测试在线上运行时,感觉像空气(AIR)一样并不存在,但在测试质量的保障上,却是非常关
键的。好的单元测试宏观上来说,具有自动化、独立性、可重复执行的特点。
A:Automatic(自动化)
I:Independent(独立性)
R:Repeatable(可重复)
-
2.单元测试应该是全自动执行的,并且非交互式的。测试用例通常是被定期执行的,执行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。单元测试中不准使用 System.out 来进行人肉验证,必须使用 assert 来验证。
-
3.保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。
-
4.单元测试是可以重复执行的,不能受到外界环境的影响。
-
5.对于单元测试,要保证测试粒度足够小,有助于精确定位问题。单测粒度至多是类级别,一般是方法级别。
-
6.单元测试代码必须写在如下工程目录:src / test / java,不允许写在业务代码目录下。
说明:源码编译时会跳过此目录,而单元测试框架默认是扫描此目录。
四、安全规约
-
2.用户敏感数据禁止直接展示,必须对展示数据进行脱敏。
说明:中国大陆个人手机号码显示为:1370969,隐藏中间 4 位,防止隐私泄露。
-
3.用户输入的 SQL 参数严格使用参数绑定或者 METADATA 字段值限定,防止 SQL 注入,禁止字符串拼接 SQL 访问数据库。
-
4.用户请求传入的任何参数必须做有效性验证。
五、MySQL数据库
(一)建表规约
-
1.表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint(1 表示是,0 表示否)。
说明:任何字段如果为非负数,必须是 unsigned。
注意:POJO 类中的任何布尔类型的变量,都不要加 is 前缀,所以,需要在
设置从 is_xxx 到 Xxx 的映射关系。数据库表示是与否的值,使用 tinyint 类型,坚持 is_xxx 的命名方式是为了明确其取
值含义与取值范围。
正例:表达逻辑删除的字段名 is_deleted,1 表示删除,0 表示未删除。
-
2.表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
说明:MySQL 在 Windows 下不区分大小写,但在 Linux 下默认是区分大小写。因此,数据库名、表名、字段名,都不允许出现任何大写字母,避免节外生枝。
-
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 类型。
(二)索引规约
- 1.业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。
- 2.超过三个表禁止 join。需要 join 的字段,数据类型必须绝对一致;多表关联查询时,保证被关联的字段需要有索引。
- 3.在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度即可。
(三)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。
-
8.数据订正(特别是删除、修改记录操作)时,要先 select,避免出现误删除,确认无误才能执行更新语句。
-
10.如果有国际化需要,所有的字符存储与表示,均以 utf-8 编码,注意字符统计函数的区别。
说明:
SELECT LENGTH("轻松工作"); 返回为 12。
SELECT CHARACTER_LENGTH("轻松工作"); 返回为 4。
如果需要存储表情,那么选择 utf8mb4 来进行存储,注意它与 utf-8 编码的区别。
-
11.TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但TRUNCATE 无事务且不触发 trigger,有可能造成事故,故不建议在开发代码中使用此语句。
说明:TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同。
(四)ORM映射
-
1.在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。
说明:1)增加查询分析器解析成本。
2)增减字段容易与 resultMap 配置不一致。
3)无用字段增加网络消耗,尤其是 text 类型的字段。
-
2.POJO 类的布尔属性不能加 is,而数据库字段必须加 is_,要求在 resultMap 中进行字段与属性之间的映射。
-
4.sql.xml 配置参数使用:#{},#param# 不要使用${} 此种方式容易出现 SQL 注入。
六、工程结构
略;暂时看不懂。
七、设计规约
略;暂时看不懂。
专有名称解释
- 1.POJO(Plain Ordinary Java Object):POJO 专指只有 setter / getter / toString 的简单类,包括 DO / DTO / BO / VO 等。
- 2.GAV(GroupId、ArtifactctId、Version): Maven 坐标,是用来唯一标识 jar 包。
- 4.ORM(Object Relation Mapping): 对象关系映射,对象领域模型与底层数据之间的转换。
- 5.NPE(java.lang.NullPointerException): 空指针异常。
- 8.OOM(Out Of Memory): 源于 java.lang.OutOfMemoryError,当 JVM 没有足够的内存来为对象分配空间并且垃圾回收器也无法回收空间时,系统出现的严重状况。
小结
2019-10-03记
虽然有很多还看不懂,但看完收获还是有的。
感觉收获最多的是OOP规约和集合处理这方面的,因为有些问题自己也碰到过,例如:
- 包装类 Integer 的比较,自己就曾使用过 == 判断踩过坑。
- 浮点数精度缺失问题,其实最早在大一刚学编程用 c 语言就碰到过这个问题,是一个判断点是否在圆上的问题,当时还不知道原因,也算是后知后觉了;BigDecimal 用 double 参数构造导致数值不对也是这个道理。
- 集合类转换数组,数组转换集合类,这两个真是大坑问题;当时大二刚学 Java 时,就知道集合类有个 toArray 方法,Arrays 有个 asList 方法,但每次用每次查,后来下定决心搞懂了,知道了 toArray 方法要传长度为 0 的空数组,asList 方法返回的只是 Arrays 的内部类,并不能直接当集合用,要用就只能作为参数创建一个新的集合,现在看到记忆也更深刻了。
- 集合在循环里 remove 的事我也干过,当时查过解决办法 ,使用 Iterator 是一种,还有就算倒着来遍历也是可以 remove 的。
命名、代码格式、并发、控制语句、注释、异常处理等方面的也有所收获,不过在写代码还需要多加注意,才能有更深的印象。MySQL 方面的也是。
至于其它如单元测试、安全规约、工程结构、设计规约这些,还不是很懂,不了解,也看了一下,懵懵的。
看完也就告一段落了,等以后提升水平再来可能又是另一番体会。
┆ 然 ┆ ┆ ┆ ┆ 可 ┆ ┆ ┆ 等 ┆ 暖 ┆
┆ 而 ┆ 始 ┆ ┆ ┆ 是 ┆ 将 ┆ ┆ 你 ┆ 一 ┆
┆ 你 ┆ 终 ┆ 大 ┆ ┆ 我 ┆ 来 ┆ ┆ 如 ┆ 暖 ┆
┆ 没 ┆ 没 ┆ 雁 ┆ ┆ 在 ┆ 也 ┆ ┆ 试 ┆ 这 ┆
┆ 有 ┆ 有 ┆ 也 ┆ ┆ 这 ┆ 会 ┆ ┆ 探 ┆ 生 ┆
┆ 来 ┆ 来 ┆ 没 ┆ ┆ 里 ┆ 在 ┆ ┆ 般 ┆ 之 ┆
┆ ┆ ┆ 有 ┆ ┆ ┆ 这 ┆ ┆ 降 ┆ 凉 ┆
┆ ┆ ┆ 来 ┆ ┆ ┆ 里 ┆ ┆ 临 ┆ 薄 ┆