Java代码开发规范

声明:本文基于《阿里巴巴代码规约-嵩山版》,在自己理解的基础上稍有扩展,希望帮到需要的朋友。

1、POJO 类中的任何布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误。

说明:在MySQL 建表约定表达是与否的变量采用 is_xxx 的命名方式,所以,需要在<resultMap>设置从 is_xxx 到 xxx 的映射关系。

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

2、POJO 类中的任何字段,需要遵循驼峰命名方式,不应该首字母为大写。

说明:POJO类中的任何字段,如果首字母为大写(不限定其他字母大写或小写),未遵循驼峰命名的方式,SpringBoot将自动进行转小写。

SpringBoot默认消息转换器为MappingJackson2HttpMessageConverter 

真正执行首字母自动转换的类为com.fasterxml.jackson.databind.util.BeanUtil#okNameForMutator方法

默认转换逻辑为:

①、如果为标准输出,越过get前缀后

       如果第一个字母为小写,直接从小写往后截取到最后原样输出。

       第一个字母、第二个字母都为大写,直接越过get前缀截取到最后原样输出。

       例如:NAME->NAME

       否则,首字母转为小写,后面截取到最后原样输出。

②、如果为优化输出,越过get前缀后

       如果第一个字母为小写,直接从小写往后截取到最后原样输出。

       否则,首字母转为小写,后面截取到最后原样输出。  例如:NAME->nAME

3、不允许任何魔法值(即未经预先定义的常量)直接出现在代码中(先声明,后使用)。

说明:

反例:开发者 A 定义了缓存的 key,然后开发者 B 使用缓存时少了下划线,即 key 是"Id#taobao"+tradeId,导致出现故障

String key = "Id#taobao_" + tradeId;

4、在 long 或者 Long 赋值时,数值后使用大写字母 L,不能是小写字母 l,小写容易跟数字混淆,造成误解。

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

5、所有的覆写方法,必须加@Override 注解。

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

6、不能使用过时的类或方法。

说明:java.net.URLDecoder 中的方法 decode(String encodeStr) 这个方法已经过时,应该使用双参数decode(String source, String encode)。接口提供方既然明确是过时接口,那么有义务同时提供新的接口;作为调用方来说,有义务去考证过时方法的新实现是什么。

7、Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。

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

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

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

说明:推荐使用 JDK7 引入的工具类 java.util.Objects#equals(Object a, Object b)

8、所有整型包装类对象之间值的比较,全部使用 equals 方法比较。

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

Integer a=127;

Integer b=127;

Integer c=128;

Integer d=128;

a==b true
c==d false

扩展:其他基本数据类型的包装类也都有这个缓存池,各类型缓存池范围如下:

 9、浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals来判断。

说明:浮点数采用“尾数+阶码”的编码方式,类似于科学计数法的“有效数字+指数”的表示方式。二进制无法精确表示大部分的十进制小数

反例:
float a = 1.0F - 0.9F;
float b = 0.9F - 0.8F;
System.out.println(a == b);
Float x = Float.valueOf(a);
Float y = Float.valueOf(b);
System.out.println(x.equals(y));
System.out.println(x == y);

false
false
false

正例:
(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.compareTo(y) == 0) {
    System.out.println("true");

}

10、BigDecimal 的等值比较应使用 compareTo()方法,而不是 equals()方法。

说明:equals()方法会比较值和精度(1.0 与 1.00 返回结果为 false),而 compareTo()则会忽略精度。

11、禁止使用构造方法 BigDecimal(double)的方式把 double 值转化为 BigDecimal 对象。

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

如:BigDecimal g = new BigDecimal(0.1F); 实际的存储值为:0.10000000149
正例:优先推荐入参为 String 的构造方法,或使用 BigDecimal 的 valueOf 方法,此方法内部其实执行了
Double 的 toString,而 Double 的 toString 按 double 的实际能表达的精度对尾数进行了截断。
BigDecimal recommend1 = new BigDecimal("0.1");
BigDecimal recommend2 = BigDecimal.valueOf(0.1);

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

说明:

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

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

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

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

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

正例:数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。

反例:某业务的交易报表上显示成交总额涨跌情况,即正负 x%,

x 为基本数据类型,调用的 RPC 服务,调

用不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示成中划线-。所以包装数据类型

的 null 值,能够表示额外的信息,如:远程调用失败,异常退出。

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

说明:

1)不允许被继承的类,如:String 类。

2)不允许修改引用的域对象,如:POJO 类的域变量。

3)不允许被覆写的方法,如:POJO 类的 setter 方法。

4)不允许运行过程中重新赋值的局部变量。

5)避免上下文重复使用一个变量,使用 final 关键字可以强制重新定义一个变量,方便更好地进行重构。

14、循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展。

说明:下例中,反编译出的字节码文件显示每次循环都会 new 出一个 StringBuilder 对象,然后进行 append

操作,最后通过 toString 方法返回 String 对象,造成内存资源浪费。

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

15、日期格式化时,传入 pattern 中表示年份统一使用小写的 y。

说明:

日期格式化时,yyyy 表示当天所在的年,而大写的 YYYY 代表是 week in which year(JDK7 之后引入的概念),意思是当天所在的周属于的年份,一周从周日开始,周六结束,只要本周跨年,返回的 YYYY就是下一年。

正例:表示日期和时间的格式如下所示:

new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")

扩展:

时间格式规则:

YYYY 代表 Week Year
yyyy 代表year
MM 代表 月(Month)
dd 当月的天
HH代表24小时制
mm:分,2位
ss:秒,2位
yyyy-MM-dd HH:mm:ss:代表将时间转换为24小时制,例: 2018-06-27 15:24:21
yyyy-MM-dd hh:mm:ss:代表将时间转换为12小时制,例: 2018-06-27 03:24:21

16、不要在程序中写死一年为 365 天,避免在公历闰年时出现日期转换错误或程序逻辑错误。

说明:

正例:
// 获取今年的天数
int daysOfThisYear = LocalDate.now().lengthOfYear();
// 获取指定某年的天数
LocalDate.of(2011, 1, 1).lengthOfYear();
反例:
// 第一种情况:在闰年 366 天时,出现数组越界异常
int[] dayArray = new int[365];
// 第二种情况:一年有效期的会员制,今年 1 月 26 日注册,硬编码 365 返回的却是 1 月 25 日
Calendar calendar = Calendar.getInstance();
calendar.set(2020, 1, 26);
calendar.add(Calendar.DATE, 365);

17、使用枚举值来指代月份。如果使用数字,注意 Date,Calendar 等日期相关类的月份month 取值在 0-11 之间。

说明:参考 JDK 原生注释,Month value is 0-based. e.g., 0 for January.

正例:

Calendar.JANUARY,Calendar.FEBRUARY,Calendar.MARCH 等来指代相应月份来进行传参或比较。

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

说明:

正例:
objectThreadLocal.set(userInfo);
try {
    // ...
} finally {
    objectThreadLocal.remove();
}

19、关于 hashCode 和 equals 的处理,遵循如下规则:

1)只要覆写 equals,就必须覆写 hashCode。

2)因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须覆写这两种方法。

3)如果自定义对象作为 Map 的键,那么必须覆写 hashCode 和 equals。

说明:String 因为覆写了 hashCode 和 equals 方法,所以可以愉快地将 String 对象作为 key 来使用。

20、判断所有集合内部的元素是否为空,使用 isEmpty()方法,而不是 size()==0 的方式。

21、在使用 java.util.stream.Collectors 类的 toMap()方法转为 Map 集合时,一定要使用含有参数类型为 BinaryOperator,参数名为 mergeFunction 的方法,否则当出现相同 key值时会抛出 IllegalStateException 异常。

说明:参数 mergeFunction 的作用是当出现 key 重复时,自定义对 value 的处理策略。

正例:
List<Pair<String, Double>> pairArrayList = new ArrayList<>(3);
pairArrayList.add(new Pair<>("version", 12.10));
pairArrayList.add(new Pair<>("version", 12.19));
pairArrayList.add(new Pair<>("version", 6.28));
Map<String, Double> map = pairArrayList.stream().collect(
// 生成的 map 集合中只有一个键值对:{version=6.28}
        Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2));
反例:
String[] departments = new String[] {"iERP", "iERP", "EIBU"};
// 抛出 IllegalStateException 异常
Map<Integer, String> map = Arrays.stream(departments)
        .collect(Collectors.toMap(String::hashCode, str -> str));

22、在使用 java.util.stream.Collectors 类的 toMap()方法转为 Map 集合时,一定要注意当 value 为 null 时会抛 NPE 异常。

说明:在 java.util.HashMap 的 merge 方法里会进行如下的判断:

if (value == null || remappingFunction == null)
    throw new NullPointerException();
反例:
List<Pair<String, Double>> pairArrayList = new ArrayList<>(2);
pairArrayList.add(new Pair<>("version1", 8.3));
pairArrayList.add(new Pair<>("version2", null));
Map<String, Double> map = pairArrayList.stream().collect(
// 抛出 NullPointerException 异常
        Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2));
正例:
Map<Integer,String> personMap= personList.stream().collect(Collectors.toMap(Person::getId, 
person -> Optional.ofNullable(person.getName()).orElse("null"),(key1, key2) -> key2));

23、ArrayList 的 subList 结果不可强转成 ArrayList,否则会抛出 ClassCastException 异常:java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。

说明:subList()返回的是 ArrayList 的内部类 SubList,并不是 ArrayList 本身,而是 ArrayList 的一个视图,对于 SubList 的所有操作最终会反映到原列表上。

正例:

ArrayList<Object> list2 = new ArrayList<>(list.subList(0, 1));
System.out.println(list2);

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

说明:因为没有实现这些方法。

反例:

HashMap<String, Integer> map = new HashMap<>();
map.put("key1", 1);
Set set = map.keySet();
set.add("key4");

entrySet() 和 keySet() 和 values()的使用总结:

1、一般只使用它们的迭代器,用于获取所有键值对。
2、除了迭代器,对元素进行操作的方法不建议使用,容易出错。
3、如果要高效遍历key-value,建议使用entrySet(),keySet()也可以做到,但需要通过key来获取value,多增加了一步。
4、获取所有key就使用keySet(),获取所有value就使用values()
5、keySet(),values(),entrySet()返回的集合是没有额外的数据实体的,都依赖于HashMap的数组+链表结构的Entry数据。所以称返回的是视图。

25、在 subList 场景中,高度注意对父集合元素的增加或删除,均会导致子列表的遍历、增加、删除产生 ConcurrentModificationException 异常。

说明:

ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(2);
arrayList.add(3);
List<Integer> subList = arrayList.subList(1, 3);
for (Integer integer : subList) {
    System.out.println(" "+integer);
}
arrayList.remove(0);
for (Integer integer : subList) {
    System.out.println(" "+integer);
}

分析:ArrayList中有个protected transient int modCount = 0;用来记录当前ArrayList被修改的次数。比如add(),remove()等都会导致modeCount增加

ArrayList.subList()会生成一个SubList的对象,SubList中有个对应modCount同步ArrayList中的modeCount,SubList对象每次再遍历时,会将自己的modeCount与ArrayList的modeCount进行对比,如果两个值不一样就会报异常:ConcurrentModificationException

26、使用集合转数组的方法,必须使用集合的 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 正在变大的情况下,负面影响与 2 相同。
4) 大于 size,空间浪费,且在 size 处插入 null 值,存在 NPE 隐患。

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

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

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

说明:

asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList 体现的是适配

器模式,只是转换接口,后台的数据仍是数组。

String[] str = new String[] { "chen", "yang", "hao" };

List list = Arrays.asList(str);

第一种情况:list.add("yangguanbao"); 运行时异常。

第二种情况:str[0] = "change"; 也会随之修改,反之亦然。

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

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

正例:
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);
 } }

30、集合初始化时,指定集合初始值大小。

说明:HashMap 使用 HashMap(int initialCapacity) 初始化,如果暂时无法确定集合大小,那么指定默认值(16)即可。

正例:initialCapacity = (需要存储的元素个数 / 负载因子) + 1。注意负载因子(即 loader factor)默认
为 0.75,如果暂时无法确定初始值大小,请设置为 16(即默认值)。
反例:
HashMap 需要放置 1024 个元素,由于没有设置容量初始大小,随着元素增加而被迫不断扩容,
resize()方法总共会调用 8 次,反复重建哈希表和数据迁移。当放置的集合元素个数达千万级时会影响程序
性能。

31、使用 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 值组合集合。

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

说明:

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

说明:

Executors 返回的线程池对象的弊端如下:

1)FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

2)CachedThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

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

说明:

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

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

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

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

说明:Lock 对象的 unlock 方法在执行时,它会调用 AQS 的 tryRelease 方法(取决于具体实现类),如果当前线程不持有锁,则抛出 IllegalMonitorStateException 异常。

正例:
Lock lock = new XxxLock();
// ...
boolean isLocked = lock.tryLock();
if (isLocked) {
    try {
        doSomething();
        doOthers();
    } finally {
        lock.unlock();
} }

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

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

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

说明:注意,子线程抛出异常堆栈,不能在主线程 try-catch 到

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

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

正例:在 JDK7 之后,可以直接使用 API ThreadLocalRandom,而在 JDK7 之前,需要编码保证每个线程持有一个单独的 Random 实例。

39、通过双重检查锁(double-checked locking)(在并发场景下)存在延迟初始化的优化问题隐患(可参考 The "Double-Checked Locking is Broken" Declaration),推荐解决方案中较为简单一种(适用于 JDK5 及以上版本),将目标属性声明为 volatile 型,比如将 helper 的属性声明修改为`private volatile Helper helper = null;`。

正例:

正例:
public class LazyInitDemo {
    private volatile Helper helper = null;
    public Helper getHelper() {
        if (helper == null) {
            synchronized (this) {
                if (helper == null) { helper = new Helper(); }
            }
        }
        return helper;
    }
    // other methods and fields... 
}

40、当 switch 括号内的变量类型为 String 并且此变量为外部参数时,必须先进行 null判断

说明:

反例:如下的代码输出是什么?

public static void main(String[] args) {
    method(null);
}

public static void method(String param) {
    switch (param) {
        // 肯定不是进入这里
        case "sth":
            System.out.println("it's sth");
            break;
        // 也不是进入这里
        case "null":
            System.out.println("it's null");
            break;
        // 也不是进入这里
        default:
            System.out.println("default");
    }
}

41、类、类属性、类方法的注释必须使用 Javadoc 规范,使用/**内容*/格式,不得使用// xxx 方式。

说明:在 IDE 编辑窗口中,Javadoc 方式会提示相关注释,生成 Javadoc 可以正确输出相应注释;在 IDE中,工程调用方法时,不进入方法即可悬浮提示方法、参数、返回值的意义,提高阅读效率。

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

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

43、HTTP 请求通过 URL 传递参数时,不能超过 2048 字节。

说明:不同浏览器对于 URL 的最大长度限制略有不同,并且对超出最大长度的处理逻辑也有差异,2048字节是取所有浏览器的最小值。

反例:某业务将退货的商品 id 列表放在 URL 中作为参数传递,当一次退货商品数量过多时,URL 参数超长,传递到后端的参数被截断,导致部分商品未能正确退货。

44、不要在 finally 块中使用 return。

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

45、用户敏感数据禁止直接展示,必须对展示数据进行脱敏。

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

46、用户输入的 SQL 参数严格使用参数绑定或者 METADATA 字段值限定,防止 SQL 注入,禁止字符串拼接 SQL 访问数据库。

说明:某系统签名大量被恶意修改,即是因为对于危险字符 # --没有进行转义,导致数据库更新时,where后边的信息被注释掉,对全库进行更新。

47、数据库小数类型为 decimal,禁止使用 float 和 double。

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

48、表必备三字段:id, create_time, update_time。

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

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

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

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

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

51、利用覆盖索引来进行查询操作,避免回表。

说明:如果一本书需要知道第 11 章是什么标题,会翻开第 11 章对应的那一页吗?目录浏览一下就好,这个目录就是起到覆盖索引的作用。正例:能够建立索引的种类分为主键索引、唯一索引、普通索引三种,

而覆盖索引只是一种查询的一种效果,用 explain 的结果,extra 列会出现:using index。

52、不要使用 count(列名)或 count(常量)来替代 count(*),count(*)是 SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。

说明:count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。

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

说明:

正例:可以使用如下方式来避免 sum 的 NPE 问题:SELECT IFNULL(SUM(column), 0) FROM table;

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

说明:

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

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

说明:

参见定义 POJO 类以及数据库字段定义规定,在 sql.xml 增加映射,是必须的。

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

说明:

#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号,如:order by #user_id#,如果传入的值是111,那么解析成sql时的值为order by "111", 如果传入的值是id,则解析成的sql为order by "id"
  
$将传入的数据直接显示生成在sql中,如:order by $user_id$,如果传入的值是111,那么解析成sql时的值为order by user_id,  如果传入的值是id,则解析成的sql为order by id.

更多优质内容,感兴趣的朋友请移步至个人公众号,谢谢支持😜😜......

公众号:wenyixicodedog

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值