Spring之路(48)–使用注解实现声明式缓存管理是So Easy
2020/2/20 8:09:33
本文主要是介绍Spring之路(48)–使用注解实现声明式缓存管理是So Easy,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
兄弟们,今日头条搜索
三线城市程序员老陈
关注我,我将持续不断推出视频教程。
背景
上一篇我们使用编程式缓存管理方式,演示了缓存如何配置,如何手工编程使用。些微的有难么一丝丝麻烦,所以本篇及其简洁的声明式缓存管理来了,直接奉上,简单粗暴,体会Spring之美。
当然,与声明式事务管理(使用注解开启事务)一样,使用注解的声明式缓存管理,也是通过AOP实现的,这个之前也论述的很清楚了,通过Spring AOP封装模板代码,是Spring里面非常惯用的封装技巧。
总体流程
OK,首先也是建立一个Spring的工程,引入相关的jar包,注意除了一直在用的Spring相关包,还需要一个aspectjweaver-1.8.1.jar
。
然后通过配置类,配置CacheManager,我们需要使用的缓存名称。CacheManager缓存管理器是Spring提供好的缓存模块,拿来直接用就是了,它本质上是一个字典容器。
最后,对需要使用缓存的方法,直接添加注解即可开启注解,Spring Cache会自动根据缓存名称+参数
是否相同来决定真实调用方法还是直接返回缓存。
由于演示实例中只对博客表进行访问,所以只需要一个缓存,名称定义为blogs即可。
具体实例
1、编写SpringConfig配置类
启用缓存,启用自动扫描,注册数据源dataSource,注册数据库操作组件namedParameterJdbcTemplate,然后注册缓存管理器cacheManage,都是常规操作了,不多扯了,代码奉上:
@Configuration // 配置类 @ComponentScan(basePackages = { "org.maoge.cachedemo.byannotation" }) // 扫描包以便发现注解配置的bean @EnableCaching // 启用缓存 public class SpringConfig { // 配置数据源 @Bean public DataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/myblog?useUnicode=true&characterEncoding=utf-8"); dataSource.setUsername("root"); dataSource.setPassword("Easy@0122"); return dataSource; } // 配置namedParameterJdbcTemplate组件 @Bean public NamedParameterJdbcTemplate namedParameterJdbcTemplate() { NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(dataSource());// 注入dataSource return template; } // 配置缓存管理器 @Bean public CacheManager cacheManager() { SimpleCacheManager cacheManager = new SimpleCacheManager(); // 缓存管理器中有很多缓存caches,其中一个名字为blogs cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("blogs"))); return cacheManager; } }
2、编写数据DO、数据访问DAO
编写数据BlogDo,与数据库表一一对应
public class BlogDo { private Long id; private String title; private String author; private String content; //省略get set... }
编写数据访问BlogDao,用于操作数据库,此时重点来了,增删改查那是样样不缺啊,完美!
@Repository // 注册为bean public class BlogDao { @Autowired // 自动注入 private NamedParameterJdbcTemplate namedParameterJdbcTemplate; /** * 按id查询 */ public BlogDo getById(Long id) { System.out.println("执行getById"); Map<String, Object> map = new HashMap<>(); map.put("id", id); return namedParameterJdbcTemplate.queryForObject("select * from blog where id=:id", map, new RowMapper<BlogDo>() { @Override public BlogDo mapRow(ResultSet rs, int rowNum) throws SQLException { BlogDo blog = new BlogDo(); blog.setAuthor(rs.getString("author")); blog.setContent(rs.getString("content")); blog.setId(rs.getLong("id")); blog.setTitle(rs.getString("title")); return blog; } }); } /** * 查询列表 */ @Cacheable("blogs") public List<BlogDo> getList() { System.out.println("执行getList"); return namedParameterJdbcTemplate.query("select * from blog", new RowMapper<BlogDo>() { @Override public BlogDo mapRow(ResultSet rs, int rowNum) throws SQLException { BlogDo blog = new BlogDo(); blog.setAuthor(rs.getString("author")); blog.setContent(rs.getString("content")); blog.setId(rs.getLong("id")); blog.setTitle(rs.getString("title")); return blog; } }); } /** * 新增 */ public void insert(BlogDo blog) { System.out.println("执行insert"); Map<String, Object> map = new HashMap<>(); map.put("author", blog.getAuthor()); map.put("content", blog.getContent()); map.put("title", blog.getTitle()); // 注意使用:xxx占位 namedParameterJdbcTemplate.update("insert into blog(author,content,title)values(:author,:content,:title)", map); } /** * 删除 */ public void delete(Long id) { System.out.println("执行delete"); Map<String, Object> map = new HashMap<>(); map.put("id", id); namedParameterJdbcTemplate.update("delete from blog where id =:id", map); } /** * 更新 */ public void update(BlogDo blog) { System.out.println("执行update"); Map<String, Object> map = new HashMap<>(); map.put("author", blog.getAuthor()); map.put("content", blog.getContent()); map.put("title", blog.getTitle()); map.put("id", blog.getId()); namedParameterJdbcTemplate.update("update blog set author=:author,content=:content,title=:title where id=:id", map); } }
3、编写数据服务Service,并开启缓存
通过为方法添加注解@Cacheable("blogs")
,表示该方法如果存在缓存,则直接取缓存,不必真正执行方法。
同时通过为方法添加注解@CacheEvict(value="blogs",allEntries=true)
,来清空名称为blogs的所有缓存条目。
具体实现如下:
@Service // 注册bean public class BlogService { @Autowired // 自动注入 private BlogDao blogDao; // 缓存,当入参出现相同值时会直接返回缓存值 @Cacheable("blogs") public BlogDo getById(Long id) { return blogDao.getById(id); } // 缓存,没有参数方法,再次调用该方法会直接返回缓存值 @Cacheable("blogs") public List<BlogDo> getList() { return blogDao.getList(); } // 执行该方法会清空blogs缓存的所有条目 @CacheEvict(value = "blogs", allEntries = true) public void add(BlogDo blog) { blogDao.insert(blog); } // 执行该方法会清空blogs缓存的所有条目 @CacheEvict(value = "blogs", allEntries = true) public void remove(Long id) { blogDao.delete(id); } // 执行该方法会清空blogs缓存的所有条目 @CacheEvict(value = "blogs", allEntries = true) public void edit(BlogDo blog) { blogDao.update(blog); } }
4、编写测试类,具体解释缓存执行过程
最后,我们通过构造测试类,来看看缓存到底是如何生效的。
public class Main { public static void main(String[] args) throws SQLException { // 获取容器 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); // 获取blogService组件 BlogService blogService = context.getBean("blogService", BlogService.class); // 第一组测试 blogService.getById(1L);//控制台输出:执行getById,第一次调用无缓存,确实调用了BlogDao的getById方法 blogService.getById(1L);//控制台无输出,已经存在针对参数1L的缓存,所以此时不会调用BlogDao.getById // 第二组测试 blogService.getById(2L);//控制台输出:执行getById,此时没有参数为1L的缓存,所以调用 blogService.getById(2L);//控制台无输出,已存在缓存 // 第三组测试 blogService.getList();//控制台输出:执行getList,此时无对应缓存 blogService.getList();//控制台无输出,已有缓存 // 第三组测试 blogService.add(new BlogDo());//注意add方法上添加了@CacheEvict(value = "blogs", allEntries = true),所以执行该方法会清空blogs相关的缓存条目 blogService.getById(1L);//控制台输出:执行getById,因为缓存已经被清空了 blogService.getById(2L);//控制台输出:执行getById,因为缓存已经被清空了 blogService.getList();//控制台输出:执行getList,因为缓存已经被清空了 } }
可见,当我们执行新增、修改、删除等操作时,直接将blogs相关的缓存条目全部清除,所以下次再调用查询肯定会真正查询数据库。
但是当我们第二次执行一个查询方法,且参数相同,此时缓存已存在,所以不再真正访问数据库了,直接返回缓存。
补充
上面的做法,比较简单粗暴,如果发生了更新,直接强制清空所有缓存,如果要管理的更精细一点的话,直接使用@CacheEvict
,会仅仅删除指定参数对应的缓存,同时如果直接使用@CachePut
,会仅仅将方法的返回值加入缓存。
这样会更加减少对数据库的访问次数,但是控制粒度这么细,要设计的精细点,别出现什么问题导致数据发生变化了还一直在取缓存。
OK,我认为缓存应该是对小数据量的(相对于关系数据库中海量的存储)、高访问频次的、低修改频次的热点数据的操作,既然修改频次低且数据量小,所以完全可以采用当发生变化则直接清空所有缓存条目的做法,虽然简单粗暴,但是毕竟安全可靠~
这篇关于Spring之路(48)–使用注解实现声明式缓存管理是So Easy的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-05-13TiDB + ES:转转业财系统亿级数据存储优化实践
- 2024-05-09“2024鸿蒙零基础快速实战-仿抖音App开发(ArkTS版)”实战课程已上线
- 2024-05-09聊聊如何通过arthas-tunnel-server来远程管理所有需要arthas监控的应用
- 2024-05-09log4j2这么配就对了
- 2024-05-09nginx修改Content-Type
- 2024-05-09Redis多数据源,看这篇就够了
- 2024-05-09Google Chrome驱动程序 124.0.6367.62(正式版本)去哪下载?
- 2024-05-09有没有大佬知道这种数据应该怎么抓取呀?
- 2024-05-09这种运行结果里的10.100000001,怎么能最快改成10.1?
- 2024-05-09企业src漏洞挖掘-有意思的命令执行