上一章中,我们实现了序列器生成器的功能,并成功运用了单例模式。细心的你可能也发现了一个问题:每次获取键值时都要去查询数据库,这样会带来一个性能的问题,那有没有办法优化列?答案是肯定的。做法就是我们常在处理数据库操作时用到的:缓存。
思路是这样的:给整个表做一个缓存,在每次取值的时候先去缓存中获取数据,如果获取不以,将去数据库中查询,注意这些我们不会递增,而是一次性出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就可以省去,这样做也是行得通。但是我个人觉得: 从逻辑上来讲,一个序列键管理器负责管理多条记录更说通。
应该说,两种设计都行通,看个人喜好吧。