您的位置:首页 > 数据库 > Redis

Spring boot redis的使用

2017-08-22 14:31 337 查看
    源码地址https://github.com/yejingtao/forblog.git中mybank-jpa-redis项目。

    上篇博文讲解了spring boot web+data的用法,本篇在前面基础上扩展下缓存和事务的功能。
内存的使用能提高io读写速度,但是单机的JVM存在内存较小、跨结点无法共享的缺陷,所以需要掌握一种缓存技术来解决单机缺陷,我们这里选择的是Redis。
    本地开发和测试推荐docker容器,docker刚上手比较难,概念上的东西不好理解,但是真正使用起来方便的飞起,因为我们不用再重复制造轮子,可以直接搬来用。
    本篇重点介绍Redis在Spring
data中的用法和事务如何控制,后续会专门做redis和docker的专题,欢迎持续关注。

    POM依赖添加对redis的使用:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

    配置文件增加redis地址的配置:

server:
port: 8081
spring:
datasource:
url: jdbc:mysql://192.168.226.130:3306/schema1
username: root
password: admin123
driver-class-name: com.mysql.jdbc.Driver
redis:
database: 0
port: 6379
host: 192.168.226.130
pool:
max-idle: 8
min-idle: 0
max-active: 8
max-wait: -1
jpa:
database: MYSQL
show-sql: true
application:
name: mybank


    主程序Application添加注解@EnableCaching,标识开启缓存。

        Entity层实现Serializable,要将复杂对象序列化方式缓存到redis中。
    
        Service层增加缓存的注解。
@Cacheable(查询缓存,每次查询以一个标识作为key,如果再次执行本查询方法根据key判断是直接返回缓存结果还是去数据库获取)
@CacheEvict(清理缓存,一般用在delete方法中)
@CachePut(重新设置缓存,一般用户update或save方法中)
如果一个方法需要对多组缓存做处理需要使用@Caching注解,入参是数组类型的@CacheEvict或者@CachePut注解。
@Override
@Cacheable(value="userInfo", key="#name")
public List<User> findUserByName(String name) {
return userRepository.findByName(name);
}

@Override
@Cacheable(value="operationInfo", key="#userId")
public List<Operation> findOperationByUserId(int userId) {
return operationRepository.findByUserId(userId);
}

@Override
@Caching(evict = { @CacheEvict(value="userInfo", key="#user.name"),
@CacheEvict(value="operationInfo", key="#user.id")})
@Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED)
//@Transactional
public User createOrUpdate(User user,String description) {
user = userRepository.save(user);
Operation operation = new Operation(user.getId(),description);
operationRepository.save(operation);
return user;
}
   
findUserByName方法根据入参name为key保存到缓存中,redis缓存的name是userInfo
        findOperationByUserId方法根据入参userId为key保存到缓存中,redis缓存中的name是operationInfo
        createOrUpdate方法会新增或者修改user和operation记录,如果不处理缓存那么之后的find方法得到的数据与数据库不同步了,所以这里我们清理掉
    userInfo和operationInfo缓存,让他们下次执行重新去数据库加载。
    以上是redis作为二级缓存的用法,用来减少数据的压力提高响应速率,redis还有个用法是作为架构的系统缓存,例如加载初始化属性、静态资源、结点间共享数据等,MainController中有此种用法。
/**
* 创建用户
* @param user
* @return
*/
@RequestMapping(value="/saveUser",method=RequestMethod.POST)
public String create(ModelMap modelMap,User user) {
String description = user.getId()>0?("update money into "+user.getCurrent()):"register a new user";
User thisUser = userService.createOrUpdate(user,description);
modelMap.addAttribute("thisUser", thisUser);
modelMap.addAttribute("thisOperations", userService.findOperationByUserId(thisUser.getId()));
//这里是系统缓存的用法
HashOperations<String, String, String> hashOp = redisTemplate.opsForHash();
hashOp.put(REDIS_KEY, thisUser.getName(), (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(new Date()));//user latest modify time
return "myuser";
}

@RequestMapping(value="/find",method=RequestMethod.GET)
@ResponseBody
public String findUserInfo(@NotNull String name) {
StringBuffer returnSb = new StringBuffer();
List<User> userList = userService.findUserByName(name);
returnSb.append("User: " + userList.get(0).toString() + "\\r\\n");
//这里是系统缓存的用法
HashOperations<String, String, String> hashOp = redisTemplate.opsForHash();
String userLatestModifyTime = hashOp.get(REDIS_KEY, userList.get(0).getName());//user latest modify time
returnSb.append("User latest modify time: " + userLatestModifyTime + "\\r\\n");
returnSb.append("Operation: ");
List<Operation> operationList = userService.findOperationByUserId(userList.get(0).getId());
if(operationList!=null && operationList.size()>0) {
operationList.forEach(o->returnSb.append(o));
}

return returnSb.toString();

}

我们想维护用户最后一次更改操作的时间点,可以通过redisClient+log中打印的sql来判断缓存是否生效。



注意:做系统缓存时我层尝试将date作为最后个参数传入HashOperations会报错java.lang.ClassCastException:
java.util.Date cannot be cast tojava.lang.String,这里只能传入字符串。
解决了缓存问题我们最后来看一下Spring data事务是如何解决的。
最简洁的办法是在需要事务控制的方法上添加一个无任何参数的@Transactional就搞得定,本质上等价于@Transactional(isolation
= Isolation.DEFAULT,propagation =Propagation.REQUIRED)
事务有2个参数隔离级别和传播行为。
隔离级别Isolation:事物之间的隔离程度,脏读、重复读、幻读。
看下源码就能了解有哪些级别都是什么意义      
public enum Isolation {

DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),

READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),

READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),

REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),

SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);

private final int value;

Isolation(int value) { this.value = value; }

public int value() { return this.value; }

}

传播行为Propagation:上午上下文直接对事物的传递是如何处理的。
依旧需要看源码才能更好理解
public enum Propagation {

REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),

SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),

MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),

REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),

NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),

NEVER(TransactionDefinition.PROPAGATION_NEVER),

NESTED(TransactionDefinition.PROPAGATION_NESTED);

private final int value;

Propagation(int value) { this.value = value; }

public int value() { return this.value; }

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息