Java访问mysql数据库,涉及到IO操作,IO操作是比较耗时的操作。所以为了提高性能,可以选择使用缓存,把常用的数据缓存起来。本文将介绍使用HashTable和HashMap实现缓存的功能。包括:“定义统一管理写入和读取缓存的类”、“定义缓存类来存储缓存数据”、“在项目启动的时候,开始加载缓存”、“从缓存管理类中读取缓存数据”、“保持缓存和数据库的一致性”。
1、定义统一管理写入和读取缓存的类。
缓存管理类CacheManager:
@Component("cacheManager")
public class CacheManager {
/** Key: 公司DB名称 <br />
* Value: 1个HashMap, 其key=EnumCacheType, value=BaseCache */
private static HashMap<String, HashMap<EnumCacheType, BaseCache>> list = new HashMap<String, HashMap<EnumCacheType, BaseCache>>();
public static void register(String companyDBName, EnumCacheType ect, BaseCache bc) {
HashMap<EnumCacheType, BaseCache> hmNow = list.get(companyDBName);
if (hmNow != null) {
hmNow.put(ect, bc);
} else {
HashMap<EnumCacheType, BaseCache> hm = new HashMap<EnumCacheType, BaseCache>();
hm.put(ect, bc);
list.put(companyDBName, hm);
}
}
public static BaseCache getCache(String companyDBName, EnumCacheType ect) {
HashMap<EnumCacheType, BaseCache> hm = list.get(companyDBName);
BaseCache bm = hm.get(ect);
if(bm == null) {
System.out.println("CacheManager意外:companyDBName=" + companyDBName + "\tect=" + ect.getName());
}
return hm.get(ect);
}
}
缓存管理类有一个hashmap类型的list变量,它的key存储的是公司的名称(存在多个公司),value是一个hashmap2。hashmap2的key是枚举类EnumCacheType,value是缓存具体Model对象的类BaseCache。
缓存管理类有两个方法,register方法负责将缓存数据,getCache方法负责获取缓存的数据。
EnumCacheType类,将需要缓存的Model对象都定义了一种缓存类型:
查看代码
public class CacheType extends BaseModel {
private static final long serialVersionUID = 2033798065231436579L;
private static int enumIndex = 0;
public enum EnumCacheType {
ECT_Commodity("Commodity", enumIndex++), //
ECT_Promotion("Promotion", enumIndex++), //
ECT_Category("Category", enumIndex++), //
ECT_Brand("Brand", enumIndex++), //
ECT_Staff("Staff", enumIndex++), //
ECT_Vip("Vip", enumIndex++), //
ECT_POS("Pos", enumIndex++), //
ECT_ConfigGeneral("ConfigGeneral", enumIndex++), //
ECT_ConfigCacheSize("ConfigCacheSize", enumIndex++), //
ECT_VipCategory("VipCategory", enumIndex++), //
ECT_InventorySheet("InventorySheet", enumIndex++), //
ECT_Provider("Provider", enumIndex++), //
ECT_ProviderDistrict("ProviderDistrict", enumIndex++), //
ECT_PurchasingOrder("PurchasingOrder", enumIndex++), //
ECT_Warehouse("Warehouse", enumIndex++), //
ECT_Warehousing("Warehousing", enumIndex++), //
ECT_SmallSheet("SmallSheet", enumIndex++), //
ECT_StaffPermission("StaffPermission", enumIndex++), //
ECT_CategoryParent("CategoryParent", enumIndex++), //
ECT_Barcodes("Barcodes", enumIndex++), //
ECT_RetailTradePromoting("RetailTradePromoting", enumIndex++), //
ECT_Company("Company", enumIndex++), //
ECT_Shop("Shop", enumIndex++), //
ECT_BxStaff("BxStaff", enumIndex++), //
ECT_BXConfigGeneral("ECT_BXConfigGeneral", enumIndex++), //
ECT_StaffBelonging("ECT_StaffBelonging", enumIndex++),//
ECT_VipCard("VipCard", enumIndex++);//
private String name;
private int index;
private EnumCacheType(String name, int index) {
this.name = name;
this.index = index;
}
public static String getName(int index) {
for (EnumCacheType c : EnumCacheType.values()) {
if (c.getIndex() == index) {
return c.name;
}
}
return null;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
}
@Override
public String checkUpdate(int iUseCaseID) {
return "";
}
@Override
public String checkDelete(int iUseCaseID) {
return "";
}
@Override
public String checkCreate(int iUseCaseID) {
return "";
}
@Override
public String checkRetrieve1(int iUseCaseID) {
return "";
}
@Override
public String checkRetrieveN(int iUseCaseID) {
return "";
}
}
2、定义缓存类来存储缓存数据。
BaseCache类,是所有Model缓存类(如StaffCache)的父类,存放缓存类的公共属性和方法:
/** 小块数据的缓存类。 如果需要经常读取小块缓存,偶尔更新缓存,可以使用本类的子类。 */
@Component("baseCache")
@Scope("prototype")
public abstract class BaseCache implements Cloneable
3、在项目启动的时候,开始加载缓存。
查看代码
/** 加载公共DB中的公司缓存和每个私有DB的普通缓存和同步缓存。 */
@PostConstruct
private void load() {
resolveCurrentEnvAndDomain();
doLoad(BaseAction.DBName_Public);
register(BaseAction.DBName_Public);
// 遍历所有公司的DB名称,加载所有私有DB的相关数据到缓存
List<BaseModel> list = CacheManager.getCache(BaseAction.DBName_Public, EnumCacheType.ECT_Company).readN(false, false);
// 先加载公共DB到缓存中
for (BaseModel bm : list) {
Company com = (Company) bm;
if (com.getDbName().equals(BaseAction.DBName_Public)) {
bxConfigGeneralCache.load(BaseAction.DBName_Public);
bxStaffCache.load(BaseAction.DBName_Public);
// vipBelongingCache.load(BaseAction.DBName_Public);
break;
}
}
// 再加载所有私有DB的缓存。如果其中一个失败,继续往下加载其它私有DB
for (BaseModel bm : list) {
Company com = (Company) bm;
try {
if (com.getDbName().equals(BaseAction.DBName_Public)) {
continue;
}
// 加载私有DB的信息到缓存
// 每一个公司的缓存,都是私有的,不能和其它公司共享,所以下面的代码,每一块缓存内部都会clone一块然后绑定到com中,不会干扰其它com
// 如果以下代码有变,则CompanyAction.loadCacheFromPrivateDB()也需要跟着变
configGeneralCache.load(com.getDbName()); // 配置需要首先加载
configCacheSizeCache.load(com.getDbName());// 配置需要首先加载
configCacheSizeCache.checkIfLoadOK(com.getDbName());
brandCache.load(com.getDbName());
brandSyncCache.load(com.getDbName());
categoryCache.load(com.getDbName());
categoryParentCache.load(com.getDbName());
categorySyncCache.load(com.getDbName());
commodityCache.load(com.getDbName());
commoditySyncCache.load(com.getDbName());
providerCache.load(com.getDbName());
providerDistrictCache.load(com.getDbName());
purchasingOrderCache.load(com.getDbName());
promotionCache.load(com.getDbName());
promotionSyncCache.load(com.getDbName());
vipCache.load(com.getDbName());
vipCategoryCache.load(com.getDbName());
warehouseCache.load(com.getDbName());
warehousingCache.load(com.getDbName());
barcodesCache.load(com.getDbName());
barcodesSyncCache.load(com.getDbName());
inventorySheetCache.load(com.getDbName());
posCache.load(com.getDbName());
posSyncCache.load(com.getDbName());
retailTradePromotingCache.load(com.getDbName());
shopCache.load(com.getDbName());
smallSheetCache.load(com.getDbName());
staffCache.load(com.getDbName());
staffPermissionCache.load(com.getDbName());
staffBelongingCache.load(com.getDbName());
vipCardCache.load(com.getDbName());
// 记录每个公司当天的销售总额和零售笔数
RealTimeSalesStatisticsToday realTimeSST = new RealTimeSalesStatisticsToday(0.000000d, 0, new Date());
RetailTradeAction.todaysSaleCache.put(com.getDbName(), realTimeSST);
// 在这里添加缓存后需要在CompanyAction的loadCacheFromPrivateDB()添加相应的xxxCache,也要修改CompanyCP.loadCache(Company company)
} catch (Exception e) {
logger.error("加载缓存异常,异常DB为:" + com.getDbName() + "\n,异常信息为:" + e.getMessage());
}
}
// 启动夜间任务的所有线程
ts.start();
}
具体的ModelCache类,如CommodityCache缓存的过程。
调用load方法:
public void load(String dbName) {
doLoad(dbName);
register(dbName);
}
doLoad方法负责从数据库中查询数据:
@SuppressWarnings("unchecked")
protected void doLoad(String dbName) {
logger.info("加载缓存(" + sCacheName + ")...");
BaseModel b = getMasterModel(dbName);
DataSourceContextHolder.setDbName(dbName);
List<?> ls = getMasterBO().retrieveNObject(BaseBO.SYSTEM, BaseBO.INVALID_CASE_ID, b);
if (getMasterBO().getLastErrorCode() != EnumErrorCode.EC_NoError) {
logger.error("加载缓存(" + sCacheName + ")失败!请重启服务器!!错误信息:" + getMasterBO().printErrorInfo());
throw new RuntimeException(BaseModel.ERROR_Tag);
}
doLoadSlave(ls, dbName);
logger.info("加载缓存(" + sCacheName + ")成功!");
writeN((List<BaseModel>) ls); // 将DB的数据缓存进本对象的hashtable中
}
writeN方法负责将数据缓存到CommodityCache对象的HashTable变量中:
/** 清除所有旧的缓存,写入新的数据 <br />
* TODO:目前存在的问题:load()中缓存加载时,并不能确定要加载多少个对象。 */
public void writeN(List<BaseModel> ls) {
System.out.println("writeN正在加锁...." + lock.writeLock().getHoldCount());
lock.writeLock().lock();
try {
doWriteN(ls);
} catch (Exception e) {
logger.error("writeN异常:" + e.getMessage());
}
lock.writeLock().unlock();
System.out.println("writeN已经解锁" + lock.writeLock().getHoldCount());
}
doWriteN方法:
protected void doWriteN(List<BaseModel> ls) {
setCache(ls);
}
setCache方法:
public Hashtable<String, BaseModel> htCommodity;
@Override
protected void setCache(List<BaseModel> list) {
htCommodity = listToHashtable(list);
}
listToHashtable方法,以ID为key,Commodity对象为value,存到HashTable中:
/** 浅复制一个list到Hashtable中。在外部不应再对list中的元素进行任何读写操作,否则很可能会引起严重问题
*
* @param ls
* @return */
protected Hashtable<String, BaseModel> listToHashtable(List<BaseModel> ls) {
if (ls == null) {
throw new RuntimeException("参数ls为null!");
}
Hashtable<String, BaseModel> ht = new Hashtable<String, BaseModel>(ls.size());
for (BaseModel bm : ls) {
ht.put(String.valueOf(bm.getID()), bm);
}
return ht;
}
回到load方法,接下来调用register方法,将缓存放到CacheManager统一管理:
public void load(String dbName) {
doLoad(dbName);
register(dbName);
}
register方法
/** 注册内存,以让后面可以正常读写 */
protected void register(String dbName) {
try {
CacheManager.register(dbName, EnumCacheType.values()[getCacheType()], (BaseCache) this.clone()); // 实际缓存的对象,是本对象的一个副本
deleteAll();// 清空本对象的缓存,本对象的副本的hashtable仍然有数据,没有被清除
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
CacheManager.register方法存放了CommodityCache的一个克隆对象,所以需要将CommodityCache类的HashTable数据清空,调用deleteAll方法:
/** 清除所有旧的缓存,但是不写入新的数据 */
public void deleteAll() {
System.out.println("deleteAll正在加锁...." + lock.writeLock().getHoldCount());
lock.writeLock().lock();
try {
doDeleteAll();
} catch (Exception e) {
logger.error("deleteAll异常:" + e.getMessage());
}
lock.writeLock().unlock();
System.out.println("deleteAll已经解锁" + lock.writeLock().getHoldCount());
}
doDeleteAll调用了setCache方法,存了一个没有数据的ArrayList对象,实现了清空操作。
protected void doDeleteAll() {
setCache(new ArrayList<BaseModel>());
}
4、从缓存管理类中读取缓存数据。
通过调用CacheManager的getCache方法从缓存中获取数据:
Commodity commodity = (Commodity) CacheManager.getCache(dbName, EnumCacheType.ECT_Commodity).read1(comm.getID(), getStaffFromSession(session).getID(), ecOut, dbName);
根据公司名称dbName,缓存类型EnumCacheType.ECT_Commodity获取到CommodityCache缓存对象:
public static BaseCache getCache(String companyDBName, EnumCacheType ect) {
HashMap<EnumCacheType, BaseCache> hm = list.get(companyDBName);
BaseCache bm = hm.get(ect);
if(bm == null) {
System.out.println("CacheManager意外:companyDBName=" + companyDBName + "\tect=" + ect.getName());
}
return hm.get(ect);
}
read1方法查找CommodityCache对象中的HashTable,从HashTable中获取缓存的数据:
/** 从缓存中clone一行数据 */
public BaseModel read1(int id, int staffID, ErrorInfo ecOut, String dbName) {
BaseModel bmToRead = null;
//
lock.readLock().lock();
try {
bmToRead = doRead1(id, staffID, ecOut, dbName);
} catch (Exception e) {
logger.error("read1出错,信息:" + e.getMessage() + "\nID=" + id);
}
lock.readLock().unlock();
return bmToRead;
}
protected BaseModel doRead1(int id, int staffID, ErrorInfo ecOut, String dbName) {
ecOut.setErrorCode(EnumErrorCode.EC_NoError);
// 从缓存中查找该行数据并返回之
if (getCache().containsKey(String.valueOf(id))) {
++iReadCacheHitNO;
try {
return getCache().get(String.valueOf(id)).clone();
} catch (CloneNotSupportedException e) {
logger.info("doRead1()异常" + e.getMessage());
}
}
// 缓存中没有该行数据,则让子类从DB中获取并加入缓存
BaseModel ret = null;
BaseModel bm = retrieve1(id, staffID, ecOut, dbName);
if (bm != null) {
if (getCache().size() + 1 > getMaxCacheNumber(dbName, staffID)) { // 避免将最近添加的值remove掉
for (Iterator<String> it = getCache().keySet().iterator(); it.hasNext();) {
getCache().remove(it.next());
break;
}
}
getCache().put(String.valueOf(bm.getID()), bm);
try {
ret = bm.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
return ret;
}
@Override
protected Hashtable<String, BaseModel> getCache() {
return htCommodity;
}
5、保持缓存和数据库的一致性。
为了保证数据库和缓存的数据一致,向数据库插入一条记录后,需要在缓存中也插入对应的记录:
// 更新商品普通缓存
CacheManager.getCache(dbName, getCacheType()).write1(bmFromDB, dbName, getStaffFromSession(req.getSession()).getID());
向数据库更新或删除一条记录后,需要删除缓存中对应的一条记录,下次访问缓存找不到数据时,需要查询数据库,再把数据放到缓存中:
// 删除普通对象的缓存
CacheManager.getCache(dbName, getCacheType()).delete1(bmFromDB);
// 缓存中没有该行数据,则让子类从DB中获取并加入缓存
BaseModel ret = null;
BaseModel bm = retrieve1(id, staffID, ecOut, dbName);
if (bm != null) {
if (getCache().size() + 1 > getMaxCacheNumber(dbName, staffID)) { // 避免将最近添加的值remove掉
for (Iterator<String> it = getCache().keySet().iterator(); it.hasNext();) {
getCache().remove(it.next());
break;
}
}
getCache().put(String.valueOf(bm.getID()), bm);
try {
ret = bm.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}