Redis在Springboot项目实现简单的初步缓存 新建了一个demo项目,来实现一下缓存,了解了一下mybatis自带的二级缓存和使用redis作为缓存的差异,感觉很不完善,但是我也是新手入门,慢慢学习成长中……
1.application.yml中配置mysql和mybatis
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 spring: #Mysql配置 datasource: username: root password: root url: jdbc:mysql://localhost:3306/demo?serverTimezone=UTC&useUnicode=true driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource #Redis配置 redis: host: 192.168.139.188 #ip port: 6379 #端口号 database: 0 #具体哪一个数据库,0-15 #Mybatis配置 mybatis: mapper-locations: classpath:cn/zl/springbootredis/mapper/*.xml type-aliases-package: cn.zl.springbootredis.dao #开启dao层的日志级别,便于查看sql语句 logging: level: cn: zl: springbootredis: dao: debug
2.User实体类以及数据库表的设计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package cn.zl.springbootredis.entity;import lombok.Data;import lombok.experimental.Accessors;import java.io.Serializable;@Data @Accessors(chain = true) public class User implements Serializable { private Long id; private String name; private String password; private Integer age; private String email; }
3.编写sql语句
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 30 31 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="cn.zl.springbootredis.dao.UserDao"> <!--开启mybatis的二级缓存,也是本地缓存,存在问题就是无法分布式实现缓存,并且每次结束Application都会清空缓存--> <cache/> <!--findAll--> <select id="findAll" resultType="cn.zl.springbootredis.entity.User"> select * from user </select> <!--findById--> <select id="findById" resultType="cn.zl.springbootredis.entity.User"> select * from user where id = #{id} </select> <!--delete--> <delete id="delete" parameterType="Long"> delete from user where id = #{id} </delete> <!--save--> <insert id="save" parameterType="cn.zl.springbootredis.entity.User"> insert into user values(#{id},#{name},#{password},#{age},#{email}) </insert> <!--update--> <update id="update" parameterType="cn.zl.springbootredis.entity.User"> update user set name = #{name},password = #{password},age = #{age},email = #{email} where id = #{id} </update> </mapper>
4.接口实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package cn.zl.springbootredis.dao;import cn.zl.springbootredis.entity.User;import java.util.List;public interface UserDao { List<User> findAll () ; User findById (Long id) ; void delete (Long id) ; void save (User user) ; void update (User user) ; }
5.业务层
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 package cn.zl.springbootredis.service.UserService;import cn.zl.springbootredis.entity.User;import java.util.List;public interface UserService { List<User> findAll () ; User findById (Long id) ; void delete (Long id) ; void save (User user) ; void update (User user) ; } package cn.zl.springbootredis.service.UserServiceImpl;import cn.zl.springbootredis.entity.User;import cn.zl.springbootredis.dao.UserDao;import cn.zl.springbootredis.service.UserService.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import java.util.List;@Service @Transactional public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override public List<User> findAll () { return userDao.findAll(); } @Override public User findById (Long id) { return userDao.findById(id); } @Override public void delete (Long id) { userDao.delete(id); } @Override public void save (User user) { userDao.save(user); } @Override public void update (User user) { userDao.update(user); } }
6.测试
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 package cn.zl.springbootredis.redis;import cn.zl.springbootredis.SpringbootRedisApplication;import cn.zl.springbootredis.entity.User;import cn.zl.springbootredis.service.UserService.UserService;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;@SpringBootTest(classes = SpringbootRedisApplication.class) @RunWith(SpringRunner.class) public class TestUserService { @Autowired private UserService userService; @Test public void testFindAll () { userService.findAll().forEach(user -> System.out.println("user =" + user)); System.out.println("====================================================" ); userService.findAll().forEach(user -> System.out.println("user =" + user)); } @Test public void testFindById () { User byId = userService.findById(55L ); System.out.println("====================================================" ); User byId1 = userService.findById(55L ); } @Test public void testDelete () { userService.delete(2L ); System.out.println("====================================================" ); } @Test public void testSave () { User user = new User(); user.setId(2L ).setName("Hali" ).setAge(21 ).setPassword("333" ).setEmail("wwww@163.com" ); userService.save(user); System.out.println("====================================================" ); } @Test public void testUpdate () { User user = new User(); user.setId(1L ).setName("Honey" ).setAge(22 ).setPassword("334" ).setEmail("nnn@163.com" ); userService.update(user); System.out.println("====================================================" ); } }
6.在没有使用redis作为缓存类型之前,一般是使用mybatis自带的二级缓存机制
1 2 3 在mapper.xml中开启二级缓存 <!--开启mybatis的二级缓存,也是本地缓存,存在问题就是无法分布式实现缓存,并且每次结束Application都会清空缓存--> <cache/>
.原理:首次查询会从数据库查询,然后将数据存储在本地缓存中,其实也就是调用了Cache,再次查询的时候就实现缓存击中,但是每次重启项目或者说重启进程,都将会清空本地的缓存,又会重新从数据库查询,再加入本地缓存,这样效率很低
7.引入redis作为缓存类型,添加type属性,指向自己配置的redis缓存类
1 2 <!--开启mybatis的二级缓存,也是本地缓存,存在问题就是无法分布式实现缓存,并且每次结束Application都会清空缓存,指定type--> <cache type="cn.zl.springbootredis.cache.RedisCache"/>
8.通过自定义RedisCache,实现Cache中的接口,来实现自定义的Redis缓存功能
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 package cn.zl.springbootredis.cache;import cn.zl.springbootredis.util.ApplicationContextUtils;import org.apache.ibatis.cache.Cache;import org.springframework.context.ApplicationContext;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.StringRedisSerializer;import sun.awt.SunHints;import java.util.concurrent.locks.ReadWriteLock;public class RedisCache implements Cache { private final String id; public RedisCache (String id) { this .id = id; } @Override public String getId () { return this .id; } @Override public void putObject (Object key, Object value) { System.out.println("key: " + key.toString()); System.out.println("value: " + value); getRedisTemplate().opsForHash().put(id.toString(),key.toString(),value); } @Override public Object getObject (Object key) { System.out.println("key: " + key.toString()); return getRedisTemplate().opsForHash().get(id.toString(), key.toString()); } @Override public Object removeObject (Object o) { System.out.println("根据指定key删除缓存" ); return null ; } @Override public void clear () { System.out.println("清空缓存" ); getRedisTemplate().delete(id.toString()); } @Override public int getSize () { return getRedisTemplate().opsForHash().size(id.toString()).intValue(); } private RedisTemplate getRedisTemplate () { RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate" ); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); return redisTemplate; } @Override public ReadWriteLock getReadWriteLock () { return null ; } }
总结:普通的二级缓存通过namespace来实现,具有很大的局限性,而通过redis来实现缓存,数据都存放在redis中,一次添加缓存能使用很久,但是这是单表情况,多表的时候会出问题,
比如一个Student表,一个Class表,通过一个classId关联,在Student的实体类中有一个Class class对象,当我对class表进行修改的时候,清空缓存只会清空class这个对应id的缓存,而student中因为缓存没有清空重新生成,所以class信息还是以前的class信息。
优化方法:使用cache-ref,将缓存放在一个id里面
1 2 <!--关联多张表的关系缓存处理,这样就把缓存放入到了同一个id里面,清空缓存的时候会全部清空--> <cache-ref namespace="cn.zl.springbootredis.entity.Student"/>
还有就是设计缓存的优化:
缓存优化策略——-对key的设计简洁化
算法:MD5
特点:a.一切文件字符串等经过md5处理之后,都会生成32位16进制字符串;
b.不同内容文件经过md5进行加密,加密结果一定不一致;
c.相同内容文件多次经过md5生成结果始终一致
直接使用Spring自带的Md5转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package cn.zl.springbootredis.util;import org.springframework.util.DigestUtils;public class Md5Utils { public static void main (String[] args) { } public static String getKeyMd5 (String key) { return DigestUtils.md5DigestAsHex(key.getBytes()); } } 然后在RedisCache中将key进行修整: Md5Utils.getKeyMd5(key.toString())
注意:如果使用了哨兵机制,在yml配置文件中,其它配置不变,但是redis的配置需要进行修整,不再是连接具体的redis主机,而是设置监听的配置
1 2 3 4 5 6 7 #Redis配置 redis: sentinel: # master是指的在sentinel.conf中书写的监听名称 master: mymaster # 连接的不再是一个具体的redis主机,写的是多个哨兵节点 nodes: 192.168.139.188:26379