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的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程