1. 编程式缓存管理
还记得之前讲过的编程式事务管理与声明式事务管理吗,编程式管理说白了就是自己手工编程去管理。
因为手工编程式的管理方式,更加基础,更加容易理解,所以我们从编程式缓存管理说起。
2. 实现方式
其实思路非常简单,缓存是针对方法的,我们将对方法的请求加入缓存中,如果再次对该方法发起同样请求(同一方法且参数相同),则不执行该方法,直接取出缓存即可。
3. 项目准备
为了可以使用AOP,再进行本文内容时,除了前面一直提到Spring的jar包,还需要引入aspectjweaver-1.8.1.jar,这个包是Spring AOP所需要的。
4. 不使用缓存的情况
4.1 定义数据DO、数据访问DAO、数据服务Service
首先定义BlogDao用来访问数据库,根据博客表的id获取博客信息
@Repository // 注册为bean
public class BlogDao {
@Autowired // 自动注入
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
/**
* 查询数据库
*/
public BlogDo getById(Long id) {
System.out.println("执行getById:" + id);
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;
}
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
然后定义BlogService提供操作封装。
@Service//注册bean
public class BlogService {
@Autowired//自动注入
private BlogDao blogDao;
public BlogDo getById(Long id) {
return blogDao.getById(id);
}
}
1
2
3
4
5
6
7
8
9
数据对象BlogDo就不用多说了:
public class BlogDo {
private Long id;
private String title;
private String author;
private String content;
//省略get set
}
1
2
3
4
5
6
7
4.2 编写Spring配置类SpringConfig
在配置类中开启bean扫描,以便将BlogService和BlogDao注册bean,同时注册数据源bean和namedParameterJdbcTemplate用于操作数据库。
@Configuration // 配置类
@ComponentScan(basePackages = { "org.maoge.cachedemo.mannual" }) // 扫描包以便发现注解配置的bean
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;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
4.3 测试
从容器中取出BlogService,然后调用其getList方法进行测试:
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);
// 测试获取列表
for(int i=0;i<10;i++) {
blogService.getById(1L);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
输出结果如下,可见真实的调用了BlogDao的getById方法10次。
执行getById:1
执行getById:1
执行getById:1
执行getById:1
执行getById:1
执行getById:1
执行getById:1
执行getById:1
执行getById:1
执行getById:1
1
2
3
4
5
6
7
8
9
10
5. 编程式管理管理的情况
5.1 修改配置类启用缓存并注册缓存管理器
首先通过@EnableCaching 启用缓存功能。然后注册缓存管理器bean,我们通过缓存管理器对缓存进行管理。
@Configuration // 配置类
@ComponentScan(basePackages = { "org.maoge.cachedemo.mannual" }) // 扫描包以便发现注解配置的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;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
务必注意,我们像缓存管理其中添加了一个名字为blogs的ConcurrentMapCache类型对象,该对象是个字典形式的缓存,也就是说在blogs这个缓存,参数只要确定了,其缓存的值就确定了。
5.2 调用BlogService.getById时添加缓存逻辑
不存在缓存时,将结果放入缓存;存在缓存,则不再执行具体方法,直接返回缓存。
@Service//注册bean
public class BlogService {
@Autowired//自动注入
private BlogDao blogDao;
@Autowired//自欧东注入
private CacheManager cacheManager;
public BlogDo getById(Long id) {
//取出名字为blogs的缓存
Cache cache=cacheManager.getCache("blogs");
//判断针对参数id的值,是否有缓存存在
if(cache.get(id)==null) {//不存在,则查询数据库,并将结果纳入缓存
BlogDo result=blogDao.getById(id);
cache.put(id, result);
return result;
}else {//存在则直接返回缓存即可
return (BlogDo)cache.get(id).get();//这个取值方式稍微有点绕
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
看明白了吗,首先缓存管理器可能要管理很多个缓存,比如用户缓存、角色缓存、机构信息缓存等等,所以需要先定义有几个缓存Cache,此处只有一个就是cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("blogs")));。
然后,一个缓存其实就是一个线程安全的键值对,将方法的参数作为键,方法的返回值作为值,存储起来。当新的请求到达时,如果请求参数都相同,则不必再去查数据库了,直接返回缓存就行,反正请求的是一个东西。
5.3 测试
同样代码进行测试
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);
// 测试获取列表
for(int i=0;i<10;i++) {
blogService.getById(1L);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
结果输出如下:
执行getById:1
1
这意味着什么,我们成功的使用了缓存,对数据库的访问得到了极大的减少。如果使用得当的话,系统性能会得到一个跃进!
如果你不放心的话,可以下个断点,看看是不是返回的值是正确的。
上面的缓存方式太复杂了,你应该看到了模板代码的痕迹,凡是重复必能抽象优化,这种模板代码用AOP解决,SOEASY啊。
然后缓存其实还有不少注意点,我将在接下来给大家详细演示和解释。