上一章中,我们实现了序列器生成器的功能,并成功运用了单例模式。细心的你可能也发现了一个问题:每次获取键值时都要去查询数据库,这样会带来一个性能的问题,那有没有办法优化列?答案是肯定的。做法就是我们常在处理数据库操作时用到的:缓存。
思路是这样的:给整个表做一个缓存,在每次取值的时候先去缓存中获取数据,如果获取不以,将去数据库中查询,注意这些我们不会递增,而是一次性出N个值出来,N的大小可以根据系统的吞吐量来决定,就算我们系统在使用中,突然Down掉了,你也不用怕,无非就是浪费了几个序列键而已。在本例中,为了演示效果,我们将N 将为5。
我们创建一个Bean来保存数据库中的一条记录的相关信息。
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 KeyInfo { /**缓存中的最大值*/ private int keyMax; /**缓存中的最小值,也是就是每次从数据库中取值以后的第一个值*/ private int keyMin; private int nextKey; /**缓存多少个*/ private int poolSize; /**键名**/ private String keyName; /** * 构造函数 * @param poolSize * @param keyName */ public KeyInfo(int poolSize,String keyName){ this.poolSize = poolSize; this.keyName = keyName; retrieveFromDB(); } /** * 从数据库获取数据 */ private void retrieveFromDB(){ Connection conn = null; try { //一次从数据库中取poolSize出来 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+ " + this.poolSize + " where keyName = ? "; PreparedStatement upateStmt = conn.prepareStatement(updateSql); upateStmt.setString(1, this.keyName); upateStmt.execute(); String sql = "select GEN_VALUE from id_gen where GEN_KEY = ? "; PreparedStatement stmt = conn.prepareStatement(sql); stmt.setString(1, this.keyName); ResultSet rs = stmt.executeQuery(); //赋值 while (rs.next()) { this.keyMax = rs.getInt(1); this.keyMin = this.keyMax - this.poolSize+1; this.nextKey = this.keyMin; } } 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 */ public int getNextKey() { //缓存中使用完以后,再重新获取 if(nextKey > keyMax){ retrieveFromDB(); } //返回nextKey,并将nextKey+1 return nextKey++; } public String getKeyName() { return keyName; } public int getKeyMax() { return keyMax; } public int getKeyMin() { return keyMin; } public int getPoolSize() { return poolSize; } }
接下来,我们看看序列键生成器的代码:
package com.pattern.id.generator; import java.util.HashMap; import java.util.Map; public class KeyGenerator { private static KeyGenerator keyGen = new KeyGenerator(); private static final int POOL_SIZE = 5; private Map<String,KeyInfo> keyList = new HashMap<String,KeyInfo>(); private KeyGenerator() { }; /** * 工厂方法 * * @return */ public static KeyGenerator getInstance() { return keyGen; } /** * 获取下一个value * * @return */ public synchronized int getNextKey(String keyName) { KeyInfo key = keyList.get(keyName); if(key==null){ key = new KeyInfo(POOL_SIZE,keyName); } return key.getNextKey(); } }
大家可以看到,我们把获取数据库的操作转移到KeyInfo中,每条记录对自己负责,我们在序列键生成器中,只需要调用各自记录的Bean,就能获取到相关信息。当第一次获取某条记录的时候相关信息时,首先我们是去缓存中查询,如果查询不到,我们就会创建该记录的Bean,并缓存起来。如果查询到,我们则直接调查用相关方法,获取我们需要的信息(下一个键值),每条记录对自己负责,而生成器,刚相当于成了一完全的缓存对象,他的所有操作都委托给相应记录的Bean自己处理。
同时,我还在想,这例可不可以用多例模式来做列? 我们将map 放到KeyInfo类中,让KeyInfo类,变成多例模式,这样KeyGenarator就可以省去,这样做也是行得通。但是我个人觉得: 从逻辑上来讲,一个序列键管理器负责管理多条记录更说通。
应该说,两种设计都行通,看个人喜好吧。