SQL建模规范

建模原则

  1. 数据库字段小写,下划线连接;每个字段都需要由COMMENT,说明其业务含义。

  2. 使用id作为主键,建议类型为bigint(20), 其对应的java类型为Long。

    • Mybatis Plus框架自动生成主键插入(雪花算法)
    • 也可以使用数据库自增,当只有1个服务,数据量较小的时候建议选择,提升插入性能。
  3. update_time、create_time
    使用timestamp或者datetime作为时间类型的首选。Mybatis-Plus框架生成的相应Java类型都是LocalDateTime。大部分场景下2种类型可以互相替换,不过在时间范围和时区支持上有差异。
    使用MySQL的自动初始化和自动更新功能。 字段变化之后,update_time数据库自动更新,如下:

1
2
3
4
create_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
create_by varchar(36) DEFAULT NULL COMMENT '创建人',
update_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
update_by varchar(36) DEFAULT NULL COMMENT '更新人',
  1. 布尔型字段使用tinyint(1)即可。
  2. 参考《阿里巴巴Java开发手册(华山版).pdf》。

雪花算法实现

  1. IDGenerator接口声明,这里值得注意的是,雪花算法中提供了一个datacenterId用来避免在不同的集群(数据中心)产生碰撞。如果有多个集群,建议进行配置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* An interface for database ID generators.
*
* @author Sebastian Schaffert (sschaffert@apache.org)
*/
public interface IDGenerator {

/**
* Return the next unique id for the type with the given name using the generator's id generation strategy.
* @return
*/
long nextId();

/**
* Shut down this id generator, performing any cleanups that might be necessary.
*
*/
void shutdown();
}
  1. SnowflakeIDGenerator实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Random;

/**
* Generate unique IDs using the Twitter Snowflake algorithm (see https://github.com/twitter/snowflake). Snowflake IDs
* are 64 bit positive longs composed of:
* - 41 bits time stamp
* - 10 bits machine id
* - 12 bits sequence number
*
*
* @author Sebastian Schaffert (sschaffert@apache.org)
*/
public class SnowflakeIDGenerator implements IDGenerator {
private static Logger log = LoggerFactory.getLogger(SnowflakeIDGenerator.class);


private final long datacenterIdBits = 10L;
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
private final long sequenceBits = 12L;

private final long datacenterIdShift = sequenceBits;
private final long timestampLeftShift = sequenceBits + datacenterIdBits;
private final long sequenceMask = -1L ^ (-1L << sequenceBits);

private final long twepoch = 1288834974657L;
private long datacenterId;

private volatile long lastTimestamp = -1L;
private volatile long sequence = 0L;


public SnowflakeIDGenerator(long datacenterId) {
if(datacenterId == 0) {
try {
this.datacenterId = getDatacenterId();
} catch (SocketException | UnknownHostException | NullPointerException e) {
log.warn("SNOWFLAKE: could not determine machine address; using random datacenter ID");
Random rnd = new Random();
this.datacenterId = rnd.nextInt((int)maxDatacenterId) + 1;
}
} else {
this.datacenterId = datacenterId;
}

if (this.datacenterId > maxDatacenterId || datacenterId < 0){
log.warn("SNOWFLAKE: datacenterId > maxDatacenterId; using random datacenter ID");
Random rnd = new Random();
this.datacenterId = rnd.nextInt((int)maxDatacenterId) + 1;
}
log.info("SNOWFLAKE: initialised with datacenter ID {}", this.datacenterId);
}

protected long tilNextMillis(long lastTimestamp){
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}

protected long getDatacenterId() throws SocketException, UnknownHostException {
NetworkInterface network = null;

Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();
while (en.hasMoreElements()) {
NetworkInterface nint = en.nextElement();
if (!nint.isLoopback() && nint.getHardwareAddress() != null) {
network = nint;
break;
}
}

byte[] mac = network.getHardwareAddress();

Random rnd = new Random();
byte rndByte = (byte)(rnd.nextInt() & 0x000000FF);

// take the last byte of the MAC address and a random byte as datacenter ID
return ((0x000000FF & (long)mac[mac.length-1]) | (0x0000FF00 & (((long)rndByte)<<8)))>>6;
}


/**
* Return the next unique id for the type with the given name using the generator's id generation strategy.
*
* @return
*/
@Override
public synchronized long nextId() {
long timestamp = System.currentTimeMillis();
if(timestamp<lastTimestamp) {
log.warn("Clock moved backwards. Refusing to generate id for {} milliseconds.",(lastTimestamp - timestamp));
try {
Thread.sleep((lastTimestamp - timestamp));
} catch (InterruptedException e) {
}
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0;
}
lastTimestamp = timestamp;
long id = ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | sequence;

if(id < 0) {
log.warn("ID is smaller than 0: {}",id);
}
return id;
}

/**
* Shut down this id generator, performing any cleanups that might be necessary.
*
*/
@Override
public void shutdown() {
//To change body of implemented methods use File | Settings | File Templates.
}
}