A)、Service/DAO 层方法命名规约
1.1)、获取单个对象的方法用 get 做前缀。
1.2)、获取多个对象的方法用 list 做前缀,复数形式结尾如:listObjects。
1.3)、获取统计值的方法用 count 做前缀。
1.4)、插入的方法用 save/insert 做前缀。
1.5)、删除的方法用 remove/delete 做前缀。
1.6)、修改的方法用 update 做前缀。
B)、领域模型命名规约
1.1)、数据对象:xxxDO,xxx 即为数据表名。
1.2)、数据传输对象:xxxDTO,xxx 为业务领域相关的名称。
1.3)、展示对象:xxxVO,xxx 一般为网页名称。
1.4)、POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO。
2.1)、【强制】所有的 POJO 类属性必须使用包装数据类型。
2.2)、【强制】RPC 方法的返回值和参数必须使用包装数据类型。
2.3)、【推荐】所有的局部变量使用基本数据类型。
说明:POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何 NPE 问题,或者入库检查,都由使用者来保证。
正例:数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。
反例:比如显示成交总额涨跌情况,即正负 x%,x 为基本数据类型,调用的 RPC 服务,调用不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示成中划线。所以包装数据类型的 null 值,能够表示额外的信息,如:远程调用失败,异常退出。
说明:注意 serialVersionUID 不一致会抛出序列化运行时异常。
说明:在方法执行抛出异常时,可以直接调用 POJO 的 toString()方法打印其属性值,便于排查问题。
说明:使用 toArray 带参方法,入参分配的数组空间不够大时,toArray 方法内部将重新分配内存空间,并返回新数组地址;如果数组元素个数大于实际所需,下标为[ list.size() ]的数组元素将被置为 null,其它数组元素保持原值,因此最好将方法入参数组大小定义与集
合元素个数一致。
正例:
List<String> list = new ArrayList<String>(2);
list.add("guan");
list.add("bao");
String[] array = new String[list.size()];
array = list.toArray(array);
反例:直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现 ClassCastException 错误。
说明:asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList 体现的是适配器模式,只是转换接口,后台的数据仍是数组。
String[] str = new String[] { "you", "wu" };
List list = Arrays.asList(str);
第一种情况:list.add(“yangguanbao”); 运行时异常。
第二种情况:str[0] = “gujin”; 那么 list.get(0)也会随之修改。
正例:
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);
}
}
说明:以上代码的执行结果肯定会出乎大家的意料,那么试一下把“1”换成“2”,会是同样的结果吗?
说明:菱形泛型,即 diamond,直接使用<>来指代前边已经指定的类型。
正例:
// <> diamond 方式
HashMap<String, String> userCache = new HashMap<>(16);
// 全省略方式
ArrayList<User> users = new ArrayList(10);
说明:HashMap 使用 HashMap(int initialCapacity) 初始化。
正例:initialCapacity = (需要存储的元素个数 / 负载因子) + 1。注意负载因子(即 loaderfactor)默认为 0.75,如果暂时无法确定初始值大小,请设置为 16(即默认值)。
反例:HashMap 需要放置 1024 个元素,由于没有设置容量初始大小,随着元素不断增加,容量 7 次被迫扩大,resize 需要重建 hash 表,严重影响性能。
说明:keySet 其实是遍历了 2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出 key 所对应的 value。而 entrySet 只是遍历了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8,使用 Map.foreach 方法。
正例:values()返回的是 V 值集合,是一个 list 集合对象;keySet()返回的是 K 值集合,是一个 Set 集合对象;entrySet()返回的是 K-V 值组合集合。
Map<String, String> map = ...;
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
...
}
反例:
Map<String, String> map = ...;
for (String key : map.keySet()) {
String value = map.get(key);
...
}
主键索引名为:pk_字段名;
唯一索引名为:uk_字段名;
普通索引名则为:idx_字段名。
说明:pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的简称。
说明:float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不正确的结果。如果存储的数据范围超过decimal 的范围,建议将数据拆成整数和小数分开存储。
说明:其中 id 必为主键,类型为 bigint unsigned、单表时自增、步长为 1。gmt_create,gmt_modified 的类型均为 datetime 类型,前者现在时表示主动创建,后者过去分词表示被动更新。
说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。
说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。
说明:即使双表 join 也要注意表索引、SQL 性能。
说明: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
说明:count(*) 会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。
正例:可以使用如下方式来避免 sum 的 NPE 问题:
SELECT
IF
( ISNULL( SUM(g) ), 0, SUM(g) )
FROM
TABLE;
说明:以学生和成绩的关系为例,学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
说明:参见定义 POJO 类以及数据库字段定义规定,在 中增加映射,是必须的。在 MyBatis Generator 生成的代码中,需要进行对应的修改。
Tomcat 6 及 以下版本
在 Tomcat 文件夹下的 conf 文件中的 server.xml 配置中添加:
// 0 表示不限制大小
maxPostSize="0"
Tomcat 7 及 以上版本
在 Tomcat 文件夹下的 conf 文件中的 server.xml 配置中添加:
// -1 表示不限制大小
maxPostSize="-1"
maxPostSize:指定 POST 方式请求的最大量,没有指定默认为2097152。
maxHttpHeaderSize ="102400"
maxHttpHeaderSize:HTTP 请求和响应头的最大量,以字节为单位,默认值为4096字节。
Q:BeanPostProcessor before instantiation of bean failed
A:缺少AspectJ 注解
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.6.8version>
dependency>
ps:主表、副表必须有关联字段。
UPDATE t1
JOIN t2
ON t1.t2_id = t2.id
SET t1.xxx = '',t2.yyy = ''
WHERE t1.id = 1
<if test="xxx != null and xxx.trim() != ''">
and xxx = #{xxx}
if>
明确一个概念,对方法的调用,即使方法中只有一句语句,也是有消耗的,包括创建栈帧、调用方法时保护现场、调用方法完毕时恢复现场等。所以例如下面的操作:
for (int i = 0; i < list.size(); i++){
}
建议替换为:
for (int i = 0, int length = list.size(); i < length; i++){
}
这样,在 list.size()很大的时候,就减少了很多的消耗。
异常对性能不利。抛出异常首先要创建一个新的对象,Throwable 接口的构造函数调用名为 fillInStackTrace() 的本地同步方法,fillInStackTrace() 方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,Java 虚拟机就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。异常只能用于错误处理,不应该用来控制程序流程。
比如 ArrayList、LinkedLlist、StringBuilder、StringBuffer、HashMap、HashSet 等等,以 StringBuilder 为例:
A)、StringBuilder(); // 默认分配16个字符的空间
B)、StringBuilder(int size); // 默认分配 size 个字符的空间
C)、StringBuilder(String str); // 默认分配16个字符 +str.length() 个字符空间
可以通过类(这里指的不仅仅是上面的 StringBuilder)的来设定它的初始化容量,这样可以明显地提升性能。比如 StringBuilder 吧,length 表示当前的 StringBuilder 能保持的字符数量。因为当 StringBuilder 达到最大容量的时候,它会将自身容量增加到当前的2倍再加2,无论何时只要 StringBuilder 达到它的最大容量,它就不得不创建一个新的字符数组然后将旧的字符数组内容拷贝到新字符数组中-这是十分耗费性能的一个操作。试想,如果能预估到字符数组中大概要存放5000个字符而不指定长度,最接近5000的2次幂是4096,每次扩容加的2不管,那么:
(1)、在4096 的基础上,再申请8194个大小的字符数组,加起来相当于一次申请了12290个大小的字符数组,如果一开始能指定5000个大小的字符数组,就节省了一倍以上的空间;
(2)、把原来的4096个字符拷贝到新的的字符数组中去。
这样,既浪费内存空间又降低代码运行效率。所以,给底层以数组实现的集合、工具类设置一个合理的初始化容量是错不了的,这会带来立竿见影的效果。但是,注意,像HashMap这种是以数组+链表实现的集合,别把初始大小和你估计的大小设置得一样,因为一个table上只连接一个对象的可能性几乎为0。初始大小建议设置为2的N次幂,如果能估计到有2000个元素,设置成 new HashMap(128)、new HashMap(256) 都可以。
例如:
for (val = 0; val < 100000; val += 5){
a = val * 8;
b = val / 2;
}
用移位操作可以极大地提高性能,因为在计算机底层,对位的操作是最方便、最快的,因此建议修改为:
for (val = 0; val < 100000; val += 5){
a = val << 3;
b = val >> 1;
}
移位操作虽然快,但是可能会使代码不太好理解,因此最好加上相应的注释。
例如:
for (int i = 1; i <= count; i++){
Object obj = new Object();
}
这种做法会导致内存中有 count 份 Object 对象引用存在,count 很大的话,就耗费内存了,建议为改为:
Object obj = null;
for (int i = 0; i <= count; i++) {
obj = new Object();
}
这样的话,内存中只有一份 Object 对象引用,每次 new Object() 的时候,Object 对象引用指向不同的 Object 罢了,但是内存中只有一份,这样就大大节省了内存空间了。
因为这毫无意义,这样只是定义了引用为 static final,数组的内容还是可以随意改变的,将数组声明为 public 更是一个安全漏洞,这意味着这个数组可以被外部类所改变。
使用单例可以减轻加载的负担、缩短加载的时间、提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面:
(1)、控制资源的使用,通过线程同步来控制资源的并发访问。
(2)、控制实例的产生,以达到节约资源的目的。
(3)、控制数据的共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信。
要知道,当某个对象被定义为 static 的变量所引用,那么 gc 通常是不会回收这个对象所占有的堆内存的,如:
public class A{
private static B b = new B();
}
此时 静态变量b 的生命周期与 A类 相同,如果 A类 不被卸载,那么 引用B 指向的 B对象 会常驻内存,直到程序终止。
在Java 集合类库中,List 的 contains 方法普遍时间复杂度为O(n),若代码中需要频繁调用 contains 方法查找数据则先将集合 list 转换成 HashSet 实现,将O(n) 的时间复杂度将为O(1)。
反例:
// 频繁调用Collection.contains() 反例
List<Object> list = new ArrayList<>();
for (int i = 0; i <= Integer.MAX_VALUE; i++){
// 时间复杂度为O(n)
if (list.contains(i))
System.out.println("list contains "+ i);
}
正例:
// 频繁调用Collection.contains() 正例
List<Object> list = new ArrayList<>();
Set<Object> set = new HashSet<>();
for (int i = 0; i <= Integer.MAX_VALUE; i++){
// 时间复杂度为O(1)
if (set.contains(i)){
System.out.println("list contains "+ i);
}
}
工具类是一堆静态字段和函数的集合,其不应该被实例化;但是,Java 为每个没有明确定义构造函数的类添加了一个隐式公有构造函数,为了避免不必要的实例化,应该显式定义私有构造函数来屏蔽这个隐式公有构造函数。
反例:
public class PasswordUtils {
// 工具类构造函数反例
private static final Logger LOG = LoggerFactory.getLogger(PasswordUtils.class);
public static final String DEFAULT_CRYPT_ALGO = "PBEWithMD5AndDES";
public static String encryptPassword(String aPassword) throws IOException {
return new PasswordUtils(aPassword).encrypt();
}
}
正例:
public class PasswordUtils {
// 工具类构造函数正例
private static final Logger LOG = LoggerFactory.getLogger(PasswordUtils.class);
// 定义私有构造函数来屏蔽这个隐式公有构造函数
private PasswordUtils(){}
public static final String DEFAULT_CRYPT_ALGO = "PBEWithMD5AndDES";
public static String encryptPassword(String aPassword) throws IOException {
return new PasswordUtils(aPassword).encrypt();
}
}
mysql int(10) 中10指的是:该字段下能输出显示的最大数字长度。括号里的数字叫数据的宽度,不同的数据类型对宽度的处理也不一样:
(1)、整数类型:这里显示的宽度和数据类型的取值范围是没有任何关系的,显示宽度只是指明 Mysql 最大可能显示的数字个数,数值的位数小于指定的宽度时会由空格填充;
如果插入了大于显示宽度的值,只要该值不超过该类型的取值范围,数值依然可以插入,而且能够显示出来。
如果你不设置宽度,系统将添加默认的宽度 tinyint(4)、smallint(6)、mediumint(9)、int(11)、bigint(20),这些默认的宽度是跟该类型的取值范围长度相关。
(2)、字符串类型:字符串类型这个宽度才真的用上了。不管是 char 还是 varchar,宽度都定义了字符串的最大长度;例如上面的 password varchar(20),如果你输入了一个21个字符的密码,那么保存和显示的只会是前20个字符,你将丢失一个字符信息,char 同理。由于 varchar 是变长存储的,所以实际开发中我们一般都把 varchar 的宽度设为最长255,它会根据实际数据长度变化,反正你没用完它也不会浪费空间。char 是定长存储,定义多长就是多长。
(3)、浮点和日期等数据类型:对数据的宽度没有要求,一般也不设置,默认是0。
使用 Collection.size() 来检测空逻辑上没有问题,但是使用 Collection.isEmpty() 使得代码更易读,并且可以获得更好的性能。任何 Collection.isEmpty() 实现的时间复杂度都是 O(1) ,但是某些 Collection.size() 实现的时间复杂度可能是 O(n) 。
反例:
if (collection.size() == 0) {
...
}
正例:
if (collection.isEmpty()) {
...
}
如果需要还需要检测 null,可采用 CollectionUtils.isEmpty(collection) 和 CollectionUtils.isNotEmpty(collection)。
在使用长整型常量值时,后面需要添加 L ,必须是大写的 L ,不能是小写的 l ,小写 l 容易跟数字 1 混淆而造成误解。
反例:
long value = 1l;
long max = Math.max(1L, 5);
正例:
long value = 1L;
long max = Math.max(1L, 5L);
当你编写一段代码时,使用魔法值可能看起来很明确,但在调试时它们却不显得那么明确了。这就是为什么需要把魔法值定义为可读取常量的原因。但是,-1、0 和 1不被视为魔法值。
反例:
for (int i = 0; i < 100; i++){
...
}
if (a == 100) {
...
}
正例:
private static final int MAX_COUNT = 100;
for (int i = 0; i < MAX_COUNT; i++){
...
}
if (count == MAX_COUNT) {
...
}
对于集合类型的静态成员变量,不要使用集合实现来赋值,应该使用静态代码块赋值。
反例:
private static Map<String, Integer> map = new HashMap<String, Integer>() {
{
put("a", 1);
put("b", 2);
}
};
private static List<String> list = new ArrayList<String>() {
{
add("a");
add("b");
}
};
正例:
private static Map<String, Integer> map = new HashMap<>();
static {
map.put("a", 1);
map.put("b", 2);
};
private static List<String> list = new ArrayList<>();
static {
list.add("a");
list.add("b");
};
Java 7 中引入了 try-with-resources 语句,该语句能保证将相关资源关闭,优于原来的 try-catch-finally 语句,并且使程序代码更安全更简洁。
反例:
private void handle(String fileName) {
BufferedReader reader = null;
try {
String line;
reader = new BufferedReader(new FileReader(fileName));
while ((line = reader.readLine()) != null) {
...
}
} catch (Exception e) {
...
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
...
}
}
}
}
正例:
private void handle(String fileName) {
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
String line;
while ((line = reader.readLine()) != null) {
...
}
} catch (Exception e) {
...
}
}
用 catch 语句捕获异常后,什么也不进行处理,就让异常重新抛出,这跟不捕获异常的效果一样,可以删除这块代码或添加别的处理。
反例:
private static String readFile(String fileName) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
String line;
StringBuilder builder = new StringBuilder();
while ((line = reader.readLine()) != null) {
builder.append(line);
}
return builder.toString();
} catch (Exception e) {
throw e;
}
}
正例:
private static String readFile(String fileName) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
String line;
StringBuilder builder = new StringBuilder();
while ((line = reader.readLine()) != null) {
builder.append(line);
}
return builder.toString();
}
}
当要把其它对象或类型转化为字符串时,使用 String.valueOf(value) 比 “”+value 的效率更高。
反例:
int i = 1;
String s = "" + i;
正例:
int i = 1;
String s = String.valueOf(i);
当一段代码过时,但为了兼容又无法直接删除,不希望以后有人再使用它时,可以添加 @Deprecated 注解进行标记。在文档注释中添加 @deprecated 来进行解释,并提供可替代方案
正例:
/**
* 保存
*
* @deprecated 此方法效率较低,请使用{@link newSave()}方法替换它
*/
@Deprecated
public void save(){
// do something
}
BigDecimal(double) 存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。
反例:
BigDecimal value = new BigDecimal(0.1D); // 0.100000000000000005551115...
正例:
BigDecimal value = BigDecimal.valueOf(0.1D); // 0.1
对象的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals 方法。当然,使用 java.util.Objects.equals() 方法是最佳实践。
反例:
public void isFinished(OrderStatus status) {
// 可能抛空指针异常
return status.equals(OrderStatus.FINISHED);
}
正例:
public void isFinished(OrderStatus status) {
return OrderStatus.FINISHED.equals(status);
}
public void isFinished(OrderStatus status) {
return Objects.equals(status, OrderStatus.FINISHED);
}
枚举通常被当做常量使用,如果枚举中存在公共属性字段或设置字段方法,那么这些枚举常量的属性很容易被修改。理想情况下,枚举中的属性字段是私有的,并在私有构造函数中赋值,没有对应的 Setter 方法,最好加上 final 修饰符。
反例:
public enum UserStatus {
DISABLED(0, "禁用"),
ENABLED(1, "启用");
public int value;
private String description;
private UserStatus(int value, String description) {
this.value = value;
this.description = description;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
正例:
public enum UserStatus {
DISABLED(0, "禁用"),
ENABLED(1, "启用");
private final int value;
private final String description;
private UserStatus(int value, String description) {
this.value = value;
this.description = description;
}
public int getValue() {
return value;
}
public String getDescription() {
return description;
}
}
字符串 String 的 split 方法,传入的分隔字符串是正则表达式!部分关键字(比如.| 等)需要转义
反例:
// 结果为[]
"a.ab.abc".split(".");
// 结果为["a", "|", "a", "b", "|", "a", "b", "c"]
"a|ab|abc".split("|");
正例:
// 结果为["a", "ab", "abc"]
"a.ab.abc".split("\\.");
// 结果为["a", "ab", "abc"]
"a|ab|abc".split("\\|");