SpringBoot整合Ehcache3的实现步骤

网友投稿 640 2022-09-05


SpringBoot整合Ehcache3的实现步骤

目录前言缓存配置maven引用个性化配置代码注入配置缓存操作缓存预热更新操作查询操作缓存与数据库数据一致性

前言

公司部门老项目要迁移升级java版本,需要进行缓存相关操作,原框架未支持这部分,经过调研java相关缓存方案大致分为ehcache和redis两种,redis的value最大值为500mb且超过1mb会对存取有性能影响,业务系统需要支持列表查询缓存就不可避免的涉及到大量的数据存取过滤,ehcache支持内存+磁盘缓存不用担心缓存容量问题,所以框架初步版本决定集成ehcache3,设计流程结构如下图所示

缓存配置

maven引用

org.springframework.boot

spring-boot-starter-cache

org.ehcache

ehcache

个性化配置

#缓存配置

cache:

ehcache:

heap: 1000

offheap: 100

disk: 500

diskDir: tempfiles/cache/

@Component

@ConfigurationProperties("frmae.cache.ehcache")

public class EhcacheConfiguration {

/**

* ehcache heap大小

* jvm内存中缓存的key数量

*/

private int heap;

/**

* ehcache offheap大小

* 堆外内存大小, 单位: MB

*/

private int offheap;

/**

* 磁盘持久化目录

*/

private String diskDir;

/**

* ehcache disk

* 持久化到磁盘的大小, 单位: MB

* diskDir有效时才生效

*/

private int disk;

public EhcacheConfiguration(){

heap = 1000;

offheap = 100;

disk = 500;

diskDir = "tempfiles/cache/";

}

}

代码注入配置

因为springboot默认缓存优先注入redis配置,所以需要手动声明bean进行注入,同时ehcache的value值必须支持序列化接口,不能使用Object代替,这里声明一个缓存基类,所有缓存value对象必须继承该类

public class BaseSystemObject implements Serializable {

}

@Configuration

@EnableCaching

public class EhcacheConfig {

@Autowired

private EhcacheConfiguration ehcacheConfiguration;

@Autowired

private ApplicationContext context;

@Bean(name = "ehCacheManager")

public CacheManager getCacheManager() {

//资源池生成器配置持久化

ResourcePoolsBuilder resourcePoolsBuilder = ResourcePoolsBuilder.newResourcePoolsBuilder()

// 堆内缓存大小

.heap(ehcacheConfiguration.getHeap(), EntryUnit.ENTRIES)

// 堆外缓存大小

.offheap(ehcacheConfiguration.getOffheap(), MemoryUnit.MB)

// 文件缓存大小

.disk(ehcacheConfiguration.getDisk(), MemoryUnit.MB);

//生成配置

ExpiryPolicy expiryPolicy = ExpiryPolicyBuilder.noExpiration();

CacheConfiguration config = CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, BaseSystemObject.class, resourcePoolsBuilder)

//设置永不过期

.withExpiry(expiryPolicy)

.build();

CacheManagerBuilder cacheManagerBuilder = CacheManagerBuilder.newCacheManagerBuilder()

.with(CacheManagerBuilder.persistence(ehcacheConfiguration.getDiskDir()));

return cacheManagerBuilder.build(true);

}

}

缓存操作

缓存预热

针对缓存框架选择的双写策略,即数据库和缓存同时写入,所以在系统启动时需要预先将数据库数据加载到缓存中针对单表声明自定义注解,个性化缓存定义自定义接口

@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

public @interface HPCache {

}

public interface IHPCacheInitService {

String getCacheName();

void initCache();

}

系统初始化时同步进行缓存初始化,扫描注解实体类与接口实现Bean

@Async

public void initCache(Class runtimeClass, List extraPackageNameList) {

List> cacheEntityList = new ArrayList<>();

if (!runtimeClass.getPackage().getName().equals(Application.class.getPackage().getName())) {

cacheEntityList.addAll(ScanUtil.getAllClassByPackageName_Annotation(runtimeClass.getPackage(), HPCache.class));

}

for (String packageName : extraPackageNameList) {

cacheEntityList.addAll(ScanUtil.getAllClassByPackageName_Annotation(packageName, HPCache.class));

}

for (Class clazz : cacheEntityList) {

TableName tableName = (TableName) clazz.getAnnotation(TableName.class);

List> resultList = commonDTO.selectList(tableName.value(), "*", "1=1", "", new HashMap<>(), false);

for (LinkedHashMap map : resultList) {

Cache cache = cacheManager.getCache(clazz.getName(), String.class, BaseSystemObject.class);

String unitguid = ConvertOp.convert2String(map.get("UnitGuid"));

try {

Object obj = clazz.newInstance();

obj = ConvertOp.convertLinkHashMapToBean(map, obj);

cache.put(unitguid, obj);

} catch (Exception e) {

e.printStackTrace();

}

}

}

//自定义缓存

Map res = context.getBeansOfType(IHPCacheInitService.class);

for (Map.Entry en : res.entrySet()) {

IHPCacheInitService service = (IHPCacheInitService) en.getValue();

service.initCache();

}

System.out.println("缓存初始化完毕");

}

需要注意,在EhcacheConfig配置类中需要进行缓存名称的提前注册,否则会导致操作缓存时空指针异常

Map annotatedBeans = context.getBeansWithAnnotation(SpringBootApplication.class);

Class runtimeClass = annotatedBeans.values().toArray()[0].getClass();

//do,dao扫描

List extraPackageNameList = new ArrayList();

extraPackageNameList.add(Application.class.getPackage().getName());

List> cacheEntityList = new ArrayList<>();

if (!runtimeClass.getPackage().getName().equals(Application.class.getPackage().getName())) {

cacheEntityList.addAll(ScanUtil.getAllClassByPackageName_Annotation(runtimeClass.getPackage(), HPCache.class));

}

for (String packageName : extraPackageNameList) {

cacheEntityList.addAll(ScanUtil.getAllClassByPackageName_Annotation(packageName, HPCache.class));

}

for (Class clazz : cacheEntityList) {

cacheManagerBuilder = cacheManagerBuilder.withCache(clazz.getName(), config);

}

//自定义缓存

Map res = context.getBeansOfType(IHPCacheInitService.class);

for (Map.Entry en :res.entrySet()) {

IHPCacheInitService service = (IHPCacheInitService)en.getValue();

cacheManagerBuilder = cacheManagerBuilder.withCache(service.getCacheName(), config);

}

更新操作

手动获取ehcache的bean对象,调用put,repalce,delete方法进行操作

private CacheManager cacheManager = (CacheManager) SpringBootBeanUtil.getBean("ehCacheManager");

public void executeUpdateOperation(String cacheName, String key, BaseSystemObject value) {

Cache cache = cacheManager.getCache(cacheName, String.class, BaseSystemObject.class);

if (cache.containsKey(key)) {

cache.replace(key, value);

} else {

cache.put(key, value);

}

}

public void executeDeleteOperation(String cacheName, String key) {

Cache cache = cacheManager.getCache(cacheName, String.class, BaseSystemObject.class);

cache.remove(key);

}

查询操作

缓存存储单表以主键—object形式存储,个性化缓存为key-object形式存储,单条记录可以通过getCache方法查询,列表查询需要取出整个缓存按条件进行过滤

public Object getCache(String cacheName, String key){

Cache cache = cacheManager.getCache(cacheName, String.class, BaseSystemObject.class);

return cache.get(key);

}

public List getAllCache(String cacheName){

List result = new ArrayList<>();

Cache cache = cacheManager.getCache(cacheName, String.class, BaseSystemObject.class);

Iterator iter = cache.iterator();

while (iter.hasNext()) {

Cache.Entry entry = (Cache.Entry) iter.next();

result.add(entry.getValue());

}

return result;

}

缓存与数据库数据一致性

数据库数据操作与缓存操作顺序为先操作数据后操作缓存,在开启数据库事务的情况下针对单条数据单次操作是没有问题的,如果是组合操作一旦数据库操作发生异常回滚,缓存并没有回滚就会导致数据的不一致,比如执行顺序为dbop1=》cacheop1=》dbop2=》cacheop2,dbop2异常,cacheop1的操作已经更改了缓存这里选择的方案是在数据库全部执行完毕后统一操作缓存,这个方案有一个缺点是如果缓存操作发生异常还是会出现上述问题,实际过程中缓存只是对内存的操作异常概率较小,对缓存操作持乐观状态,同时我们提供手动重置缓存的功能,算是一个折中方案,下面概述该方案的一个实现

声明自定义缓存事务注解

@Target({ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

public @interface CacheTransactional {

}

声明切面监听,在标记了CacheTransactional注解的方法执行前进行Redis标识,统一执行完方法体后执行缓存操作将缓存操作以线程id区分放入待执行队列中序列化到redis,提供方法统一操作

public class CacheExecuteModel implements Serializable {

private String obejctClazzName;

private String cacheName;

private String key;

private BaseSystemObject value;

private String executeType;

}

private CacheManager cacheManager = (CacheMajyogaKkHdnager) SpringBootBeanUtil.getBean("ehCacheManager");

@Autowired

private RedisUtil redisUtil;

public void putCacheIntoTransition(){

String threadID = Thread.currentThread().getName();

System.out.println("init threadid:"+threadID);

CacheExecuteModel cacheExecuteModel = new CacheExecuteModel();

cacheExecuteModel.setExecuteType("option");

redisUtil.redisTemplateSetForCollection(threadID,cacheExecuteModel, GlobalEnum.RedisDBNum.Cache.get_value());

redisUtil.setExpire(threadID,5, TimeUnit.MINUTES, GlobalEnum.RedisDBNum.Cache.get_value());

}

public void putCache(String cacheName, String key, BaseSystemObject value) {

if(checkCacheOptinionInTransition()){

String threadID = Thread.currentThread().getName();

CacheExecuteModel cacheExecuteModel = new CacheExecuteModel("update", cacheName, key, value.getClass().getName(),value);

redisUtil.redisTemplateSetForCollection(threadID,cacheExecuteModel, GlobalEnum.RedisDBNum.Cache.get_value());

redisUtil.setExpire(threadID,5, TimeUnit.MINUTES, GlobalEnum.RedisDBNum.Cache.get_value());

}else{

executeUpdateOperation(cacheName,key,value);

}

}

public void deleteCache(String cacheName, String key) {

if(checkCacheOptinionInTransition()){

String threadID = Thread.currentThread().getName();

CacheExecuteModel cacheExecuteModel = new CacheExecuteModel("delete", cacheName, key);

redisUtil.redisTemplateSetForCollection(threadID,cacheExecuteModel, GlobalEnum.RedisDBNum.Cache.get_value());

redisUtil.setExpire(threadID,5, TimeUnit.MINUTES, GlobalEnum.RedisDBNum.Cache.get_value());

}else{

executeDeleteOperation(cacheName,key);

}

}

public void executeOperation(){

String threadID = Thread.currentThread().getName();

if(checkCacheOptinionInTransition()){

List executeList = redisUtil.redisTemplateGetForCollectionAll(threadID, GlobalEnum.RedisDBNum.Cache.get_value());

for (LinkedHashMap obj:executeList) {

String executeType = ConvertOp.convert2String(obj.get("executeType"));

if(executeType.contains("option")){

continue;

}

String obejctClazzName = ConvertOp.convert2String(obj.get("obejctClazzName"));

String cacheName = ConvertOp.convert2String(obj.get("cacheName"));

String key = ConvertOp.convert2String(obj.get("key"));

LinkedHashMap valueMap = (LinkedHahttp://shMap)obj.get("value");

String valueMapjson = JSON.toJSONString(valueMap);

try{

Object valueInstance = JSON.parseObject(valueMapJson,Class.forName(obejctClazzName));

if(executeType.equals("update")){

executeUpdateOperation(cacheName,key,(BaseSystemObject)valueInstance);

}else if(executeType.equals("delete")){

executeDeleteOperation(cacheName,key);

}

}catch (Exception e){

e.printStackTrace();

}

}

redisUtil.redisTemplateRemove(threadID,GlobalEnum.RedisDBNum.Cache.get_value());

}

}

public boolean checkCacheOptinionInTransition(){

String threadID = Thread.currentThread().getName();

System.out.println("check threadid:"+threadID);

return redisUtil.isValid(threadID, GlobalEnum.RedisDBNum.Cache.get_value());

}


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:将Komodo Edit打造成Python开发的IDE
下一篇:电影票API接口对接,电影票接口也可以调用啦
相关文章

 发表评论

暂时没有评论,来抢沙发吧~