Java使用HashTable实现缓存

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();
			}
		}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值