主要是 项目的框架里面 生成的数据库主键, 居然是 要 关联到 数据库表的, 而且 还会 更新表,怎么看怎么效率低! 可是 其他人说,这个是 效率最高的方法。。。我是一脸懵B ,所以就打算 改造它。 它的源码是这样的:
配置
<bean id="idGenerator" class="com.skg.base.db.service.IdGeneratorImpl" >
<property name="machineId" value="${machine.id}" />
<property name="machineName" value="${machine.name}" />
</bean>
代码
package com.skg.base.db.service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.UUID;
import javax.annotation.Resource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import com.skg.base.core.util.string.StringUtils;
public class IdGeneratorImpl implements IdGenerator,InitializingBean{
@Resource
public JdbcTemplate jdbcTemplate;
/**
* 增长段值
*/
private final Long increaseBound = 1000000l;
/**
* 当前DBID
*/
private Long dbid = 1l;
/**
* 当前递增的最大值
*/
private Long maxDbid = -1l;
/**
* 机器ID
*/
private Long machineId = null;
/**
* 机器名称 多台物理机器集群部署时,需要唯一区分
*/
private String machineName="";
/**
* ID的基准长度
*/
private Long idBase=10000000000000L;
public Long genLid() {
return getUniqueId();
}
public String genSid() {
return genLid().toString();
}
public String genUuid() {
return UUID.randomUUID().toString();
}
/**
* 获取唯一的ID标识
*
* @return
*/
private synchronized Long getUniqueId() {
if (dbid > maxDbid) {
genNextDbIds();
}
Long nextId = dbid++;
return nextId + machineId * idBase;
}
private void genNextDbIds() {
String sql = "UPDATE crm_gl_id SET start_=?,max_=? WHERE id_=?";
dbid = maxDbid;
maxDbid += increaseBound;
jdbcTemplate.update(sql, dbid, maxDbid, machineId);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public void init(){
if(machineId!=null && StringUtils.isNotEmpty(machineName)){
String sql = "select * from crm_gl_id where id_ ="+machineId+" and mac_name_='"+ machineName+ "'";
//检查该机器是否已经存在增长的键值记录
try {
jdbcTemplate.queryForObject(sql, new RowMapper() {
public Object mapRow(ResultSet rs, int i) throws SQLException {
dbid = rs.getLong("start_");
maxDbid = rs.getLong("max_");
machineId = rs.getLong("id_");
return machineId;
}
});
//插入该机器的键值增长记录
genNextDbIds();
} catch (Exception ex) {
maxDbid = dbid + increaseBound;
sql = "INSERT INTO crm_gl_id(id_,start_,max_,mac_name_)VALUES(?,?,?,?)";
jdbcTemplate.update(sql, new Object[]{machineId.toString(), dbid, maxDbid, machineName});
}
}
}
public void afterPropertiesSet() throws Exception {
init();
}
public void setMachineId(Long machineId) {
this.machineId = machineId;
}
public void setMachineName(String machineName) {
this.machineName = machineName;
}
@Override
public String genTimeId() {
Calendar calendar = Calendar.getInstance();
Date date = calendar.getTime();
SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMddHHmmss");
String timeStr = sdf.format(date);
return timeStr;
}
}
是有序的 id了 。 之前我做的一个项目产生的主键是 用的是 黑科技产生的有序的id 我觉得挺好的。 即 Twitter的snowflake算法 , 分布式高效有序ID生产黑科技(sequence) SequenceUtil 参考地址: http://www.cnblogs.com/haoxinyue/p/5208136.html 源码就不上了,百度很多的。可是问题来了, 我还真看不懂 他的原理。 好底层啊,又移位有什么的。 如果我想要 看出 机器id 还可以不出来,怎么通过 产生的id 反推出就 机器id ? 我百度了一下,没看到。。。代码太深奥了,我是菜鸟。 于是我自定义一个。 自定义的时候,发现不是有序的,,,可是反思,我们一般的主键需要有序?而且也不可能数据都是绝对的有序的。。。只需要 从小到大 排序即可。 下面是我的代码;
package com.skg.base.db.service;
import java.util.UUID;
import org.springframework.beans.factory.InitializingBean;
import com.skg.base.db.helper.SystemClock;
/**
*
* @author : oumin
* @date :生成 从小到大的的 主键 工具类,通过 当前的时间戳 没有必要要 完全的有序。如果需要请自己
* 写一个获取时间戳的然后自增即可。这样就可以用原子性的自增了,可以考虑不用同步锁
* @desc :2017年4月29日
*
*/
public class IdGeneratorByTimeImpl implements IdGenerator,InitializingBean{
/**
* 机器ID
*/
private Long machineId = null;
/**
* 机器名称 多台物理机器集群部署时,需要唯一区分
*
*/
private String machineName = "";
private long lastTimestamp = -1L;
public Long genLid() {
return new Long(getUniqueId());
}
/**
* string id 用得多,因此 直接返回string 类型的
*/
public String genSid() {
return getUniqueId();
}
public String genUuid() {
return UUID.randomUUID().toString();
}
/**
* 获取唯一的ID标识
*
* @return
*/
private synchronized String getUniqueId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(
String.format(
"Clock moved backwards. Refusing to generate id for %d milliseconds",
lastTimestamp - timestamp));
}
if (lastTimestamp == timestamp) {
timestamp = genNextDbIds(lastTimestamp);
}
lastTimestamp = timestamp;
// 可以如果要求出 机器id 可以同判断 0 的位置 之前来获取 机器 id
// 如果有必要的话,可以 强制要求 机器的id 数字里面不能包括 0 数字 这样就可以有效截取出来机器id 了
String timestampStr = machineId + "0" + timestamp;
return timestampStr;
}
private long genNextDbIds(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
private long timeGen() {
return SystemClock.now();
}
// public void init() {
//
// }
public void afterPropertiesSet() throws Exception {
// init(); // 方法初始化,可以 初始化自己的 内容
}
public void setMachineId(Long machineId) {
this.machineId = machineId;
}
public void setMachineName(String machineName) {
this.machineName = machineName;
}
@Override
public String genTimeId() {
return timeGen() + "";
}
}
时间戳代码:
package com.skg.base.db.helper;
import java.sql.Timestamp;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
/**
* 如果要用到 System.currentTimeMillis() 就一定要调用该方法
* 高并发场景下System.currentTimeMillis()的性能问题的优化
* <p><p>
* System.currentTimeMillis()的调用比new一个普通对象要耗时的多(具体耗时高出多少我还没测试过,有人说是100倍左右)<p>
* System.currentTimeMillis()之所以慢是因为去跟系统打了一次交道<p>
* 后台定时更新时钟,JVM退出时,线程自动回收<p>
* 10亿:43410,206,210.72815533980582%<p>
* 1亿:4699,29,162.0344827586207%<p>
* 1000万:480,12,40.0%<p>
* 100万:50,10,5.0%<p>
* @author lry
*/
public class SystemClock {
private final long period;
private final AtomicLong now;
private SystemClock(long period) {
this.period = period;
this.now = new AtomicLong(System.currentTimeMillis());
scheduleClockUpdating();
}
private static class InstanceHolder {
public static final SystemClock INSTANCE = new SystemClock(1);
}
private static SystemClock instance() {
return InstanceHolder.INSTANCE;
}
private void scheduleClockUpdating() {
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(runnable, "System Clock");
thread.setDaemon(true);
return thread;
}
});
scheduler.scheduleAtFixedRate(new Runnable() {
public void run() {
now.set(System.currentTimeMillis());
}
}, period, period, TimeUnit.MILLISECONDS);
}
private long currentTimeMillis() {
return now.get();
}
public static long now() {
return instance().currentTimeMillis();
}
public static String nowDate() {
return new Timestamp(instance().currentTimeMillis()).toString();
}
}
这些代码都是大部分 粘贴的, 你们应该都可以百度到。。。 如果需要有序的,可以自己改造 的。当然我也不知道性能怎样? 想来,用着爽。。。再说了,一般的项目够用了。。。
第一 ,
id 有序 还是很重要的。。。 为什么? 因为 数据量 大了的时候,,, 特别是 分库分表的时候,,有序的id 我们 将 他们 取模 ,即求余,,比如 3台数据库,,, 分库,,, id %3 来觉得将 这些数据分到 那个库里面,,,这样分得数据就比较 均匀 也比较 合理了。。。 可是我测试过了,, SequenceUtil 产生的 id , 随着时间的变化也不是绝对的有序,,比如产生了一个 id , 过5秒产生一个id ,他们并不是 有序递增的,而是按照由大到小递增的,,,如果是在一段时间内容,一直产生id, 那么他们就是有序的而已。。。因此 用不用 SequenceUtil 还是使用自己定义的 id 生成方式,,差别并不是很大,,,对以后的分表分库来说是不会造成很大的影响的。所以大可放心使用自己自定义的主键生成策略
第三,
建议将 SequenceUtil 的 产生 id 定义为 String 类型,,数据库的主键也是 varchar 类型。。 因为 对于一个大型系统来说,,, 如果 把 主键定义为int 或者 long 类型的话, 会容易出现一些后期维护问题的
1, 前端 js 会可能出现精度 缺失的情况, 当然了 可以 把 long 类型的 , 给前端的时候,转为 string 类型, 或者数据库 映射到 bean model 的时候 也 自动映射为 string,,, 然后 前端 转 id 过来的时候再转类型。。。 等等,,这不是增加了开发的 时间成本? 维护起来麻烦死了,,, 开发效率会比较低的,,,而 收益并没有增加多少? 特别是 项目时间很赶的情况下。。。所以 把主键 设置为 varchar 是可以的。 我们牺牲一点数据库性能,来 为了以后 更加的方便,这是很值得的,,
2, 对于一些大型系统来说, 他们的主键 都是 有32位,,设置 64位那么长的,,, 对于MySQL 来说,, long 类型,长度 最多是 20位,,, 在 Java 里面 long 类型最长也是 19位而已。。 因此 使用 varchar 是 肯定的。varchar 的主键,对于以后的分库分表的 主键 扩展设置,和标识来说是很方便的。
3, 建议使用 主键是 varchar ,当然也是可以使用 long 类型了,,,看具体项目以后的发展了,如果是创业项目 int 可以哦,说不定以后会进行优化和改进的,赚不到钱,一个项目的生命周期也是有可能很短的,,,,那么使用 主键自增这样的也是可以的,,就是以后 分库分表麻烦而已
第四,, 当然了,我也不知道,,机器id ,, 序列号什么意思。。 我只知道 SequenceUtil 可以产生比较短的,有序的,比较多的,long 数据就够了。。 原理什么的,,看不懂懒得管
其实上面的自定义的纯时间作为主键还是不够 优化的
因为相同毫秒内不能插入数据,只能等下一个毫秒内,这样会有点影响效率的,和毫秒内可以插入的数量的,下面是我的优化:
package com.stylefeng.guns.common.helper;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.stereotype.Component;
import com.baomidou.mybatisplus.toolkit.Sequence;
/**
*
* @author : oumin
* @date :生成 从小到大的的 主键 工具类,通过 当前的时间戳 没有必要要 完全的有序。
* 这里进行了 简化,
* 1,去掉了机器id ,而且也没有数据库集群。。
* 2, 直接使用当前时间而不是 黑科技的主键自增,减少了长度了,简化了,因为没必要那么多长度,而且也不是很需要很精确的自增。
* 3, 如果以后数据库需要分库分表,则将 主键 取模 库的总数 ,比如数据库 3个,, 主键 % 3 = 应该放入的库 ,就可以知道应该将该条数据放入那个数据库里面了
* 4, 大神说,如果是以纯时间 的方式,一秒内最多产生100W条id,多了肯定会有重复的,
* 如果以后真的数据量有这个了,可以在后面加上几个 随机数 (自己改造) ,或者直接使用 原生的主键黑科技: 分布式高效有序ID生产黑科技(sequence),1秒内可以有400W条
* @desc :2017年4月29日
*
*/
@Component
public class IdGeneratorByTimeImpl {
private long lastTimestamp = -1L;
private static String baseStrNum="100";
private static int baseInt = 100;
public Long genLid() {
return new Long(getUniqueId());
}
/**
* string id 用得多,因此 直接返回string 类型的
*/
public String genSid() {
return getUniqueId();
}
/**
* 获取唯一的ID标识
*
* @return
*/
private synchronized String getUniqueId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(
String.format(
"Clock moved backwards. Refusing to generate id for %d milliseconds",
lastTimestamp - timestamp));
}
if (lastTimestamp == timestamp) {
baseInt++;
if (baseInt > 999 ) {
// 相同 毫秒内 , 不取 下一个毫秒的数,这样效率会高一些,为了提高插入效率和相同毫秒内可以插入的数据量
timestamp = genNextDbIds(lastTimestamp);
baseInt=100;
}
}else{
//不是相同毫秒内,初始化
if(baseInt != 100){
baseInt = 100;
}
}
lastTimestamp = timestamp;
if(baseInt == 100){
return String.valueOf(timestamp)+baseStrNum;
}else{
return String.valueOf(timestamp)+String.valueOf(baseInt);
}
}
private long genNextDbIds(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
private long timeGen() {
return SystemClock.now();
}
public static void main(String[] args) {
IdGeneratorByTimeImpl a = new IdGeneratorByTimeImpl();
for (int i = 0; i < 200; i++) {
System.out.println(a.genSid());
}
}
}