postgresql
DROP TABLE IF EXISTS base;
CREATE TABLE base(
id SERIAL NOT NULL,
data_id VARCHAR(32) NOT NULL,
created_by VARCHAR(255),
revision INTEGER,
state INTEGER NOT NULL,
status INTEGER NOT NULL,
create_time TIMESTAMP NOT NULL,
update_time TIMESTAMP NOT NULL,
remark VARCHAR(255),
deleted INTEGER,
tenant_id INTEGER NOT NULL,
PRIMARY KEY (id)
);
COMMENT ON TABLE base IS '表基础格式';
COMMENT ON COLUMN base.data_id IS '数据id';
COMMENT ON COLUMN base.created_by IS '创建人';
COMMENT ON COLUMN base.revision IS '乐观锁';
COMMENT ON COLUMN base.state IS '(隐藏、显示)、(是、否)、(可用、不可用)、(启用、禁用)';
COMMENT ON COLUMN base.status IS '状态';
COMMENT ON COLUMN base.create_time IS '创建时间';
COMMENT ON COLUMN base.update_time IS '更新时间';
COMMENT ON COLUMN base.remark IS '备注';
COMMENT ON COLUMN base.deleted IS '逻辑删除标记';
COMMENT ON COLUMN base.tenant_id IS '租户id';
mysql
DROP TABLE IF EXISTS base;
CREATE TABLE base(
`id` bigint unsigned AUTO_INCREMENT COMMENT 'id' ,
`data_id` bigint unsigned NOT NULL COMMENT '数据id' ,
`created_by` VARCHAR(32) COMMENT '创建人' ,
`revision` INT(1) unsigned COMMENT '乐观锁' ,
`state` INT(1) unsigned NOT NULL DEFAULT '0' COMMENT '(隐藏、显示)、(是、否)、(可用、不可用)、(启用、禁用)' ,
`status` INT(1) unsigned NOT NULL DEFAULT '0' COMMENT '状态' ,
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`remark` VARCHAR(32) COMMENT '备注' ,
`deleted` INT(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除标记' ,
`tenant_id` INT(11) unsigned zerofill NOT NULL COMMENT '租户id' ,
PRIMARY KEY (id)
) COMMENT = '表基础格式';
【强制】必须显式指定主键, 勿用复合主键. 主键的命名统一为:id
【强制】bigint id 无意义主键id
【强制】bigint unsigned data_id 数据id,有的业务需要进行先插入再根据id进行操作
【强制】varchar(32) created_by 操作人
【强制】int(1) unsign deleted 逻辑删除标记.默认值0,删除值1
【强制】datetime create_time 默认值 CURRENT_TIMESTAMP 额外值 DEFAULT_GENERATED
【强制】datetime update_time 默认值 CURRENT_TIMESTAMP 额外值 DEFAULT_GENERATED on update CURRENT_TIMESTAMP
【强制】关于状态字段根据业务含义命名为: state、status
【强制】界面上要显示成树形结构的表,至少需要3个字段: id、parent_id、sort_value
【强制】POJO 类的布尔属性不能加 is,而数据库字段必须加 is_,要求在 resultMap 中进行字段与属性之间的映射。
【强制】主键索引名为 pk_字段名
【强制】唯一索引名为 uk_字段名
【强制】普通索引名则为 idx_字段名。
【强制】不得使用外键与级联,一切外键概念必须在应用层解决
【强制】不用存储过程
【强制】数据库名、表名、字段名统一使用小写字母, _ 分割
【强制】表和字段必须加注释!
【强制】当字段为外键时,字段名为:关联表_id, 注释需要在字段注释基础上,换行加上 #关联表表名来说明关联的哪张表。
【建议】租户字段以及机构字段用zerofill ,这样数据表尽可能美观
【建议】枚举字段直接用enum。注释模板: 注释内容;#枚举类名{枚举值英文名:“枚举值英文注释”; …}
说明:data_id可以用varchart类型,uuid去生成分布式id
也可以用redis的方式生成long类型id。但是需要注意redis开启事务之后和没有开启事务的区别。注意批量id生成的测试。
package com.pms.regist.id;
import com.pms.common.core.utils.SequenceUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.StringJoiner;
/**
* {@docRoot}
* {@inheritDoc}
*
* @author miaoyi
* <p>描述: [分布式id生成服务] </p>
* 如果存在多redis实例:请考虑以下三点
* ①时钟同步:时间不同步可能会导致不同节点生成具有相同时间戳的ID,尤其是在接近“秒级”精度的时间戳使用时。
* ②数据一致性:实例之间需要有某种形式的数据共享或一致性协议,以确保自增计数器(count)的唯一性和正确性。常见的做法是使用Redis的集群模式(如Redis Cluster)或哨兵模式(Sentinel),并确保incr操作在集群中是原子性的。
* ③键的分区策略:代码中通过"icr:" + keyPrefix + ":" + date来生成Redis的键名,这一步需要确保在多Redis实例的架构中,相同keyPrefix和date的键总是路由到同一个Redis实例上。
* 如果使用了哈希槽(hash slot)分区的Redis Cluster,这一点尤为重要,因为键的哈希值决定了它归属的槽和实例。设计不当可能导致不同实例上的键冲突或重复。
*/
@Service
public class RedisIdUtils {
/**
* 预生成开始时间戳:这个可以自己定义一个时间
*/
private static final long BEGIN_TIMESTAMP = 1640995200L;
/**
* 序列号的位数
*/
private static final int COUNT_BITS = 32;
/**
* redis提供的字符串
*/
private final StringRedisTemplate stringRedisTemplate;
/**
* 有参构造函数
*
* @param stringRedisTemplate stringRedisTemplate
*/
public RedisIdUtils(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
/**
* 功能描述:根据keyPrefix前缀生成对应业务的全局唯一ID
* keyPrefix:使用前缀来区分不同的业务
*
* @return long ID
*/
public long nextId(String keyPrefix) {
// 1.生成时间戳
LocalDateTime now = LocalDateTime.now();
long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
long timestamp = nowSecond - BEGIN_TIMESTAMP;
// 2.生成序列号
// 2.1.获取当前日期,精确到天
String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
// 2.2.自增长 icr:order:2023:08:13
long count = this.incr("icr:" + keyPrefix + ":" + date);
// 3.拼接并返回 timestamp << COUNT_BITS :向左移动32位
//原本时间戳在低位上,通过向左移动32位,变位到高位存储,低32位都是0,然后与自增序列按位操作
//形成低32位为序列号。
return timestamp << COUNT_BITS | count;
}
/**
* 功能描述:生产dataId !!注意:这个id返回给前端时候需要注意精度问题。如果用Long类型返回给前端就会损失精度
*/
public long generateDataId() {
return nextId("data:id");
}
/**
* 生成自增ID-用来生成订单号
* keyPrefix:使用前缀来区分不同的业务
*
* @return 自增ID
*/
public String autoId(String keyPrefix) {
LocalDateTime now = LocalDateTime.now();
String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
String currentDate = SequenceUtil.getCurrentDate();
long sequence = this.incr("icr:" + keyPrefix + ":" + date);
String seq = SequenceUtil.getSequence(sequence);
StringJoiner sj = new StringJoiner("");
sj.add(currentDate).add(seq);
return sj.toString();
}
/**
* 获取递增---注意这里不要开启redis事务。开启事务事务传递特性会包NEP异常
* 注意这里只返回递增id,只要返回了就计算已经用掉了
*
* @param key key
* @return long 递增id
*/
public long incr(String key) {
return stringRedisTemplate.opsForValue().increment(key, 1);
}
}