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

相关文章
|
12天前
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
138 73
|
7天前
|
Java Spring 容器
【SpringFramework】Spring IoC-基于注解的实现
本文主要记录基于Spring注解实现IoC容器和DI相关知识。
40 21
|
12天前
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
|
12天前
|
Java Spring
【Spring配置】idea编码格式导致注解汉字无法保存
问题一:对于同一个项目,我们在使用idea的过程中,使用汉字注解完后,再打开该项目,汉字变成乱码问题二:本来a项目中,汉字注解调试好了,没有乱码了,但是创建出来的新的项目,写的注解又成乱码了。
|
2月前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
150 2
|
2月前
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
61 2
|
2月前
|
前端开发 Java 开发者
Spring MVC中的控制器:@Controller注解全解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序控制层的核心。它不仅简化了控制器的定义,还提供了灵活的请求映射和处理机制。本文将深入探讨`@Controller`注解的用法、特点以及在实际开发中的应用。
109 0
|
3月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
261 2
|
14天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
20天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
75 14

热门文章

最新文章