自定义生成数据库主键类

主要是 项目的框架里面 生成的数据库主键, 居然是 要 关联到 数据库表的, 而且 还会 更新表,怎么看怎么效率低! 可是 其他人说,这个是 效率最高的方法。。。我是一脸懵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());
		}
		
	}




}


转载于:https://my.oschina.net/ouminzy/blog/889537

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值