我们在做数据库设计的时候,一般都会为数据库设计一个主键,来方便查询,更多的时候这个主键是代理主键,也就是说并没有实际意义。所以通常我们会把这个主键设计为自增的方式。比如Mysql中,我们可以设置为:autoIncresement, Oracle中可以利用Sequence..... 但是有些数据库是不支持这种自增的方式的,也就是说:自增必须我们自己通过程序来实现,比如sybase。 那么这种我们通过程序来控制自增长的过程,就叫序列键生成。简单的说:用一张表来记录其它表的主键的当前值,进来通过程序来达到自增的目的。
除了数据库不支持自增方式以后,还有另外一种情况会使用到序列键生成,那就是多张表的代理主键的值不能重复时。比如现在有2张表:T1,T2,他们的主键分别是pk1,pk2.如果说我们的业务要求pk1,pk2两列的值不能出现重复时,那么用我们以往的自增方式显然是办不到的,这个时候就需要生成序列键。
好了进入正题,我们先建立一张表,SQL语句如下:
CREATE TABLE `id_gen` (
`GEN_KEY` varchar(20) NOT NULL,
`GEN_VALUE` int(11) DEFAULT 0,
PRIMARY KEY (`GEN_KEY`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
我们要主键的 keyname与它当前的value保存到这个表中,每次我们取值之前,先update一下value,再取出来value。有的人会说:为什么我们每次不先取value再更新列?现在试想一下,如果当你取完值以后,系统被中断了,这时你还没有来得及更新value,那么下次取的到仍就是同一个值,如果上一个值已经被正确使用了,那么就出现了重复值,与我们设计的初衷就不符了。如果先更新,再取值的话,算被中断,顶多是浪费了一个值而已。 从上面的分析,我们不难看出,这个序列键生成器,在整个系统中只需要一个。那么,很快我们就想到了单例模式,那么我们就开始设置我们的生成器。
package com.pattern.id.generator;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class KeyGenerator {
private static KeyGenerator keyGen = new KeyGenerator();
private KeyGenerator() {
};
/**
* 工厂方法
*
* @return
*/
public static KeyGenerator getInstance() {
return keyGen;
}
/**
* 获取下一个value
*
* @return
*/
public synchronized int getNextKey(String keyName) {
return getNextKeyFromDB(keyName);
}
private int getNextKeyFromDB(String keyName) {
Connection conn = null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager
.getConnection("jdbc:mysql://localhost:3306/frameworkdb?user=root&password=123456");
String updateSql = "update id_gen set GEN_VALUE = GEN_VALUE+1 where GEN_KEY = ? ";
PreparedStatement upateStmt = conn.prepareStatement(updateSql);
upateStmt.setString(1, keyName);
upateStmt.execute();
String sql = "select GEN_VALUE from id_gen where GEN_KEY = ? ";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, keyName);
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
return rs.getInt(1);
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return 0;
}
}
关于上面的代码,我不想做过多解释,都是JDBC的基础知识,关于JDBC资源,应该一层层来,我这里写示例代码就直接性关闭Connection。现在我们来写个测试类,来测试一下, 看我们这个类是否能正常工作。
package com.pattern.id.generator; public class ClientTest { /** * @param args */ public static void main(String[] args) { KeyGenerator gen = KeyGenerator.getInstance(); int value = gen.getNextKey("test"); System.out.println(value); } }
多运行几次,可以看结果是从1开始不停的递增上去的,当然你也可以用多线程来测试是否会出现重复值的现象,我可以肯定的说:不会,至于为什么,可以参见我的另一篇博客,关于synchronized的理解。
好啦,这说明,我们的序列键生成器功能已经达到了预期目的。
在这里给大家扩展点知识,用过hibernate的同学可能就知道。其实hibernate对于这种方式是支持,至于在hibernate中怎么实现这一功能,我就不多说了。有兴趣的同学可以在Google里面搜索:@TableGenerator