利用 redis 中字符串类型完成 项目中手机验证码存储的实现
验证码一般都具有时效性,我们在redis中可以设置一个key的超时时间,
当用户在超时时间之内响应时,会与redis中的数据进行对比,验证验证码的正确性
当用户在超时时间之外响应,数据在redis中已经被删除,无法进行验证
利用 redis 中字符串类型完成 具有时效性业务功能
比如说在平常买票或者网上购物的时候, 12306 淘宝
订单还有15分钟失效,我们可以将订单信息存入redis,
当数据失效时我们可以返回订单失效。
利用 redis 做分布式集群系统中 Session 共享
利用 redis 中 zset数据类型 元素 分数 完成排行榜之类的功能
ZSet是可排序的set,可以利用ZSet做排行榜排名之类的功能,显示排行前20的信息等等
利用 redis 实现分布式缓存
可以把数据库中的数据放入缓存中,日后在查询的时候不需要走数据库了,而是直接走缓存,
缓存中没有的从数据库中查,查完放入缓存
利用 redis 存储认证之后的 token 信息 微信小程序 微信公众号 ——> 令牌(token)redis 超时
利用 redis 解决分布式集群系统中分布式锁的问题
在一个java虚拟机中,解决锁的方法有很多,比如: synchronized,但是我们要注意synchronized
只能解决一台机器上的线程共享问题,现在我们有多台机器,多台虚拟机,synchronized并不能保证
这多台机器之间的线程安全问题。因为 redis 是单进程单线程的,我们可以使用 redis 解决多台
机器之间的线程安全问题。
# 什么是缓存
定义: 就是计算机内存中的一段数据
# 内存中的数据特点
1. 读写快 2. 断电立即消失
# 缓存解决了什么问题
1. 提高网站吞吐量,提高网站运行效率(从缓存中取数据就相当于从内存中取数据,读写更快)
2. 核心解决问题: 缓存的存在减轻了数据库的访问压力(如果数据在缓存中有,直接在缓存中取就可以了)
# 误区
既然缓存可以提高提高效率,那项目中所有数据都加入缓存岂不是更好?
并不是
注意:使用缓存时一定是数据库中数据极少发生修改,更多用于查询这种情况。
当修改比较多时,我们每次不仅要在数据库中查询新的数据,还要对缓存中
的数据做替换,这种情况下使用缓存会极大地降低系统的效率。
# 本地缓存和分布式缓存的区别
本地缓存: 存在于应用服务器内存中的数据称之为本地缓存 (local cache)
分布式缓存: 存储在当前应用服务器内存之外的数据称之为分布式缓存 (distribute cache)
# 什么是集群,什么是分布式
集群: 将同一种服务器的多个节点放在一起共同对系统提供服务的过程称之为集群
分布式: 由多个不同的服务集群共同对系统提供服务,这个系统称之为分布式系统(distribute system)
分布式是在集群基础之上构建的
# 利用mybatis自身本地缓存结合redis实现分布式缓存
a. mybatis中应用级缓存(二级缓存) SqlSessionFactory 级别缓存 所有会话共享
b. 如何开启(二级缓存)
只需要在 mapper.xml 中加入下面的一条语句
在mapper.xml中加入上面那句话开启二级缓存使用的是本地缓存
c. 查看Cache标签缓存实现
结论: Mybatis底层默认使用的是 org.apache.ibatis.cache.impl.PerpetualCache 实现
d. 自定义RedisCache实现
1. 通过mybatis默认cache源码得知 可以使用自定义Cache类 实现 Cache接口 并对里面的方法做实现
2. 使用RedisCache实现
e. 当表之间有关联关系时,我们可以让有关联关系查询之间的DAO的查询指向同一个缓存
mybatis中的二级缓存是本地缓存,二级缓存是SqlSessionFactory级别的,所有会话共享
如果想要使用mybatis的二级缓存,只需要在 mapper配置文件中加入 下面这条语句 即可
<cache/>
包结构
- 引入依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.3version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.38version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.19version>
dependency>
- 编写 application.properties 配置文件
application.properties 配置文件
# 修改SpringBoot应用的端口号
server.port=8989
# 配置与redis相关的数据
spring.redis.host=192.168.72.129
spring.redis.port=7000
spring.redis.database=0
# 数据源
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/2001?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root
# 指定mapper配置文件的位置以及起别名
mybatis.mapper-locations=classpath:com/baizhi/mapper/*.xml
mybatis.type-aliases-package=com.baizhi.entity
# 设置日志的级别
logging.level.com.baizhi.dao=debug
在入口类上加上 @MapperScan(“com.baizhi.dao”) 注解扫描dao接口所在的包
// 入口类
@SpringBootApplication
@MapperScan("com.baizhi.dao") // 在入口类上加上这个注解让它扫描dao接口所在的包
public class RedisDay2Application {
public static void main(String[] args) {
SpringApplication.run(RedisDay2Application.class, args);
}
}
- 相关类
UserDAO
public interface UserDAO {
List<User> findAll();
}
User
// 下面这两个注解的作用是创建get、set方法,使用了下面的这两个注解就不需要写get、set方法了,会自动创建
@Data
@Accessors(chain = true)
public class User implements Serializable { // 将对象放入缓存是对象必须序列化
private String id;
private String name;
private Integer age;
private Date bir;
}
UserService
public interface UserService {
List<User> findAll();
}
UserServiceImpl
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDAO userDAO;
@Override
public List<User> findAll() {
return userDAO.findAll();
}
}
RedisDay2Application入口类
@SpringBootApplication
@MapperScan("com.baizhi.dao") // 扫描dao接口所在的包
public class RedisDay2Application {
public static void main(String[] args) {
SpringApplication.run(RedisDay2Application.class, args);
}
}
UserDAOMapper.xml配置文件
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.baizhi.dao.UserDAO">
<cache/>
<select id="findAll" resultType="com.baizhi.entity.User">
select id, name, age, bir from t_user
select>
mapper>
- 测试类
// 下面两个注解相当于启动SpringBoot应用
@SpringBootTest(classes = RedisDay2Application.class) // 让SpringBoot启动起来,并指向入口类
@RunWith(SpringRunner.class) // 让测试运行于Spring环境
public class TestUserService {
@Autowired
private UserService userService;
@Test
public void test(){
userService.findAll().forEach(user -> System.out.println("user = " + user));
System.out.println("============================");
userService.findAll().forEach(user -> System.out.println("user = " + user));
}
}
结果
我们可以看到在第二次查询的时候击中了缓存,并没有向数据库中查询数据,而是直接击中缓存,从缓存中拿取数据。
补充
mybatis中的二级缓存是本地缓存,使用了应用的内存,这是它的不足之处,且当应用重启之后,缓存中的数据会丢失,我们需要先从数据库中读取,之后才能使用缓存,我们想要让应用中的内存全部用于处理响应,这就需要用到下面的分布式缓存了。
我们在mapper配置文件中加入
<cache/>
就可以使用 mybatis 的二级缓存,实际上使用的是PerpetualCache,和下面这句话是一个意思
<cache type="org.apache.ibatis.cache.impl.PerpetualCache"/>
mybatis中的二级缓存默认使用的是PerpetualCache,底层实际上是java中的HashMap
我们怎么把mybatis中的二级缓存改为redis呢,在做自定义 Cathe 的时候自定义一个类像 PerpetualCache 内部一样
简单的实现并且实现 Cache 接口并且在mapper配置文件中指定使用这个我们自定义的RedisCache即可。不过在这
之前我们需要先想一想如何获取redisTemplate对象,在操作RedisCache的时候我们肯定需要用到RedisTemplate对
象,而RedisCache是由mybatis管理的,并不是由工厂管理的,那我们怎么拿到RedisCache对象呢,我们可以创建
一个工厂工具类,通过工具类传入指定对象的名字去获取我们想要的在工厂中的对象(比如: redisTemplate)
如果想要获取工厂并获取工厂中的对象可以建一个工具类
// 用来获取SpringBoot创建好的工厂
@Component
public class ApplicationContextUtils implements ApplicationContextAware {
// 保留下来工厂
private static ApplicationContext applicationContext;
// 将创建好的工厂以参数的形式传递给这个类
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
// 提供在工厂中获取对象的方法 RedisTemplate redisTemplate
public static Object getBeanName(String name){
return applicationContext.getBean(name);
}
}
接下来我们来实现自定义的RedisCache
因为在使用redis整合mybatis时,在使用hash类型时,value中的key太长影响效率,所以我们需要使用md5算法修改value中的key为一个32位的16进制的数
// 自定义redis缓存实现
public class RedisCache implements Cache {
// 当前放入缓存的mapper的namespace
// 大Key
private final String id;
// 必须存在一个类型为String的id的构造方法
public RedisCache(String id) {
//System.out.println("id ==============> " + id);
this.id = id;
}
// 返回cache唯一标识不能为空
@Override
public String getId() {
return id;
}
// 向缓存中放入数据
@Override
// 大Key value中的key
public void putObject(Object o, Object o1) {
//System.out.println("o = " + o.toString());
//System.out.println("o1 = " + o1);
// 使用redis 中 Hash类型作为缓存存储模型 Key hashkey value
// 大Key是id value中的key是o1 value中的值是o1
getRedisTemplate().opsForHash().put(id.toString(), getKeyToMD5(o.toString()), o1);
// 针对不同的业务模块设置不同的缓存时间
//if(id.equals("com.baizhi.dao.UserDAO")){
// getRedisTemplate().expire(id.toString(), 60, TimeUnit.MILLISECONDS);
//}
//if(id.equals("com.baizhi.dao.EmpDAO")){
getRedisTemplate().expire(id.toString(), 30, TimeUnit.MILLISECONDS);
//}
}
//从缓存中获取数据
@Override
// 大Key
public Object getObject(Object o) {
//System.out.println("o = " + o.toString());
return getRedisTemplate().opsForHash().get(id.toString(), getKeyToMD5(o.toString()));
}
// 注意: 这个方法为mybatis的保留方法 默认没有实现 后续版本可能会实现
@Override
// 大Key
public Object removeObject(Object o) {
//System.out.println("根据指定key删除缓存");
return null;
}
// 只要调用增、删、该,都会调用clear方法清空缓存
@Override
public void clear() {
// 清空namespace
getRedisTemplate().delete(id.toString()); // 清空缓存
}
// 用来计算缓存数量
@Override
public int getSize() {
// 获取Hash中key value数量
return getRedisTemplate().opsForHash().size(id.toString()).intValue();
}
// 封装redisTemplate
public RedisTemplate getRedisTemplate(){
// 根据key从redis的hash类型中获取数据
// 通过application工具类获取RedisTemplate对象
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBeanName("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
// 封装一个对value中的key进行md5操作的方法
private String getKeyToMD5(String key){
return DigestUtils.md5DigestAsHex(key.getBytes());
}
}
在实现自定义RedisCache时我们必须满足以下几点要求:
如果我们想要使用自定义的RedisCache的话,我们还需要在mapper配置文件中指定使用这个RedisCache
<cache type="com.baizhi.redis.RedisCache"/>
还有几点需要注意:
存入redis中的数据是Hash类型,大Key是mapper配置文件中namespace中的名字,value中的key是大Key和查询语句等组合成的字符串,不过我们使用了md5技术,所以value中的key是一个32位的16进制的字符串
在进行增、删、改操作的时候会清空缓存
关于md5的小知识:
用两个文件,一个是 aa.txt 一个是 bb.txt 怎么判断两个文件的内容是一致的呢,可以使用MD5算法,不同的内容通过MD5算法得到的结果一定不同。
另外:
如果我们使用的不同DAO的操作之间的表没有关联关系时,我们在使用缓存时使它们各自是用自己的缓存,比如现在有两个mapper配置文件,一个是User、一个是Emp,当这两个DAO对应的表没有关系时,mapper配置文件中缓存这样写:
User的Mapper
<cache type="com.baizhi.redis.RedisCache"/>
Emp的Mapper
<cache type="com.baizhi.redis.RedisCache"/>
当表之间有关联关系时:
User的Mapper
<cache type="com.baizhi.redis.RedisCache"/>
Emp的Mapper
<cache-ref namespace="com.baizhi.dao.UserDAO"/>
当然也可以两个都使用Emp为缓存的key,只要两个区域共享即可
接下来我们写一个小案例来看一下Redis缓存在项目中的应用:
包结构:
- 引入依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.3version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.38version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.19version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<scope>testscope>
dependency>
- application.properties 配置文件
# 修改SpringBoot应用的端口号
server.port=8989
# 配置与redis相关的数据
spring.redis.host=192.168.72.129
spring.redis.port=7000
spring.redis.database=0
# 数据源
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/2001?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root
# 指定mapper配置文件的位置以及起别名
mybatis.mapper-locations=classpath:com/baizhi/mapper/*.xml
mybatis.type-aliases-package=com.baizhi.entity
# 设置日志的级别
logging.level.com.baizhi.dao=debug
- 相关类
DAO:
public interface EmpDAO {
List<Emp> findAll();
}
public interface UserDAO {
List<User> findAll();
User findById(String id);
void delete(String id);
void save(User user);
void update(User user);
}
资源类(放入缓存中的对象必须实现序列化):
@Data
@Accessors(chain = true)
public class Emp implements Serializable {
private String id;
private String name;
}
// 下面这两个注解的作用是创建get、set方法,使用了下面的这两个注解就不需要写get、set方法了,会自动创建
@Data
@Accessors(chain = true)
public class User implements Serializable { // 在使用RedisTemplate操作redis的时候对象必须序列化
private String id;
private String name;
private Integer age;
private Date bir;
}
service:
public interface EmpService {
List<Emp> findAll();
}
@Service
@Transactional
public class EmpServiceImpl implements EmpService{
@Autowired
private EmpDAO empDAO;
@Override
public List<Emp> findAll() {
return empDAO.findAll();
}
}
public interface UserService {
List<User> findAll();
User findById(String id);
void delete(String id);
void save(User user);
void update(User user);
}
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDAO userDAO;
@Override
public List<User> findAll() {
return userDAO.findAll();
}
@Override
public User findById(String id) {
return userDAO.findById(id);
}
@Override
public void delete(String id) {
userDAO.delete(id);
}
@Override
public void save(User user) {
user.setId(UUID.randomUUID().toString());
userDAO.save(user);
}
@Override
public void update(User user) {
userDAO.update(user);
}
}
入口类:
@SpringBootApplication
@MapperScan("com.baizhi.dao") // 扫描dao接口所在的包
public class RedisDay2Application {
public static void main(String[] args) {
SpringApplication.run(RedisDay2Application.class, args);
}
}
ApplicationContextUtils工具类(获取工厂中的对象):
// 用来获取SpringBoot创建好的工厂
@Component
public class ApplicationContextUtils implements ApplicationContextAware {
// 保留下来工厂
private static ApplicationContext applicationContext;
// 将创建好的工厂以参数的形式传递给这个类
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
// 提供在工厂中获取对象的方法 RedisTemplate redisTemplate
public static Object getBeanName(String name){
return applicationContext.getBean(name);
}
}
自定义的RedisCache类:
// 自定义redis缓存实现
public class RedisCache implements Cache {
// 当前放入缓存的mapper的namespace
// 大Key
private final String id;
// 必须存在一个类型为String的id的构造方法
public RedisCache(String id) {
System.out.println("id ==============> " + id);
this.id = id;
}
// 返回cache唯一标识不能为空
@Override
public String getId() {
return id;
}
// 向缓存中放入数据
@Override
// value中的Key value中的值
public void putObject(Object o, Object o1) {
//System.out.println("o = " + o.toString());
//System.out.println("o1 = " + o1);
// 使用redis 中 Hash类型作为缓存存储模型 Key hashkey value
// 大Key是id value中的key是o1 value中的值是o1
getRedisTemplate().opsForHash().put(id.toString(), getKeyToMD5(o.toString()), o1);
// 针对不同的业务模块设置不同的缓存时间
//if(id.equals("com.baizhi.dao.UserDAO")){
// getRedisTemplate().expire(id.toString(), 60, TimeUnit.MILLISECONDS);
//}
//if(id.equals("com.baizhi.dao.EmpDAO")){
getRedisTemplate().expire(id.toString(), 30, TimeUnit.MILLISECONDS);
//}
}
//从缓存中获取数据
@Override
// 大Key
public Object getObject(Object o) {
//System.out.println("o = " + o.toString());
return getRedisTemplate().opsForHash().get(id.toString(), getKeyToMD5(o.toString()));
}
// 注意: 这个方法为mybatis的保留方法 默认没有实现 后续版本可能会实现
@Override
// 大Key
public Object removeObject(Object o) {
//System.out.println("根据指定key删除缓存");
return null;
}
// 只要调用增、删、该,都会调用clear方法清空缓存
@Override
public void clear() {
// 清空namespace
getRedisTemplate().delete(id.toString()); // 清空缓存
}
// 用来计算缓存数量
@Override
public int getSize() {
// 获取Hash中key value数量
return getRedisTemplate().opsForHash().size(id.toString()).intValue();
}
// 封装redisTemplate
public RedisTemplate getRedisTemplate(){
// 根据key从redis的hash类型中获取数据
// 通过application工具类获取RedisTemplate对象
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBeanName("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
// 封装一个对value中的key进行md5操作的方法
private String getKeyToMD5(String key){
return DigestUtils.md5DigestAsHex(key.getBytes());
}
}
Mapper配置文件:
EmpDAOMapper.xml
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.baizhi.dao.EmpDAO">
<cache-ref namespace="com.baizhi.dao.UserDAO"/>
<select id="findAll" resultType="Emp">
select id, name from t_emp
select>
mapper>
UserDAOMapper.xml
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.baizhi.dao.UserDAO">
<cache type="com.baizhi.redis.RedisCache"/>
<select id="findAll" resultType="com.baizhi.entity.User">
select id, name, age, bir from t_user
select>
<select id="findById" parameterType="String" resultType="User">
select id, name, age, bir from t_user where id = #{id}
select>
<delete id="delete" parameterType="String">
delete from t_user where id = #{id}
delete>
<insert id="save" parameterType="User">
insert into t_user values(#{id}, #{name}, #{age}, #{bir})
insert>
<update id="update" parameterType="User">
update t_user set name = #{name}, age = #{age}, bir = #{bir} where id = #{id}
update>
mapper>
- 测试类
TestEmpService
// 下面两个注解相当于启动SpringBoot应用
@SpringBootTest(classes = RedisDay2Application.class) // 让SpringBoot启动起来,并指向入口类
@RunWith(SpringRunner.class) // 让测试运行于Spring环境
public class TestEmpService {
@Autowired
private EmpService empService;
@Test
public void testFindAll(){
empService.findAll().forEach(emp -> System.out.println(emp));
System.out.println("=============================");
empService.findAll().forEach(emp -> System.out.println(emp));
}
@Test
public void testmd5(){
// org.springframework.util.DigestUtils 包下的 DigestUtils
String key = "-1628801192:1544301354:com.baizhi.dao.EmpDAO.findAll:0:2147483647:select id, name from t_emp:SqlSessionFactoryBean";
// 将key转化为32位的16进制的字符串
String s = DigestUtils.md5DigestAsHex(key.getBytes());
System.out.println("s = " + s);
}
}
TestUserService:
// 下面两个注解相当于启动SpringBoot应用
@SpringBootTest(classes = RedisDay2Application.class) // 让SpringBoot启动起来,并指向入口类
@RunWith(SpringRunner.class) // 让测试运行于Spring环境
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 testFindId(){
System.out.println(userService.findById("1"));
System.out.println("============================");
System.out.println(userService.findById("1"));
}
@Test
public void testDelete(){
userService.delete("1");
System.out.println("============================");
}
@Test
public void testSave(){
User user = new User();
user.setName("王五");
user.setAge(20);
user.setBir(new Date());
userService.save(user);
System.out.println("============================");
}
@Test
public void testUpdate(){
User user = new User();
user.setId("2");
user.setName("赵六");
user.setAge(20);
user.setBir(new Date());
userService.update(user);
System.out.println("============================");
}
}