我们在做数据库设计的时候,一般都会为数据库设计一个主键,来方便查询,更多的时候这个主键是代理主键,也就是说并没有实际意义。所以通常我们会把这个主键设计为自增的方式。比如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