文章目录
Redis入门:
默认有16个数据库,初始默认使用的是0数据库。采用单线程和 多路IO复用技术。
**Nosql(Not only sql)😗*非结构化的数据库:
- 键值对
- 列存储
- 文档型
- 图形化的存储
Redis通过键值对进行我们的存储,redis底层使用键值对的方式,Memecached和Redis都是内存行的存储方式,他的数据存储在内存中,定时会把数据存储到文件中
Redis使用场景:
- 在windows中限制为版本3.2的redis,需要ruby的支持
- Linux中可以使用其他的版本的的redis
Linux中去安装的redis:需要把你的redis的启动看成守护线程,把Redis的redis.conf,里面的deamonsize 修改成yes,当成线程启用。 - 点赞,排行榜,计数器,缓存(将经常不变的数据放在缓存中,提高访问的速度),对数据的读写要求比较高,对数据的一致性要求不太高的场景。
- 分布式锁
- 分布式会话:分布式系统中可以实现
session
(共享缓存)
Redis的优缺点
优点
- Redis是基于内存结构,读(11万次/秒)写(8万次/秒)效率高
- Redis是单线程操作但是多路复用实现了高性能。
- Redis所以操作都是原子性,可以Lua脚本多个操作合并为一个原子操作(Redis事务)
- Redis基于键值对存储,支持多种数据类型
缺点
- 用作缓存和其他数据库存在数据一致性问题
- 使用缓存存在,缓存击穿,缓存穿透,缓存雪崩的风险
- Redis可以作为数据库进行数据的存储,存在数据丢失的风险。
Redis的数据类型:
string
是redis的最基本的类型,你可以理解为和Memcached的一模一样类型,一个key,对应一个value.
String类型是二进制安全的。意思是redis的string类型可以包含任何数据,比如jpg图片或者序列化的对象(也就是student,user这些对象但是一定要实现Serializable),string类型是Redis最基本的数据类型,一个键最大存储512MB.
我们开始使用的时候,可以使用客户端进行基体的操作:
到你的redis目录下;redis-cli
127.0.0.1:6379> set a "b"
OK
127.0.0.1:6379> get a
"b"
127.0.0.1:6379>
Hash
Redis hash 是一个string类型的filed和value的映射表,hash特别适合用于存储对象。
1.hset key value(key value) :向Hash中存入值。
2.hget key value(key):取出Hash中key的值。
Hmset 设值
Hgetall取值
127.0.0.1:6379> hset people name houzhicong
(integer) 1
127.0.0.1:6379> hget people name
List
Redis列表是最简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部或者尾部。可以重复
Lpush 插入
Lrange 查看
127.0.0.1:6379> lpush list java
(integer) 6
127.0.0.1:6379> lpush list javaweb
(integer) 7
127.0.0.1:6379> lpush list smallhzc
(integer) 8
127.0.0.1:6379> lpush list java
(integer) 9
127.0.0.1:6379> lrange list 0 10
1) "java"
2) "smallhzc"
3) "javaweb"
4) "java"
5) "oracle"
6) "mysql"
7) "spring"
8) "jsp"
9) "java"
Set
Set是一个无序的集合(存储不可以进行重复,如果你是重复数据,他只会存储一份)
集合是通过哈希表实现的,所以添加,删除,查找的复杂度是O(1).
sadd命令
添加一个string元素,key对应的set集合中,成功返回1,否则为0
Sadd插入数据
smembers查询,可以看到不可以进行重复的添加
127.0.0.1:6379> sadd set redis
(integer) 1
127.0.0.1:6379> sadd set oracle
(integer) 1
127.0.0.1:6379> sadd set oracle
(integer) 0
127.0.0.1:6379> sadd set houzhicong
(integer) 1
127.0.0.1:6379> smembers set
1) "houzhicong"
2) "oracle"
3) "redis"
Zset(sorted set)是string类型的集合。
zset和set一样不可以有重复的成员。
不同的是每个元素会关联一个double类型的分数,redis正是通过分数来为集合中的成员进行从大到小的排序。
zset的成员是唯一的,但分数可以重复。
zadd命令 添加元素到集合
zrangebyscore查看
127.0.0.1:6379> zadd score 1 80
(integer) 1
127.0.0.1:6379> zadd score 2 77
(integer) 1
127.0.0.1:6379> zadd score 3 66
(integer) 1
127.0.0.1:6379> zrange score 0 10
1) "80"
2) "77"
3) "66"
需要用一个double类型的数字进行绑定
java 代码连接Redis
需要导入相关的jar包:
<!-- 引入redis客户端依赖 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.4.2</version>
</dependency>
package com.hzc;
import redis.clients.jedis.Jedis;
import java.util.*;
public class TestRedisConection {
static Jedis jedis = new Jedis("192.168.48.5");
public static void main(String[] args) {
System.out.println("开始Redis的服务----------");
System.out.println("服务连接成功---------------"+jedis.ping());
// setString();
// hash();
// redisSet();
// redisList();
set();
}
public static void setString() {
System.out.println("存储数据到时String数据中去");
jedis.set("username", "iloveyouhouandmyparents12");
// jedis.del("username");
System.out.println(jedis.get("username"));
}
public static void hash() {
// 存储数据到hash里边
String mset = jedis.mset("name", "hou", "age", "22", "address", "hunan");
// 由于key相同所以后面的这个会覆盖掉前面的
String mset1 = jedis.mset("name", "jiejie", "age", "25", "address", "hunan");
System.out.println(mset);
// 获取你的redis中的数据
List<String> list = jedis.mget("name", "age", "address");
for (String string :
list) {
System.out.println(string);
}
}
public static void redisSet() {
// 放入值到你的list中
jedis.hset("list1", "name", "list11");
jedis.hset("list1", "age", "23");
// 23也会被覆盖掉
jedis.hset("list1", "age", "53");
jedis.hset("list1", "address", "美国");
// set里面的值不可以进行重复
jedis.hset("list1", "address", "美国");
String name = jedis.hget("list1", "name");
System.out.println(name);
// 获取list1中的所有的内容
System.out.println("-----------------hset中的所有的内容");
Map<String, String> map = jedis.hgetAll("list1");
Set<String> set = map.keySet();
for (String string :
set) {
System.out.println(string);
}
Collection<String> values = map.values();
for (String value :
values) {
System.out.println(value);
}
}
public static void redisList() {
// 通过这个方式存储数据到list
jedis.lpush("student", "zhangsan");
jedis.lpush("student", "lisi");
jedis.lpush("student", "王五");
Long lo = jedis.lpush("student", "小狗");
System.out.println(lo);
//这个地方就是取出数据,第一个数据的名字,第二个开始的位置,第三个结束的位置进
List<String> list = jedis.lrange("student", 0, jedis.llen("student"));
for (String string : list) {
System.out.println(string);
}
// 通过这个方法可以看到key为student里边一共存储了多少个数据;
System.out.println(jedis.llen("student"));
System.out.println("弹出一个元素:"+jedis.lpop("student"));
System.out.println("弹出一个元素:"+jedis.lpop("student"));
System.out.println("弹出一个元素"+jedis.lpop("student"));
System.out.println("弹出一个元素"+jedis.lpop("student"));
System.out.println(jedis.llen("student"));
}
public static void set(){
// 这个进行数据的存储
jedis.sadd("set001","houzhicong");
jedis.sadd("set001","don't fear anything");
jedis.sadd("set001","roothouzhicong");
jedis.sadd("set001","whatever you through,it's the best arrangement");
// 获取他里边的数据
Set<String> set = jedis.smembers("set001");
for (String string:set
) {
System.out.println(string);
}
// 也可以用迭代器进行取出值来
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
建立一个Pojo类Person:
package com.hzc.po;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
//这个类是进行序列化接口-----可以标识你的这个类对象可以转换为二进制
public class Person implements Serializable {
int id;
String name;
String address;
int age;
}
RedisObject:
package com.hzc.po;
import redis.clients.jedis.Jedis;
public class RedisObject {
static Jedis jedis=new Jedis("192.168.48.5");
public static void main(String[] args) {
// 测试连接的效果
System.out.println(jedis.ping());
//进行反序列化的操作
Person person2 = get();
System.out.println(person2);
}
// 创建一个person对象存储到Redis中
public static void save(){
// 通过二进制进行相关的插入的操作,但是插入的是一个二进制文件,还需要反序列化
Person person=new Person(1001,"smallcuteMonkey","hunan",20);
jedis.set("person".getBytes(),SerializeUtil.serialize(person));
Person person1=new Person(1002,"iloveyoumyself","hunan",23);
jedis.set("person1".getBytes(),SerializeUtil.serialize(person1));
}
//从redis中读取数据到java中
public static Person get(){
byte[] personBytes = jedis.get("person".getBytes());
return (Person) SerializeUtil.unserialize(personBytes);
}
}
Redis进行持久化操作
RDB
RDB (Redis DataBase):指定的时间间隔将数据集写入磁盘。是redis的默认的
AOF (Append on File):
RDB:
持久化流程:通过生成的一个fork子进程,进行生成一个dump.rdb文件,然后最后把这个文件去覆盖我们的持久化文件。
持久化的key在指定配置规则发生变化的话,dump.rdb文件大小 会发生变化。最后一次持久化的时候可能造成数据丢失(发生变化的key没有签到指定的数量。)
save:手动的设置key变化的规则,在指定的时间间隔有多少key发生变化会进行持久化操作。
bgsave:redis在后台异步进行同步操作,快照同时可以响应客户端请求。
缺点:如果数据量比较大的key进行保存的时候复制一份的时候会有性能压力。
AOF(Append on File)
以日志的形式来记录每个写操作(增量保存),只将redis执行过的写指令记录下来,只许追加文件但不可以改写文件。redis重启的时候会根据这个写指令来恢复数据。
和RDB区别: 默认不开起的,需要通过配置redis.conf,手动开启,默认文件 appendonly.aof 保存路径和 RDB一致。
如果AOF和RDB同时开启听谁的?
系统默认听取AOF的数据(不会存在数据丢失)
Aof出现异常怎么修复?
里面有一个 redis-check-aof 的东西,里面有相关的Aof的信息,通过命令的形式进行修复
redis-check-aof -- fix appendonly.aof
Aof同步频率设置?
rewrite操作?
当Aof文件超过设置的限制,会进行压缩文件。
只记录开始的操作和最后的写命令。
- 客户端的请求写命令会被append追加到Aof缓冲区内。
- Aof根据持久化策略[always,everysec,no]将操作同步到磁盘Aof中
- 如果文件大小超过重写策略,会对文件进行rewrite操作,压缩AOF文件容量。
- redis重新启动的时候会进行加载Aof文件,恢复数据。
Redis实现缓存的流程
数据查询的时候会先查询Redis里面的数据,如果存在直接返回,如果不存在将查询mysql(或者你的其他的数据库的信息),然后重新将你的没有从Redis查询到的数据写入到数据库中。
Redis的8种淘汰策略
# volatile-lru -> remove the key with an expire set using an LRU algorithm
# allkeys-lru -> remove any key according to the LRU algorithm
# volatile-random -> remove a random key with an expire set
# allkeys-random -> remove a random key, any key
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
# noeviction -> don't expire at all, just return an error on write operations
#
翻译:
Redis的高频面试题目
-
Redis作为缓存的使用场景?
-
Redis的如何实现高可用?
- Redis有持久化机制,RDB和 AOF,默认是AoF,(可能会存在数据丢失)
- Redis可以实现主从复制 ,可以通过配置哨兵,保证可用性
- Redis也支持集群,通过集群配置保证Redis的 高并发。
-
你刚才说redis集群请问如何解决redis集群的脑裂问题?
-
Redis中的数据可以设置过期时间,当数据过期之后的方法key并没有进行清除,请问如何处理?
-
高并发下出现的商品超卖问题怎么解决?
我们的sychronized针对的只是一台服务器的的锁也是JVM锁,但是集群模式下的,并发出现的商品超卖问题怎么解决?
Redis可以实现 分布式锁。
Redis分布式锁实现?
1.分布锁还需要加上JVM锁吗?
需要,这样可以提高性能,当你的每个模块只有一个线程抢占到锁,每个模块就一个线程去抢占分布式锁可以提高分布式锁的性能。
2. 两大类分布式锁?
- 类cas自旋的分布式锁的询问方式(redis,mysql实现)
- even事件通知我后续锁的变化,轮询向外过程(zookeeper,etcd)
zookeper实现的分布式锁?
参考锋迷商城通过Redis的setnx命令方式实现分布式锁:
商品订单超卖问题:
代码:
// 保存订单的步骤
// 1.查询选中购买的购物车详情
// 2. 校验库存
// 3.保存订单
// 4.保存订单快照
// 5.购买成功需要删除购物车记录
// 可以知道这四个步骤需要同时成功或者同时失败,符合一个事务的操作(ACID)
@Transactional(isolation = Isolation.SERIALIZABLE)
public Map<String,String> addOrder(List<Integer> cids, Orders orders) throws SQLException{
synchronized (this){
Map<String,String> map=new HashMap<>();
// 根据cids查询购物车的详情记录(包括库存)
List<ShoppingCartVO> shoppingCartVOList = shoppingCartMapper.selectShoppingcartByids(cids);
// 并发操作下防止库存扣减数量不对
Boolean flag = true;
String[] skuIds = new String[shoppingCartVOList.size()];
for (int i = 0; i < shoppingCartVOList.size(); i++) {
// 可以根据 一条购物车数据中包含一个skuId进行 找到这个skuId
String skuId = shoppingCartVOList.get(i).getSkuId();
//这个setIfAbsent相当于setnx命令
Boolean ifAbsent = stringRedisTemplate.boundValueOps(skuId).setIfAbsent("skuId");
// 把每个商品的skuId放入数组中,用于标识每个商品,方便后面的释放的相对应的商品锁
if(ifAbsent){
skuIds[i] = skuId;
}
flag = flag && ifAbsent;
}
if (flag){
List<ShoppingCartVO> list = shoppingCartMapper.selectShoppingcartByids(cids);
// 校验库存
boolean f=true;
String untitled="";
for (ShoppingCartVO sc :list
) {
if(Integer.valueOf(sc.getCartNum())>sc.getStock()){
f=false;
}
// if(shoppingCartVOList.size() == 1){
// untitled=untitled+sc.getProductName();
// break;
// }
// 获取所有的商品名称,以,分割拼接成字符串
untitled=untitled+sc.getProductName()+",";
}
if(untitled.lastIndexOf(",") == untitled.length()-1) { //lastIndexOf获取最后一个逗号的索引,判断是否是字符串的最后一位
untitled = untitled.substring(0, untitled.length()-1);
}
if(f){
// 表示库存充足进行保存
// 1.userId 2 untitled名称 3 收件人地址,姓名,电话,地址
// 4. 总价格 5.支付方式
// 6.创建 订单的时间
// 7.订单初始状态 1 待支付
orders.setStatus(1);
orders.setUntitled(untitled);
orders.setCreateTime(new Date());
orders.setCancelTime(new Date());
orders.setDeliveryTime(new Date());
orders.setFinishTime(new Date());
// 生成订单编号
String orderId = UUID.randomUUID().toString().replace("-", "6").substring(0,26);
orders.setOrderId(orderId);
// 保存订单
int i=ordersMapper.insert(orders);
if(i>0){
// ordersItem 生成商品快照
// List<OrdersItem> ordersItemList=new ArrayList<>();
for (ShoppingCartVO sc :list) {
// 生成订单的编号
int cnum=Integer.valueOf(sc.getCartNum());
String itemid=System.currentTimeMillis()+(new Random().nextInt(9999)+100)+"";
String itemid1 = itemid.substring(1, 13);
// 注意一下double需要转换为Bigdecimal类型
// public OrdersItem(Integer orderId, Integer productId,
// String productName,
// String productImg, Integer skuId, String skuName,
// BigDecimal productPrice, Integer buyCounts,
// BigDecimal totalAmount, Date basketDate, Date buyTime,
// Integer isComment)
// int itemid2=Integer.parseInt(itemid1);
OrdersItem ordersItem= new OrdersItem();
// 这个Oders表的orderId必须和OdersItem表的orderId类型和数据一样
ordersItem.setOrderId(itemid1);
ordersItem.setProductId(Integer.valueOf(sc.getProductId()));
ordersItem.setProductName(sc.getProductName());
ordersItem.setProductImg(sc.getProductImg());
ordersItem.setSkuId(Integer.valueOf(sc.getSkuId()));
System.out.println(sc.getSkuName());
// ordersItem.setSkuName(sc.getSkuName());
System.out.println(sc.getSellPrice());
ordersItem.setProductPrice(new BigDecimal(String.valueOf(sc.getProductPrice())));
ordersItem.setBuyCounts(cnum);
ordersItem.setTotalAmount(sc.getProductPrice());
ordersItem.setBasketDate(new Date());
ordersItem.setBuyTime(new Date());
ordersItem.setIsComment(0);
// ordersItemList.add(ordersItem);
int m=ordersItemMapper.insert(ordersItem);
}
// int j = ordersItemMapper.insertList(ordersItemList);
// 扣减库存???
// 根据套餐Id修改库存量
for (ShoppingCartVO sc :list
) {
String skuId = sc.getSkuId();
int newStock=sc.getStock()-Integer.valueOf(sc.getCartNum());
// Example example = new Example(ProductSku.class);
// Example.Criteria criteria = example.createCriteria();
// criteria.andEqualTo("skuId",skuId);
// ProductSku productSku = productSkuMapper.selectByPrimaryKey(skuId);
ProductSku productSku=new ProductSku();
productSku.setSkuId(skuId);
productSku.setStock(newStock);
// productSku.setSkuImg(null);
productSkuMapper.updateByPrimaryKeySelective(productSku);
}
// 保存订单成功 购物车中的记录购买成功之后,购物车中对应做删除
for (Integer cid:cids
) {
shoppingCartMapper.deleteByPrimaryKey(cid);
}
//订单生成成功释放锁
for (int m = 0; m < skuIds.length; m++) {
if(null != skuIds[m] && !"".equals(skuIds[m])){
// 释放对应商品锁,方便下一个商品操作
stringRedisTemplate.delete(skuIds[m]);
}
}
map.put("orderId",orderId);
map.put("productNames",untitled);
return map;
}
}else{
// 不足
return null;
}
}else {
// 对于不同的商品需要释放锁操作
for (int i = 0; i < skuIds.length; i++) {
if(null != skuIds[i] && !"".equals(skuIds[i])){
// 释放对应商品锁,方便下一个商品操作
stringRedisTemplate.delete(skuIds[i]);
}
}
}
}
return null;
}
出现并发的相关的问题
1. 订单中部分商品加锁成功,但是某个商品加锁失败,最后的加锁状态为失败,那其他商品加的锁怎么办?
如果最后加锁失败了,应该释放所有的锁,把所有的商品的锁,delete(key)
2.在加锁成功之前,我们根据购车车id查询出来的数据(包含库存)是否使用这个库存进行校验?
- 不能 因为在加锁成功之前和加锁成功之后里面可能有其他的并发线程修改了库存,所以每次加锁成功还得再次查询购物车的数据,再得到新的库存量。
3. 当当前的线程加锁成功之后,程序挂了或者发生异常,没有没有走到释放锁,那这种情况怎么解决?
可以给加锁的key设置一个过期时间,比如10s,所以就算如果发生 异常,也会自动释放锁
//这个setIfAbsent相当于setnx命令
Boolean ifAbsent = stringRedisTemplate.boundValueOps(skuId).setIfAbsent("skuId",10, TimeUnit.MINUTES);
出现问题: t1因为某些原因没有执行完成业务,然后t2加锁成功,这个时候t1进行释放锁,它释放的是t2的锁,导致t2在无锁状态下执行业务。
解决:
- 给锁加上一个标识value,会设置到redis中
- 从redis中获取value,如果和当前的value相等,则是当前线程加的锁,直接给释放。
问题:查询出来的锁标识,和删除锁之间的还是有并发问题,如果两个指令不是同时成功,或者同时失败,没有这种原子性也会出现问题?
解决:通过Lua脚本进行编排在一起,使他们具有这样的特性。
Redisson框架实现分布式锁
1. Redisson
2. 引入依赖
<!--分布式锁实现的依赖-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.5</version>
</dependency>
2. 引入配置类连接Redis
package com.qfedu.fmmall.service.config;/*
**
*@author SmallMonkey
*@Date 2022/12/23 12:51
*
*
**/
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
// @Value("${redisson.addr.singleAddr.host}")
@Value("${spring.redis.host}")
private String host;
@Value("${redisson.addr.singleAddr.password}")
// @Value("${spring.redis.password}")
private String password;
@Value("${redisson.addr.singleAddr.port}")
// @Value("${spring.redis.port}")
private int port;
@Value("${redisson.addr.singleAddr.database}")
// @Value("${spring.redis.database}")
private int database;
@Bean
public RedissonClient redissonClient(){
Config config = new Config();
config.useSingleServer()
.setAddress("redis://" + host + ":" + port)
//由于Redisson是没有设置密码的,所以这里不用setPassWord()了
//.setPassword(password)
.setDatabase(database);
return Redisson.create(config);
}
}
3. application.yml配置文件里面配置上host,port,password
server:
port:
spring:
datasource:
druid:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/fmmall?characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
username: root
password: roothouzhicong
application:
name: mapper
redis:
host: 127.0.0.1 #或者写localhost
port: 6379
database: 0
password:
mybatis:
mapper-locations: classpath:mappers/*Mapper.xml
type-aliases-package: com.qfedu.fmmall.entity
# mybatis配置日志打印sql语句到控制台
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
logging:
level:
io.swagger.models.parameters.AbstractSerializableParameter: error
redisson:
addr:
singleAddr:
host:
password:
port: 6379
database: 0
4. 业务里面进行止锁(不管上锁成功与否,最后都要进行锁的释放,所以放在finally{}代码块里面进行锁的释放)
@Transactional(isolation = Isolation.SERIALIZABLE)
public Map<String,String> addOrder(List<Integer> cids, Orders orders) throws SQLException{
// synchronized (this){
Map<String,String> map= null;
// 根据cids查询购物车的详情记录(包括库存)
List<ShoppingCartVO> shoppingCartVOList = shoppingCartMapper.selectShoppingcartByids(cids);
// 并发操作下防止库存扣减数量不对
Boolean islock = true;
String[] skuIds = new String[shoppingCartVOList.size()];
Map<String,String> values = new HashMap<>(16);
//用于存放当前订单中的锁,最后用于翻译锁
Map<String,RLock> locks = new HashMap<>();
for (int i = 0; i < shoppingCartVOList.size(); i++) {
// 可以根据 一条购物车数据中包含一个skuId进行 找到这个skuId
String skuId = shoppingCartVOList.get(i).getSkuId();
//方法一没有直接使用Redis实现
/*String value = UUID.randomUUID().toString();
//这个setIfAbsent相当于setnx命令
// value给每个锁一个标识,防止 t2线程释放 t1线程锁错误
Boolean ifAbsent = stringRedisTemplate.boundValueOps(skuId).setIfAbsent(value,10, TimeUnit.MINUTES);*/
//方法二使用Redisson框架实
boolean b = false;
RLock rlock = redissonClient.getLock(skuId);
try {
b = rlock.tryLock(10,3,TimeUnit.MINUTES);
// 把每个商品的skuId放入数组中,用于标识每个商品,方便后面的释放的相对应的商品锁
if(b){
skuIds[i] = skuId;
//放入map中
// values.put(skuId,value);
locks.put(skuId,rlock);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
islock = islock && b;
}
try{
if (islock){
//这里的购物车数据需要重新查询,因为在上锁的这段时间有并发线程购买东西
List<ShoppingCartVO> list = shoppingCartMapper.selectShoppingcartByids(cids);
// 校验库存
boolean f=true;
String untitled="";
for (ShoppingCartVO sc :list
) {
if(Integer.valueOf(sc.getCartNum())>sc.getStock()){
f=false;
}
// if(shoppingCartVOList.size() == 1){
// untitled=untitled+sc.getProductName();
// break;
// }
// 获取所有的商品名称,以,分割拼接成字符串
untitled=untitled+sc.getProductName()+",";
}
if(untitled.lastIndexOf(",") == untitled.length()-1) { //lastIndexOf获取最后一个逗号的索引,判断是否是字符串的最后一位
untitled = untitled.substring(0, untitled.length()-1);
}
if(f){
// 表示库存充足进行保存
// 1.userId 2 untitled名称 3 收件人地址,姓名,电话,地址
// 4. 总价格 5.支付方式
// 6.创建 订单的时间
// 7.订单初始状态 1 待支付
orders.setStatus(1);
orders.setUntitled(untitled);
orders.setCreateTime(new Date());
orders.setCancelTime(new Date());
orders.setDeliveryTime(new Date());
orders.setFinishTime(new Date());
// 生成订单编号
String orderId = UUID.randomUUID().toString().replace("-", "6").substring(0,26);
orders.setOrderId(orderId);
// 保存订单
int i=ordersMapper.insert(orders);
if(i>0){
// ordersItem 生成商品快照
// List<OrdersItem> ordersItemList=new ArrayList<>();
for (ShoppingCartVO sc :list) {
// 生成订单的编号
int cnum=Integer.valueOf(sc.getCartNum());
String itemid=System.currentTimeMillis()+(new Random().nextInt(9999)+100)+"";
String itemid1 = itemid.substring(1, 13);
// 注意一下double需要转换为Bigdecimal类型
// public OrdersItem(Integer orderId, Integer productId,
// String productName,
// String productImg, Integer skuId, String skuName,
// BigDecimal productPrice, Integer buyCounts,
// BigDecimal totalAmount, Date basketDate, Date buyTime,
// Integer isComment)
// int itemid2=Integer.parseInt(itemid1);
OrdersItem ordersItem= new OrdersItem();
// 这个Oders表的orderId必须和OdersItem表的orderId类型和数据一样
ordersItem.setOrderId(itemid1);
ordersItem.setProductId(Integer.valueOf(sc.getProductId()));
ordersItem.setProductName(sc.getProductName());
ordersItem.setProductImg(sc.getProductImg());
ordersItem.setSkuId(Integer.valueOf(sc.getSkuId()));
System.out.println(sc.getSkuName());
// ordersItem.setSkuName(sc.getSkuName());
System.out.println(sc.getSellPrice());
ordersItem.setProductPrice(new BigDecimal(String.valueOf(sc.getProductPrice())));
ordersItem.setBuyCounts(cnum);
ordersItem.setTotalAmount(sc.getProductPrice());
ordersItem.setBasketDate(new Date());
ordersItem.setBuyTime(new Date());
ordersItem.setIsComment(0);
// ordersItemList.add(ordersItem);
int m=ordersItemMapper.insert(ordersItem);
}
// int j = ordersItemMapper.insertList(ordersItemList);
// 扣减库存???
// 根据套餐Id修改库存量
for (ShoppingCartVO sc :list
) {
String skuId = sc.getSkuId();
int newStock=sc.getStock()-Integer.valueOf(sc.getCartNum());
// Example example = new Example(ProductSku.class);
// Example.Criteria criteria = example.createCriteria();
// criteria.andEqualTo("skuId",skuId);
// ProductSku productSku = productSkuMapper.selectByPrimaryKey(skuId);
ProductSku productSku=new ProductSku();
productSku.setSkuId(skuId);
productSku.setStock(newStock);
// productSku.setSkuImg(null);
productSkuMapper.updateByPrimaryKeySelective(productSku);
}
// 保存订单成功 购物车中的记录购买成功之后,购物车中对应做删除
for (Integer cid:cids
) {
shoppingCartMapper.deleteByPrimaryKey(cid);
}
//订单生成成功释放锁
for (int m = 0; m < skuIds.length; m++) {
if(null != skuIds[m] && !"".equals(skuIds[m])){
/* String s = stringRedisTemplate.boundValueOps(skuIds[m]).get();
if(s != null && s.equals(values.get(skuIds[m]))){
// 释放对应商品锁,方便下一个商品操作
stringRedisTemplate.delete(skuIds[m]);
}*/
//redisson实现
locks.get(skuIds[m]).unlock();
}
}
map = new HashMap<>();
map.put("orderId",orderId);
map.put("productNames",untitled);
}
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
//方法二: Redisson框架实现删除
for (int m = 0; m < skuIds.length; m++) {
if(null != skuIds[m] && !"".equals(skuIds[m])){
//redisson实现
locks.get(skuIds[m]).unlock();
}
}
}
// 不足
return map;
}