1.2 什么样的数据适合放入缓存查询频率比较高,修改频率比较低。
安全系数低的数据
1.3 使用redis作为缓存1.3.1 未使用配置类注意要将实体类实现序列化:
@data@allargsconstructor@noargsconstructor@tablename(value = "tb_dept")public class dept implements serializable { @tableid(value = "id",type = idtype.auto) private integer id; private string name; private string realname;}
对应依赖:
<dependencies> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-data-redis</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> </dependency> <!--连接数据源--> <dependency> <groupid>mysql</groupid> <artifactid>mysql-connector-java</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-jdbc</artifactid> </dependency> <!--mp的依赖--> <dependency> <groupid>com.baomidou</groupid> <artifactid>mybatis-plus-boot-starter</artifactid> <version>3.4.2</version> </dependency> <dependency> <groupid>org.projectlombok</groupid> <artifactid>lombok</artifactid> <optional>true</optional> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-test</artifactid> <scope>test</scope> </dependency> </dependencies>
controller层对应代码:
@restcontroller@requestmapping("order")public class deptcontroller { @resource private deptservice deptservice; @getmapping("getbyid/{id}") //order/getbyid/1 //{}可以放多个,由下面的传参函数对应 //@pathvariable:获取请求映射中{}的值 public dept getbyid(@pathvariable integer id){ return deptservice.findbyid(id); } @getmapping("deletebyid/{id}") public string deletebyid(@pathvariable integer id){ int i = deptservice.deletebyid(id); return i>0?"删除成功":"删除失败"; } @getmapping("insert") public dept insert(dept dept){ dept insert = deptservice.insert(dept); return insert; } @getmapping("update") public dept update(dept dept){ dept update = deptservice.update(dept); return update; }}
service层对应代码:
@servicepublic class deptservice { @resource private deptmapper deptmapper; //当存储的value类型为对象类型使用redistemplate //存储的value类型为字符串。stringredistemplate @autowired private redistemplate redistemplate; //业务代码 public dept findbyid(integer id){ valueoperations forvalue = redistemplate.opsforvalue(); //查询缓存 object o = forvalue.get("dept::" + id); //缓存命中 if(o!=null){ return (dept) o; } dept dept = deptmapper.selectbyid(id); if(dept!=null){ //存入缓存中 forvalue.set("dept::"+id,dept,24, timeunit.hours); } return dept; } public int deletebyid(integer id){ redistemplate.delete("dept::"+id); int i = deptmapper.deletebyid(id); return i; } public dept insert(dept dept){ int insert = deptmapper.insert(dept); return dept; } public dept update(dept dept){ redistemplate.delete("dept::"+dept.getid()); int i = deptmapper.updatebyid(dept); return dept; }}
配置源:
# 配置数据源
spring.datasource.driver-class-name=com.mysql.cj.jdbc.driver
spring.datasource.url=jdbc:mysql://localhost:3306/mydb?servertimezone=asia/shanghai
spring.datasource.username=root
spring.datasource.password=root
#sql日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.stdoutimpl
#连接redis
spring.redis.host=192.168.22*.1**
spring.redis.port=6379
查看的缓存: 前部分代码相同@before通知,后部分代码也相同后置通知。 我们可以aop完成缓存代码和业务代码分离。
spring框架它应该也能想到。--使用注解即可完成。解析该注解。
1.3.2 使用配置类(1)把缓存的配置类加入
@bean public cachemanager cachemanager(redisconnectionfactory factory) { redisserializer<string> redisserializer = new stringredisserializer(); jackson2jsonredisserializer jackson2jsonredisserializer = new jackson2jsonredisserializer(object.class); //解决查询缓存转换异常的问题 objectmapper om = new objectmapper(); om.setvisibility(propertyaccessor.all, jsonautodetect.visibility.any); om.enabledefaulttyping(objectmapper.defaulttyping.non_final); jackson2jsonredisserializer.setobjectmapper(om); // 配置序列化(解决乱码的问题),过期时间600秒 rediscacheconfiguration config = rediscacheconfiguration.defaultcacheconfig() .entryttl(duration.ofseconds(600)) //缓存过期10分钟 ---- 业务需求。 .serializekeyswith(redisserializationcontext.serializationpair.fromserializer(redisserializer))//设置key的序列化方式 .serializevalueswith(redisserializationcontext.serializationpair.fromserializer(jackson2jsonredisserializer)) //设置value的序列化 .disablecachingnullvalues(); rediscachemanager cachemanager = rediscachemanager.builder(factory) .cachedefaults(config) .build(); return cachemanager;
(2) 使用开启缓存注解
(3)使用注解
//业务代码 //使用查询注解:cachenames表示缓存的名称 key:唯一标志---dept::key //先从缓存中查看key为(cachenames::key)是否存在,如果存在则不会执行方法体,如果不存在则执行方法体并把方法的返回值存入缓存中 @cacheable(cachenames = {"dept"},key="#id") public dept findbyid(integer id){ dept dept = deptmapper.selectbyid(id); return dept; }//先删除缓存在执行方法体。 @cacheevict(cachenames = {"dept"},key = "#id") public int deletebyid(integer id){ int row = deptmapper.deletebyid(id); return row; } //这个注释可以确保方法被执行,同时方法的返回值也被记录到缓存中,实现缓存与数据库的同步更新。 @cacheput(cachenames = "dept",key="#dept.id") public dept update(dept dept){ int insert = deptmapper.updatebyid(dept); return dept; }
2.分布式锁使用压测工具测试高并发下带来线程安全问题
2.1 压测工具的使用
内部配置:
2.2 库存项目2.2.1 controller层@restcontroller@requestmapping("bucket")public class bucketcontroller { @autowired private bucketservice bucketservice; @getmapping("update/{productid}") public string testupdate(@pathvariable integer productid){ string s = bucketservice.updatebyid(productid); return s; }}
2.2.2 dao层//此处写就不需要在启动类使用注解@mapperpublic interface bucketmapper extends basemapper<bucket> { public integer updatebucketbyid(integer productid);}
2.2.3 entity层@data@allargsconstructor@noargsconstructorpublic class bucket { @tableid(value = "productid",type = idtype.auto) private integer productid; private integer num;}
2.2.4 service层@servicepublic class bucketservice { @resource private bucketmapper bucketmapper; public string updatebyid(integer productid){ //查看该商品的库存数量 bucket bucket = bucketmapper.selectbyid(productid); if(bucket.getnum()>0){ //修改库存每次减1 integer integer = bucketmapper.updatebucketbyid(productid); system.out.println("扣减成功!剩余库存数:"+(bucket.getnum()-1)); return "success"; }else { system.out.println("扣减失败!库存数不足"); return "fail"; } }}
2.2.5 mapper<?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="com.qy151wd.dao.bucketmapper"> <update id="updatebucketbyid" parametertype="int"> update bucket set num=num-1 where productid=#{productid} </update></mapper>
2.2.6 依赖 <dependencies> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-data-redis</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> </dependency> <!--连接数据源--> <dependency> <groupid>mysql</groupid> <artifactid>mysql-connector-java</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-jdbc</artifactid> </dependency> <!--mp的依赖--> <dependency> <groupid>com.baomidou</groupid> <artifactid>mybatis-plus-boot-starter</artifactid> <version>3.4.2</version> </dependency> <dependency> <groupid>org.projectlombok</groupid> <artifactid>lombok</artifactid> <optional>true</optional> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-test</artifactid> <scope>test</scope> </dependency> </dependencies>
2.2.7 测试结果
我们看到同一个库存被使用了n次。以及数据库中库存为负数。 线程安全问题导致。
2.3 解决方案2.3.1 使用 synchronized 或者lock锁对应的service层修改为
@servicepublic class bucketservice { @resource private bucketmapper bucketmapper; public string updatebyid(integer productid){ //加自动锁 synchronized (this){ //查看该商品的库存数量 bucket bucket = bucketmapper.selectbyid(productid); if(bucket.getnum()>0){ //修改库存每次减1 integer integer = bucketmapper.updatebucketbyid(productid); system.out.println("扣减成功!剩余库存数:"+(bucket.getnum()-1)); return "success"; }else { system.out.println("扣减失败!库存数不足"); return "fail"; } } }}
如果搭建了项目集群,那么该锁无效 。
2.3.2 使用redistemplate(1)使用idea开集群项目
(2)使用nginx
(3)测试结果
发现又出现: 重复数字以及库存为负数。
(4)解决方法
service对应代码修改
@servicepublic class bucketservice { @resource private bucketmapper bucketmapper; @autowired private redistemplate redistemplate; public string updatebyid(integer productid){ valueoperations<string,string> forvalue = redistemplate.opsforvalue(); boolean flag = forvalue.setifabsent("aaa::" + productid, "-----------------"); if(flag){ try{ //查看该商品的库存数量 bucket bucket = bucketmapper.selectbyid(productid); if(bucket.getnum()>0){ //修改库存每次减1 integer integer = bucketmapper.updatebucketbyid(productid); system.out.println("扣减成功!剩余库存数:"+(bucket.getnum()-1)); return "success"; }else { system.out.println("扣减失败!库存数不足"); return "fail"; } }finally { redistemplate.delete("aaa::"+productid); } } return "服务器正忙,请稍后再试......."; }}
注意此处的测压速度不易太快(推荐使用5秒100个线程)
经过测压测试后,结果为:
以上就是java redis使用场景实例分析的详细内容。
