序列号生成器

本文介绍了一种自增序列号生成器的设计与实现,包括数据库表结构定义、核心Java类代码实现及测试方法。该生成器适用于业务流程号生成场景,支持不同长度的字符串和整数类型。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

数据库中有自增长的字段,但是自增长字段有他的局限性,有的数据库是不支持自增长的。在开发过程中,部分客户业务需要生成业务的流程号,单纯的数字无法满足需求,于是就产生了编写一个序列号生成器的想法。

1、首先创建数据库表

create table sys_max_number (
   mn_id                varchar(32)          not null,
   mn_key_name          varchar(64)          not null,
   mn_key_value         bigint               not null default 0,
   mn_remark            varchar(512)         not null,
   constraint PK_SYS_MAX_NUMBER primary key (mn_id),
   constraint AK_UNIQUE_MAX_NUMBER_SYS_MAX_ unique (mn_key_name)
)
go

各个字段的含义如下:数据库主键,流水号的key,键值(默认0),流水号key说明

代码如下:

 package com.sys.maxnumber;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.work.core.db.DbConnectionFactory;

/**
 * @author wangmingjie
 * @date 2009-8-4上午09:59:15
 */
public class KeyInfo {
 private static Log log = LogFactory.getLog(KeyInfo.class); 
 private long keyMax; // 最大键值
 private long keyMin; // 最小键值
 private long nextKey; // 下一个键值
 private int poolSize; // 缓存大小,注意,太大容易浪费号码
 private String keyName; // 主键,用在哪个应用中的。

 public KeyInfo(int poolSize, String keyName) {
  this.poolSize = poolSize;
  this.keyName = keyName;
  retrieveFromDB(5); // 注意不能直接使用这个类,否则在多线程的情况下,会出问题。
  //上面必须初始化的时候调用,否则出错!目的是为了首先要给nextKey赋值
 }

 public long getKeyMax() {
  return keyMax;
 }

 public long getKeyMin() {
  return keyMin;
 }

 /**
  * 获取下一个字符串的键值。如果不符合长度,前面用零补齐;<br>
  * 例如参数是四,intkey=10,那么返回0010
  * 注意,这里面调用的是getNextIntKey方法。
  * 2,147,483,647 ,
  * @param paddingLength
  *            字符串长度,最大长度为10位。因为整形的缘故。
  *           
  * @return 如果参数长度大于指定的长度了,那么返回实际长度的字符串。
  */
 public String getNextStringKey(final int paddingLength) {
  String s = Long.toString(getNextIntKey());
  int len = s.length();
  if (len < paddingLength) {
   StringBuffer buf = new StringBuffer(paddingLength);
   for (int i = 0; i < paddingLength - len; i++) {
    buf.append('0');
   }
   buf.append(s);
   s = buf.toString();
  }
  return s;
 }

 /**
  * 获取下一个整型的键值;
  *
  * @return
  */
 public synchronized int getNextIntKey() {
  if (nextKey > keyMax) {
   retrieveFromDB(5);
  }
  return (int)(nextKey++);
 }
 
 /**
  * 获取长整形的键值
  * @return
  */
 public synchronized long getNextLongKey(){
  if (nextKey > keyMax) {
   retrieveFromDB(5);
  }
  return nextKey++;
 }

 private void retrieveFromDB(int count) {
  if (count == 0) {
   if(log.isErrorEnabled())
    log.error("最终获取序列号失败!放弃继续获取...");   
   return;
  }
  boolean success = false;
  
  long keyFromDB = 0;
  String updateSql = "UPDATE sys_max_number SET mn_key_value = mn_key_value + "
    + poolSize + " WHERE mn_key_name =? ";

  String selectSql = "SELECT mn_key_value FROM sys_max_number WHERE mn_key_name = ? ";
  Connection conn = null;
  PreparedStatement pstUpdate = null;
  PreparedStatement pstSelect = null;
  ResultSet rst = null;
  try {
//   conn = DbConnectionFactory.getJdbcConn();
   conn = DbConnectionFactory.getConnection();//从连接池中获取数据库连接
   conn.setAutoCommit(false);
   pstUpdate = conn.prepareStatement(updateSql);
   pstUpdate.setString(1, keyName.trim());
   pstUpdate.executeUpdate();

   pstSelect = conn.prepareStatement(selectSql);
   pstSelect.setString(1, keyName.trim());
   rst = pstSelect.executeQuery();
   while (rst.next()) {
    keyFromDB = rst.getLong(1);
   }
   conn.commit();
   success = true;
  } catch (SQLException e) {
   try {
    conn.rollback();
   } catch (SQLException e1) {
    e1.printStackTrace();
   }
   e.printStackTrace();
   if(log.isWarnEnabled())
    log.warn("获取序列号失败!", e);
  } finally {

   try {
    if (rst != null)
     rst.close();
   } catch (SQLException e) {
   }
   try {
    if (pstUpdate != null)
     pstUpdate.close();
   } catch (SQLException e) {
   }
   try {
    if (pstSelect != null)
     pstSelect.close();
   } catch (SQLException e) {
   }
   try {
    if (conn != null)
     conn.close();
   } catch (SQLException e) {
   }
  }

  keyMax = keyFromDB;
  keyMin = keyFromDB - poolSize + 1;
  nextKey = keyMin;

  if (!success) {
   if(log.isWarnEnabled())
    log.warn("警告,因线程争夺,获取下一个序列号失败。进行下一次尝试..."); 
   // Call this method again, but sleep briefly to try to avoid thread contention.
   try {
    Thread.sleep(75);
   } catch (InterruptedException ie) {
   }
   retrieveFromDB(count - 1);
  }
 }
}

======================================

package com.sys.maxnumber;

import java.util.HashMap;
//
//import org.apache.commons.logging.Log;
//import org.apache.commons.logging.LogFactory;

/**
 * @author wangmingjie
 * @date 2009-8-4上午10:33:19
 */
public class KeyGenerator {
// private static Log log = LogFactory.getLog(KeyGenerator.class); 
 
 private static HashMap<String, KeyGenerator> kengens = new HashMap<String, KeyGenerator>(
   10);
 /* 最小为1,因为数据库中,默认的是0,生成的序列值最小为1 */
 private static final int POOL_SIZE = 1;
 
 private KeyInfo keyinfo;

 /**
  * 构造方法。
  * @param poolSize 每次获取几个数值,放到缓存中。如果小于1,那么默认为1。<br>
  * 建议值为2,这样每次重新启动应用最多浪费一个数,而且能够提高到两倍的效率。<br>
  * 如果使用1,废号的几率是最低的。但是不能应用在高并发的系统中。
  * @param keyName  对应最大好表的键。
  */
 private KeyGenerator(int poolSize,String keyName) {
  if(poolSize<1){
   poolSize=POOL_SIZE;
  }
  keyinfo = new KeyInfo(poolSize, keyName);
 }

 /**
  * 保证线程安全。
  * @param poolSize 缓存大小
  * @param keyName  主键名称
  * @return
  */
 public static synchronized KeyGenerator getInstance(int poolSize,String keyName) {
  KeyGenerator keygen;
  if (kengens.containsKey(keyName)) {
   keygen = kengens.get(keyName);
   //System.out.println("从缓存中获得"+keyName);
  } else {
   keygen = new KeyGenerator(poolSize,keyName);
   kengens.put(keyName, keygen); //注册到hashmap中。提高效率
   //System.out.println("注册了"+keyName);
  }
  return keygen;
 }

 /**
  * 获取到int类型的序列值
  * @return
  */
 public int getNextIntKey() {
  return keyinfo.getNextIntKey();
 }
 /**
  * 获取到long类型的序列值。一般不会用到。除非是像移动联通,这样的,每天的通话记录都是上亿的。
  * @return
  */
 public long getNextLongKey() {
  return keyinfo.getNextLongKey();
 } 
 /**
  * 获取到string类型的序列值
  * @param paddingLength 返回string的长度,最大长度是10位。如果使用long可以到19位。
  * @return
  */
 public String getNextStringKey(int paddingLength){
  return keyinfo.getNextStringKey(paddingLength);
 }
}
================测试类如下==========================

package com.sys.maxnumber;


/**
 * 注意测试的时候,修改KeyInfo的retrieveFromDB方法,使其通过jdbc连接。
 * 使用的时候注意,keyname必须在最大号表中存在!
 * @author wangmingjie
 * @date 2009-8-4上午10:38:19
 */
public class KeyClient {
//一般使用方法,KeyGenerator.getInstance(1,"BugProject").getNextIntKey();
//在并发数量多的情况下面,提高poolsize的值,这样就可以减少数据库的访问次数。 
 
 /**
  * 使用方法
  *
  * @param args
  */
 public static void main(String[] args) {
//  KeyGenerator keygen = KeyGenerator.getInstance(1,"BugProject");
//
//  for (int i = 0; i < 25; i++) {
//   System.out.println("key(" + (i + 1) + ")= " + keygen.getNextIntKey());
//  }
//  
//  for (int i = 0; i < 25; i++) {
//   System.out.println("key(" + (i + 1) + ")= " + keygen.getNextStringKey(6));
//  }
  
  ThreadA t = new ThreadA();
  Thread t1 = new Thread(t, "A");
  Thread t2 = new Thread(t, "B");
  Thread t3 = new Thread(t, "C");
  Thread t4 = new Thread(t, "D");
  t1.start();
  t2.start();
  t3.start();
  t4.start();
//  访问多了就出现事务死锁。所以高并发系统中必须提高缓存数量。
//  java.sql.SQLException: 事务(进程 ID  143)与另一个进程已被死锁在  lock 资源上,且该事务已被选作死锁牺牲品。请重新运行该事务。
//  Bkey A(12)= 000137  
//  ThreadA ta = new ThreadA();
//  ta.run(); 
//  ThreadB tb = new ThreadB();
//  tb.run(); 
  
 }
}
class  ThreadA implements Runnable{
 public void run(){
  KeyGenerator keygen = KeyGenerator.getInstance(1,"BugProject");
  for (int i = 0; i < 500; i++) {
   System.out.println(Thread.currentThread().getName()+"key A(" + (i + 1) + ")= "
     + keygen.getNextIntKey()+"||"
     + keygen.getNextIntKey()
     +"||"
     + keygen.getNextStringKey(4));
  }
 }
}
class  ThreadB implements Runnable{
 public void run(){
  KeyGenerator keygen = KeyGenerator.getInstance(1,"BugProject");
  for (int i = 0; i < 500; i++) {
   System.out.println(Thread.currentThread().getName()+"key B(" + (i + 1) + ")= " + keygen.getNextStringKey(6));
  }
 }
}
==============pojo================

package com.sys.model;

import java.io.Serializable;

/**
 *
 */

public class MaxNumber implements Serializable {


 // constructors
 public MaxNumber() {

 }

 /**
  * Constructor for primary key
  */
 public MaxNumber(String id) {
  this.setId(id);

 }

 // primary key
 private String id;//ID

 

 public java.lang.String getId() {
  return id;
 }


 public void setId(String id) {
  this.id = id;
 }


 private String keyName;  //主键
 private Integer keyValue;  //值
 private String remark;  //备注

 public String getKeyName() {
  if(keyName==null) return null;
  else return keyName.trim();
 }

 public void setKeyName(String keyName) {
  this.keyName = keyName;
 }
 public Integer getKeyValue() {
  return keyValue;
 }

 public void setKeyValue(Integer keyValue) {
  this.keyValue = keyValue;
 }
 public String getRemark() {
  if(remark==null) return null;
  else return remark.trim();
 }

 public void setRemark(String remark) {
  this.remark = remark;
 }


 /* (non-Javadoc)
  * @see java.lang.Object#hashCode()
  */
 @Override
 public int hashCode() {
  final int PRIME = 31;
  int result = super.hashCode();
  result = PRIME * result + ((id == null) ? 0 : id.hashCode());
  return result;
 }

 /* (non-Javadoc)
  * @see java.lang.Object#equals(java.lang.Object)
  */
 @Override
 public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (!super.equals(obj))
   return false;
  if (getClass() != obj.getClass())
   return false;
  final MaxNumber other = (MaxNumber) obj;
  if (id == null) {
   if (other.id != null)
    return false;
  } else if (!id.equals(other.id))
   return false;
  return true;
 }


 public String toString() {
  StringBuffer sb = new StringBuffer("");
  sb.append("MaxNumber{id=");sb.append(id);sb.append(",");
  sb.append("keyName=");sb.append(keyName);sb.append(",");
  sb.append("keyValue=");sb.append(keyValue);sb.append(",");
  sb.append("remark=");sb.append(remark);
  sb.append("}");
  return sb.toString();
 }

 

}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值