Spring之路(48)–使用注解实现声明式缓存管理是So Easy

简介: 本文目录1. 背景2. 总体流程3. 具体实例3.1 编写SpringConfig配置类3.2 编写数据DO、数据访问DAO3.3 编写数据服务Service,并开启缓存3.4 编写测试类,具体解释缓存执行过程4. 补充

1. 背景

上一篇我们使用编程式缓存管理方式,演示了缓存如何配置,如何手工编程使用。些微的有难么一丝丝麻烦,所以本篇及其简洁的声明式缓存管理来了,直接奉上,简单粗暴,体会Spring之美。


当然,与声明式事务管理(使用注解开启事务)一样,使用注解的声明式缓存管理,也是通过AOP实现的,这个之前也论述的很清楚了,通过Spring AOP封装模板代码,是Spring里面非常惯用的封装技巧。


2. 总体流程

OK,首先也是建立一个Spring的工程,引入相关的jar包,注意除了一直在用的Spring相关包,还需要一个aspectjweaver-1.8.1.jar。


然后通过配置类,配置CacheManager,我们需要使用的缓存名称。CacheManager缓存管理器是Spring提供好的缓存模块,拿来直接用就是了,它本质上是一个字典容器。


最后,对需要使用缓存的方法,直接添加注解即可开启注解,Spring Cache会自动根据缓存名称+参数是否相同来决定真实调用方法还是直接返回缓存。


由于演示实例中只对博客表进行访问,所以只需要一个缓存,名称定义为blogs即可。


3. 具体实例

3.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;

}

}


3.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.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);

}

}


3.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相关的缓存条目全部清除,所以下次再调用查询肯定会真正查询数据库。


但是当我们第二次执行一个查询方法,且参数相同,此时缓存已存在,所以不再真正访问数据库了,直接返回缓存。


4. 补充

上面的做法,比较简单粗暴,如果发生了更新,直接强制清空所有缓存,如果要管理的更精细一点的话,直接使用@CacheEvict,会仅仅删除指定参数对应的缓存,同时如果直接使用@CachePut,会仅仅将方法的返回值加入缓存。


这样会更加减少对数据库的访问次数,但是控制粒度这么细,要设计的精细点,别出现什么问题导致数据发生变化了还一直在取缓存。


OK,我认为缓存应该是对小数据量的(相对于关系数据库中海量的存储)、高访问频次的、低修改频次的热点数据的操作,既然修改频次低且数据量小,所以完全可以采用当发生变化则直接清空所有缓存条目的做法,虽然简单粗暴,但是毕竟安全可靠~

相关文章
|
1月前
|
缓存 监控 Java
SpringBoot @Scheduled 注解详解
使用`@Scheduled`注解实现方法周期性执行,支持固定间隔、延迟或Cron表达式触发,基于Spring Task,适用于日志清理、数据同步等定时任务场景。需启用`@EnableScheduling`,注意线程阻塞与分布式重复问题,推荐结合`@Async`异步处理,提升任务调度效率。
417 128
|
21天前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
193 2
|
1月前
|
XML Java 数据格式
常用SpringBoot注解汇总与用法说明
这些注解的使用和组合是Spring Boot快速开发和微服务实现的基础,通过它们,可以有效地指导Spring容器进行类发现、自动装配、配置、代理和管理等核心功能。开发者应当根据项目实际需求,运用这些注解来优化代码结构和服务逻辑。
219 12
|
1月前
|
Java 测试技术 数据库
使用Spring的@Retryable注解进行自动重试
在现代软件开发中,容错性和弹性至关重要。Spring框架提供的`@Retryable`注解为处理瞬时故障提供了一种声明式、可配置的重试机制,使开发者能够以简洁的方式增强应用的自我恢复能力。本文深入解析了`@Retryable`的使用方法及其参数配置,并结合`@Recover`实现失败回退策略,帮助构建更健壮、可靠的应用程序。
189 1
使用Spring的@Retryable注解进行自动重试
|
1月前
|
传感器 Java 数据库
探索Spring Boot的@Conditional注解的上下文配置
Spring Boot 的 `@Conditional` 注解可根据不同条件动态控制 Bean 的加载,提升应用的灵活性与可配置性。本文深入解析其用法与优势,并结合实例展示如何通过自定义条件类实现环境适配的智能配置。
127 0
探索Spring Boot的@Conditional注解的上下文配置
|
1月前
|
智能设计 Java 测试技术
Spring中最大化@Lazy注解,实现资源高效利用
本文深入探讨了 Spring 框架中的 `@Lazy` 注解,介绍了其在资源管理和性能优化中的作用。通过延迟初始化 Bean,`@Lazy` 可显著提升应用启动速度,合理利用系统资源,并增强对 Bean 生命周期的控制。文章还分析了 `@Lazy` 的工作机制、使用场景、最佳实践以及常见陷阱与解决方案,帮助开发者更高效地构建可扩展、高性能的 Spring 应用程序。
Spring中最大化@Lazy注解,实现资源高效利用
|
1月前
|
Java 测试技术 编译器
@GrpcService使用注解在 Spring Boot 中开始使用 gRPC
本文介绍了如何在Spring Boot应用中集成gRPC框架,使用`@GrpcService`注解实现高效、可扩展的服务间通信。内容涵盖gRPC与Protocol Buffers的原理、环境配置、服务定义与实现、测试方法等,帮助开发者快速构建高性能的微服务系统。
367 0
|
5月前
|
缓存 NoSQL 关系型数据库
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
|
1月前
|
存储 缓存 NoSQL
Redis专题-实战篇二-商户查询缓存
本文介绍了缓存的基本概念、应用场景及实现方式,涵盖Redis缓存设计、缓存更新策略、缓存穿透问题及其解决方案。重点讲解了缓存空对象与布隆过滤器的使用,并通过代码示例演示了商铺查询的缓存优化实践。
136 1
Redis专题-实战篇二-商户查询缓存
|
27天前
|
缓存 负载均衡 监控
135_负载均衡:Redis缓存 - 提高缓存命中率的配置与最佳实践
在现代大型语言模型(LLM)部署架构中,缓存系统扮演着至关重要的角色。随着LLM应用规模的不断扩大和用户需求的持续增长,如何构建高效、可靠的缓存架构成为系统性能优化的核心挑战。Redis作为业界领先的内存数据库,因其高性能、丰富的数据结构和灵活的配置选项,已成为LLM部署中首选的缓存解决方案。

热门文章

最新文章