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