接下来的几篇我们来讲解一下spring boot 中如何集成spring cache. spring cache 中支持如下cache:
我们只讲解ConcurrentMapCache和EhCache,其他的cache感兴趣的可以查阅相关资料.本文首先来看下ConcurrentMapCache.
在pom文件中加入如下依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
在启动类加上@EnableCaching注解开始spring cache的支持.
由于加入cache,为了模拟的方便,我们加入mybatis,pom文件加入如下配置:
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>1.3.0version>
dependency>
在application.properties中加入如下配置:
mybatis.mapper-locations=classpath:com/example/demo/mapper/*.xml
在启动类中加入如下注解即可:
@MapperScan("com.example.demo.mapper")
UserMapper如下:
package com.example.demo.mapper;
import com.example.demo.model.User;
public interface UserMapper {
int deleteByPrimaryKey(Long id);
int insert(User record);
int insertSelective(User record);
User selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(User record);
int updateByPrimaryKey(User record);
}
User,代码如下:
package com.example.demo.model;
import java.util.Date;
public class User {
private Long id;
private String nickName;
private String emailNew;
private Date registerTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName == null ? null : nickName.trim();
}
public String getEmailNew() {
return emailNew;
}
public void setEmailNew(String emailNew) {
this.emailNew = emailNew == null ? null : emailNew.trim();
}
public Date getRegisterTime() {
return registerTime;
}
public void setRegisterTime(Date registerTime) {
this.registerTime = registerTime;
}
}
UserMapper.xml 如下:
version="1.0" encoding="UTF-8" ?>
"-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
"com.example.demo.mapper.UserMapper" >
id="BaseResultMap" type="com.example.demo.model.User" >
<id column="id" property="id" jdbcType="BIGINT" />
<result column="nick_name" property="nickName" jdbcType="VARCHAR" />
<result column="email_new" property="emailNew" jdbcType="VARCHAR" />
<result column="register_time" property="registerTime" jdbcType="TIMESTAMP" />
id="Base_Column_List" >
id, nick_name, email_new, register_time
id="deleteByPrimaryKey" parameterType="java.lang.Long" >
delete from user
where id = #{id,jdbcType=BIGINT}
id="insert" parameterType="com.example.demo.model.User" >
insert into user (id, nick_name, email_new,
register_time)
values (#{id,jdbcType=BIGINT}, #{nickName,jdbcType=VARCHAR}, #{emailNew,jdbcType=VARCHAR},
#{registerTime,jdbcType=TIMESTAMP})
id="insertSelective" parameterType="com.example.demo.model.User" >
insert into user
"(" suffix=")" suffixOverrides="," >
<if test="id != null" >
id,
if>
<if test="nickName != null" >
nick_name,
if>
<if test="emailNew != null" >
email_new,
if>
<if test="registerTime != null" >
register_time,
if>
"values (" suffix=")" suffixOverrides="," >
<if test="id != null" >
#{id,jdbcType=BIGINT},
if>
<if test="nickName != null" >
#{nickName,jdbcType=VARCHAR},
if>
<if test="emailNew != null" >
#{emailNew,jdbcType=VARCHAR},
if>
<if test="registerTime != null" >
#{registerTime,jdbcType=TIMESTAMP},
if>
id="updateByPrimaryKeySelective" parameterType="com.example.demo.model.User" >
update user
<set >
<if test="nickName != null" >
nick_name = #{nickName,jdbcType=VARCHAR},
if>
<if test="emailNew != null" >
email_new = #{emailNew,jdbcType=VARCHAR},
if>
<if test="registerTime != null" >
register_time = #{registerTime,jdbcType=TIMESTAMP},
if>
set>
where id = #{id,jdbcType=BIGINT}
id="updateByPrimaryKey" parameterType="com.example.demo.model.User" >
update user
set nick_name = #{nickName,jdbcType=VARCHAR},
email_new = #{emailNew,jdbcType=VARCHAR},
register_time = #{registerTime,jdbcType=TIMESTAMP}
where id = #{id,jdbcType=BIGINT}
UserService如下:
package com.example.demo.service;
import com.example.demo.model.User;
public interface UserService {
int insertSelective(User record);
User selectByPrimaryKey(Long id);
User updateByPrimaryKeySelective(User record);
int deleteByPrimaryKey(Long id);
}
UserServiceImpl 如下:
package com.example.demo.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import com.example.demo.mapper.UserMapper;
import com.example.demo.model.User;
@Service
public class UserServiceImpl implements UserService{
private static Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
@Autowired
private UserMapper userMapper;
@Override
public int insertSelective(User record) {
return userMapper.insertSelective(record);
}
@Override
@Cacheable(cacheNames="users",key="#id")
public User selectByPrimaryKey(Long id) {
logger.info("查询数据库");
return userMapper.selectByPrimaryKey(id);
}
@Override
@CachePut(cacheNames="users",key="#record.id")
public User updateByPrimaryKeySelective(User record) {
userMapper.updateByPrimaryKeySelective(record);
return userMapper.selectByPrimaryKey(record.getId());
}
@Override
@CacheEvict(cacheNames="users",key="#id")
public int deleteByPrimaryKey(Long id) {
return userMapper.deleteByPrimaryKey(id);
}
}
这里有必要说明一下spring cache相关的注解:
@Cacheable:配置了findByName函数的返回值将被加入缓存。同时在查询时,会先从缓存中获取,若不存在才再发起对数据库的访问。该注解主要有下面几个参数:
value、cacheNames:两个等同的参数(cacheNames为Spring 4新增,作为value的别名),用于指定缓存存储的集合名。由于Spring 4中新增了@CacheConfig,因此在Spring 3中原本必须有的value属性,也成为非必需项了
key:缓存对象存储在Map集合中的key值,非必需,缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式,比如:@Cacheable(key = “#p0”):使用函数第一个参数作为缓存的key值
@CachePut:配置于函数上,能够根据参数定义条件来进行缓存,它与@Cacheable不同的是,它每次都会真是调用函数,所以主要用于数据新增和修改操作上。它的参数与@Cacheable类似,具体功能可参考上面对@Cacheable参数的解析
参考链接如下:
Spring Boot中的缓存支持(一)注解配置与EhCache使用
Spring4.1新特性——Spring缓存框架增强
UserController,代码如下:
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.example.demo.model.User;
import com.example.demo.service.UserService;
@Controller
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/add-user")
@ResponseBody
public String insertSelective(User record) {
userService.insertSelective(record);
return "OK";
}
@RequestMapping("/get-by-id")
@ResponseBody
public User selectByPrimaryKey(Long id) {
return userService.selectByPrimaryKey(id);
}
@RequestMapping("/update-by-id")
@ResponseBody
public String update(User record) {
userService.updateByPrimaryKeySelective(record);
return "ok";
}
@RequestMapping("/delete-by-id")
@ResponseBody
public Integer delete(Long id) {
return userService.deleteByPrimaryKey(id);
}
}
测试:
首先我们访问如下链接:
http://127.0.0.1:8080/[email protected]
http://127.0.0.1:8080/[email protected]
加入2条数据
然后我们访问如下链接:
http://127.0.0.1:8080/get-by-id?id=1
发现有打印日志,如下:
2018-01-23 14:52:41.658 INFO 60766 --- [nio-8881-exec-6] c.example.demo.service.UserServiceImpl : 查询数据库
返回值如下:
{
id: 1,
nickName: "harry",
emailNew: "[email protected]",
registerTime: 1484488802000
}
此时我们再次访问,发现没有再次打印日志,而是直接从缓存中返回的.
访问如下链接就行修改:
http://127.0.0.1:8080/update-by-id?id=1&nickName=22
将昵称改为22.
此时我们继续访问 http://127.0.0.1:8080/get-by-id?id=1 ,发现没有打印日志,且返回的结果中昵称也改为了22,如下:
{
id: 1,
nickName: "22",
emailNew: "[email protected]",
registerTime: 1484488802000
}
ConcurrentMapCache的自动装配声明在SimpleCacheConfiguration中.
SimpleCacheConfiguration 声明了如下注解:
@Configuration
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
@Conditional(CacheCondition.class) –> 通过CacheCondition来进行判断,代码如下:
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 1. 获得被注解的类名
String sourceClass = "";
if (metadata instanceof ClassMetadata) {
sourceClass = ((ClassMetadata) metadata).getClassName();
}
ConditionMessage.Builder message = ConditionMessage.forCondition("Cache",
sourceClass);
// 2. 实例化RelaxedPropertyResolver,读取spring.cache.开头的属性
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(
context.getEnvironment(), "spring.cache.");
// 3. 如果没有配置spring.cache.type,则返回匹配--> 进行自动选择
if (!resolver.containsProperty("type")) {
return ConditionOutcome.match(message.because("automatic cache type"));
}
// 4. 根据被注解的类名获得对应的CacheType
CacheType cacheType = CacheConfigurations
.getType(((AnnotationMetadata) metadata).getClassName());
// 5. 将spring.cache.type 配置的值 中的-变为_,然后变成大写,如果和CacheType的name相等的化,则返回匹配,否则,返回不匹配
String value = resolver.getProperty("type").replace('-', '_').toUpperCase();
if (value.equals(cacheType.name())) {
return ConditionOutcome.match(message.because(value + " cache type"));
}
return ConditionOutcome.noMatch(message.because(value + " cache type"));
}
bean方法,代码如下:
@Bean
public ConcurrentMapCacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return this.customizerInvoker.customize(cacheManager);
}
该方法的处理逻辑如下: