前面两篇博客一篇是实现了redis做缓存,原理是在启动类中开启@EnableCaching注解,之后在需要缓存的地方使用@Cacheable和@CacheEvict注解;另一篇是实现了redis处理并发操作,原理是使用jedis的setnx命令操作。现在希望同时实现这两个功能,即可以在查询时使用缓存,也可以在更新时处理并发,这里综合前两篇博客即可:
一、项目:
结构:
1、pom:
4.0.0
com.redis
redislock
0.0.1-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
1.4.1.RELEASE
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-redis
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.1.1
mysql
mysql-connector-java
5.1.21
com.alibaba
fastjson
1.2.33
2、properties配置文件:
#mysql
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/produce?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=wtyy
#mybatis
mybatis.mapper-locations=classpath*:mapper/*Mapper.xml
spring.redis.host=localhost
spring.redis.port=6379
#spring.redis.password=
spring.redis.database=1
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
spring.redis.pool.max-idle=500
spring.redis.pool.min-idle=0
spring.redis.timeout=0
3、RedisUtil封装Jedis操作:
package com.product.util;
import javax.xml.ws.BindingType;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
//import org.apache.log4j.chainsaw.Main;
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* @Description:redis工具类
* @ClassName:
* @date 2016年10月31日 上午11:25:06
*/
@SuppressWarnings("unused")
@Component
public class RedisUtil {
private static final String IP = "127.0.0.1"; // ip
private static final int PORT = 6379; // 端口
// private static final String AUTH=""; // 密码(原始默认是没有密码)
private static int MAX_ACTIVE = 1024; // 最大连接数
private static int MAX_IDLE = 200; // 设置最大空闲数
private static int MAX_WAIT = 10000; // 最大连接时间
private static int TIMEOUT = 10000; // 超时时间
private static boolean BORROW = true; // 在borrow一个事例时是否提前进行validate操作
private static JedisPool pool = null;
private static Logger logger = Logger.getLogger(RedisUtil.class);
/**
* 初始化线程池
*/
static {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(MAX_ACTIVE);
config.setMaxIdle(MAX_IDLE);
config.setMaxWaitMillis(MAX_WAIT);
config.setTestOnBorrow(BORROW);
pool = new JedisPool(config, IP, PORT, TIMEOUT);
}
/**
* 获取连接
*/
public static synchronized Jedis getJedis() {
try {
if (pool != null) {
return pool.getResource();
} else {
return null;
}
} catch (Exception e) {
logger.info("连接池连接异常");
return null;
}
}
// // 加锁
// public boolean tryLock(String key){
// Jedis jedis = null;
// jedis = getJedis();
//
// String result = jedis.setex(key,1000,"1");
//
//
// if("OK".equals(result)){
//
// return true;
// }
//
// return false;
// }
//
// // 释放锁
// public void releaseLock(String key){
// Jedis jedis = null;
// jedis = getJedis();
// jedis.del(key);
//
// }
/**
* @Description:设置失效时间
* @param @param key
* @param @param seconds
* @param @return
* @return boolean 返回类型
*/
public static void disableTime(String key, int seconds) {
Jedis jedis = null;
try {
jedis = getJedis();
jedis.expire(key, seconds);
} catch (Exception e) {
logger.debug("设置失效失败.");
} finally {
getColse(jedis);
}
}
public static boolean exists(String key) {
boolean flag = false;
Jedis jedis = null;
try {
jedis = getJedis();
flag = jedis.exists(key);
} catch (Exception e) {
logger.debug("设置失效失败.");
} finally {
getColse(jedis);
}
return flag;
}
/**
* @Description:插入对象
* @param @param key
* @param @param obj
* @param @return
* @return boolean 返回类型
*/
public static boolean addObject(String key, Object obj) {
Jedis jedis = null;
String value = JSONObject.toJSONString(obj);
try {
jedis = getJedis();
jedis.set(key, value);
return true;
} catch (Exception e) {
logger.debug("插入数据有异常.");
return false;
} finally {
getColse(jedis);
}
}
/**
* @Description:存储key~value
* @param @param key
* @param @param value
* @return void 返回类型
*/
public static Long addValue(String key, String value) {
Jedis jedis = null;
try {
jedis = getJedis();
//String code = jedis.set(key, value);
return jedis.setnx(key, value);
} catch (Exception e) {
logger.debug("插入数据有异常.");
return null;
} finally {
getColse(jedis);
}
}
/**
* @Description:删除key
* @param @param key
* @param @return
* @return boolean 返回类型
*/
public static boolean delKey(String key) {
Jedis jedis = null;
try {
jedis = getJedis();
Long code = jedis.del(key);
if (code > 1) {
return true;
}
} catch (Exception e) {
logger.debug("删除key异常.");
return false;
} finally {
getColse(jedis);
}
return false;
}
/**
* @Description: 关闭连接
* @param @param jedis
* @return void 返回类型
*/
public static void getColse(Jedis jedis) {
if (jedis != null) {
jedis.close();
}
}
}
4、serviceImpl:
package com.product.serviceImpl;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.product.util.RedisUtil;
import com.product.mapper.ProductMapper;
import com.product.mapper.UserOrderMapper;
import com.product.module.Product;
import com.product.module.UserOrder;
import com.product.service.OrderService;
import com.product.service.ProductService;
@Service("orderService")
public class OrderServiceImpl implements OrderService {
@Autowired
private ProductService productService;
@Autowired
private UserOrderMapper userOrderMapper;
// 用户下订单,返回订单id
@Override
public Integer order(String productId, String userId) {
// 逻辑操作
// 先判断productId是否存在
Product product = productService.getByProductId(productId);
if (product == null) {
return null;
}
// 是否有库存
Integer id = product.getId();
Integer total = product.getTotal();
System.out.println("下单前库存" + total);
UserOrder order = new UserOrder();
if (total <= 0) {
return null;
}
order.setCreatetime(new Date());
order.setProductid(productId);
order.setUserid(userId);
int add = userOrderMapper.addOrder(order);
if (add > 0) {
// 创建订单成功,库存--
total--;
System.out.println("下单后库存" + total);
productService.updateTotal(id, total);
// return order.getId();
}
return null;
}
@Override
public Integer getCountByProductId(String productId) {
return userOrderMapper.getCountByProductId(productId);
}
}
package com.product.serviceImpl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import com.product.mapper.ProductMapper;
import com.product.module.Product;
import com.product.service.ProductService;
@Service("productService")
public class ProductServiceImpl implements ProductService{
@Autowired
private ProductMapper productMapper;
@Override
public Product getByProductId(String productId) {
Product product = productMapper.getByProductId(productId);
return product;
}
@Override
public Integer getCountByProductId(String productId) {
return productMapper.getCountByProductId(productId);
}
@Override
public int updateTotal(Integer id, Integer total) {
return productMapper.updateTotal(id,total);
}
@Cacheable(cacheNames="product", key="'product'+#productId")
@Override
public Product cacheGetByProductId(String productId) {
Product product = productMapper.getByProductId(productId);
System.out.println("输出则没有走缓存");
return product;
}
@CacheEvict(cacheNames="product", key="'product'+#productId",condition="#productId!=''")
@Override
public void del(String productId) {
}
@Cacheable(cacheNames="proList")
@Override
public List getList() {
List list =productMapper.selectAll();
System.out.println("查询结合,输出则没有走缓存");
return list;
}
@CacheEvict(cacheNames="proList")
@Override
public void delList() {
}
}
5、controller:
package com.product.controller;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.product.module.Product;
import com.product.service.OrderService;
import com.product.service.ProductService;
import com.product.util.RedisUtil;
@RequestMapping("/product")
@RestController
public class ProductController {
@Autowired
private OrderService orderService;
@Autowired
private ProductService productService;
@Autowired
private RedisUtil redisUtil;
private String KEY = "productId";
@RequestMapping("/order")
public void order(@RequestParam String userId,
@RequestParam String productId) {
boolean lock = redisUtil.exists(KEY);
if (!lock) {
//
Long result = redisUtil.addValue(KEY, productId);
redisUtil.disableTime(KEY, 60);
// redisUtil.disableTime(KEY, 5);
// redisClient.expire(PRODUCT_LOCK, 60, String.valueOf(productId));
if (result == 1) {
System.err.println("不存在key,执行逻辑操作");
orderService.order(productId, userId);
redisUtil.delKey(KEY);
}
}else{
System.out.println("存在该key,不允许执行");
}
}
@RequestMapping("/getCache")
public void getByProductId(@RequestParam String productId){
Product product = productService.cacheGetByProductId(productId);
System.out.println(product);
}
@RequestMapping("/delCache")
public void del(@RequestParam String productId){
System.out.println("删除商品缓存");
productService.del(productId);
}
@RequestMapping("/getCacheList")
public void getList(){
List list = productService.getList();
System.out.println("集合长度"+list.size());
}
@RequestMapping("/delCacheList")
public void delList(){
System.out.println("删除商品集合缓存");
productService.delList();
}
}
注:
(1)并发接口也可以这样写,以productId为key,随便写一个value:
@RequestMapping("/order")
public void order(@RequestParam String userId,@RequestParam String productId){
//是否有缓存
boolean exists = redisUtil.exists(productId);
if(!exists){
//没有,加上
Long sexNx = redisUtil.addValue(productId, "lock");
if(sexNx == 1){
orderService.order(productId,userId);
redisUtil.delKey(productId);
}
}else{
System.out.println(productId+"正在被抢购");
}
}
(2)缓存也可以用RedisUtil手动存进来,这种方法是不需要在启动类中开启@EnableCaching的,如:
@RequestMapping("/getProductInfo")
public void getProductInfo(@RequestParam String productId){
Product product = productService.getProductInfo(productId);
System.err.println(product);
}
@Override
public Product getProductInfo(String productId) {
String key = "productInfo"+productId;
boolean exists = redisUtil.exists(key);
Product product = null;
if(exists){
String productStr = redisUtil.getValue(key);
System.out.println("存在缓存"+productStr);
product = JSON.parseObject(productStr,Product.class);
}else{
System.out.println("没有走缓存");
product = productMapper.getByProductId(productId);
redisUtil.addObject(key, product);
redisUtil.disableTime(key, 10);
}
return product;
}
6、启动类:
package com.product;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@MapperScan("com.product.mapper")
@EnableCaching
public class Start {
public static void main(String[] args) {
SpringApplication.run(Start.class, args);
}
}
二、测试:
1、测缓存:
2、测同步:
另建一个测试工程:
package com.test.test;
import com.test.util.HttpRequestUtil;
public class ProductTest implements Runnable{
public static void main(String[] args) {
ProductTest productTest = new ProductTest();
for(int i=0;i<50;i++){
Thread thread = new Thread(productTest);
thread.start();
}
}
@Override
public void run() {
String url = "http://localhost:8080/product/order";
String productId = "abcd";
String userId = "userid";
String param = "userId="+userId+"&productId="+productId;
HttpRequestUtil.sendPost(url, param);
}
}
模拟50个并发量,运行该测试类测试。