一、缓存的应用
什么是缓存?
在互联网场景下,尤其 2C 端大流量场景下,需要将一些经常展现和不会频繁变更的数据,存放在存取速率更快的地方。缓存就是一个存储器,在技术选型中,常用 Redis 作为缓存数据库。缓存主要是在获取资源方便性能优化的关键方面。
缓存的应用场景有哪些呢?
比如常见的电商场景,根据商品ID获取商品信息时,店铺信息和商品详情信息就可以缓存在 Redis,直接从 Redis 获取。减少了去数据库查询的次数。但会出现新的问题,就是如何对缓存进行更新?
摘要: 原创出处 www.bysocket.com
二、更新缓存的策略
参考《缓存更新的套路》
http://coolshell.cn/articles/17416.html,缓存更新的模式有四种:Cache aside, Read through, Write through, Write behind caching。
这里我们使用的是 Cache Aside 策略,从三个维度:(摘自 耗子叔叔博客)
失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
命中:应用程序从cache中取数据,取到后返回。
更新:先把数据存到数据库中,成功后,再让缓存失效。
大致的流程:
a. 从商品 Cache 中获取商品详情,如果存在,则返回获取 Cache 数据返回。
b. 如果不存在,则从商品 DB 中获取。获取成功后,将数据存到 Cache 中。则下次获取商品详情,就可以从 Cache 就可以得到商品详情数据。
c. 从商品 DB 中更新或者删除商品详情成功后,则从缓存中删除对应商品的详情缓存
三、案例的应用
3.1)安装Redis忽略
3.2)数据库忽略(同五)
3.3)工程结构
3.4)pom依赖包redis,还有jsp,json 其他忽略
<!-- spring-boot-starter-redis 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.3.1.RELEASE</version>
</dependency>
<!--JSP-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<!-- apache common -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<!-- json jar -->
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>0.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-lgpl</artifactId>
<version>1.8.5</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-lgpl</artifactId>
<version>1.8.5</version>
</dependency>
3.5) application.properties 应用配置文件,添加 Redis 相关配置
## 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/springbootdb?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
## Mybatis 配置
##mybatis.typeAliasesPackage=org.spring.springboot.domain
##mybatis.mapperLocations=classpath:mapper/*.xml
## Redis 配置
## Redis数据库索引(默认为0)
spring.redis.database=0
## Redis服务器地址
spring.redis.host=127.0.0.1
## Redis服务器连接端口
spring.redis.port=6379
## Redis服务器连接密码(默认为空)
spring.redis.password=
## 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8
## 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
## 连接池中的最大空闲连接
spring.redis.pool.max-idle=8
## 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
## 连接超时时间(毫秒)
spring.redis.timeout=0
##jsp
spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp
3.6)实体类必须序列化
public class City implements Serializable {...}
3.7)Dao接口
@Mapper
// 这个注解是Mybatis 的 Mapper(无需配置文件)
public interface CityDao {
/**
* 根据城市名称,查询城市信息
*
* @param cityName
* 城市名
*/
@Select("select * from city where id=#{id}")
// 查询语句
// 返回 Map 结果集
@Results({ @Result(column = "id", property = "id"),
@Result(column = "province_id", property = "provinceId"),
@Result(column = "city_name", property = "cityName"),
@Result(column = "description", property = "description")
})
public City findCity(@Param("id") Long id);
/**
* 查询语句
*
* 注意:属性与数据列名不一致就会显示NUll,那么就必须配置ResultMap一一对应
*
* @return
*/
@Select("select * from city")
// 返回 Map 结果集
@Results({ @Result(column = "id", property = "id"),
@Result(column = "province_id", property = "provinceId"),
@Result(column = "city_name", property = "cityName"),
@Result(column = "description", property = "description")
})
public List<City> all();
/**
* 根据ID删除数据
*
* @param id
* @return
*/
@Delete("delete from city where id=#{id}")
Long delete(Long id);
/**
* 添加数据
*
* @param city
* @return
*/
@Insert("insert into city values(#{id},#{provinceId},#{cityName},#{description})")
Long save(City city);
/**
* 更新数据
*
* @param city
* @return
*/
@Update("update city set province_id=#{provinceId},city_name=#{cityName},description=#{description} where id=#{id}")
Long updates(City city);
}
3.8)业务实现类
@Service
public class CityServiceImpl implements CityService {
// 日志对象 org.slf4j.Logger
private Logger logger = LoggerFactory.getLogger(CityServiceImpl.class);
// 注入dao
@Autowired
private CityDao cityDao;
// 注入redisTemplate模板
@Autowired
private RedisTemplate redisTemplate;
/**
*
* (1)如果缓存存在,从缓存中获取城市信息
*
* (2)如果缓存不存在,从 DB 中获取城市信息,然后插入缓存
*/
@Override
public City findCityName(Long id) {
// 从缓存中获取
String key = "city_" + id;
System.out.println("findCityName===>"+key);
// 操作视图接口类 String/Value 操作
ValueOperations<String, City> operations = redisTemplate.opsForValue();
// 缓存是否存在
boolean flag = redisTemplate.hasKey(key);
System.out.println("flag====>"+flag);
// 判断
if (flag) {
// 获得value
City city = operations.get(key);
// 输出日志
logger.info("CityServiceImpl.findCityName(),从缓存中获取数据:"
+ city.toString());
return city;
}
// 从 DB 中获取城市信息
City citydb = cityDao.findCity(id);
// 插入到缓存中
operations.set(key, citydb, 10, TimeUnit.SECONDS);
logger.info("CityServiceImpl.findCityName(),数据插入到缓存中:"
+ citydb.toString());
return citydb;
}
/**
* 查询所有的数据
*/
@Override
public List<City> list() {
// 从 DB 中获取城市信息
List<City> list = cityDao.all();
return list;
}
/**
* 添加数据
*/
@Override
public Long add(City city) {
// TODO Auto-generated method stub
return cityDao.save(city);
}
/**
*
* 删除
* (1)如果缓存存在,删除 (2)如果缓存不存在,不操作
*/
@Override
public Long del(Long id) {
// TODO Auto-generated method stub
Long del = cityDao.delete(id);
// 获得key
String key = "city_" + id;
// 判断是否存在缓存
if (redisTemplate.hasKey(key)) {
// 缓存存在,删除缓存
redisTemplate.delete(key);
// 日志跟踪
logger.info("CityServiceImpl.del(),数据从缓存中删除>>>>" + id);
}
return del;
}
/**
*
* 更新 (1)如果缓存存在,删除 (2)如果缓存不存在,不操作
*/
@Override
public Long update(City city) {
// TODO Auto-generated method stub
Long my = cityDao.updates(city);
System.out.println(my+"------>"+city);
// 获得key
String key = "city_" + city.getId();
// 判断
if (redisTemplate.hasKey(key)) {
// 删除缓存
redisTemplate.delete(key);
// 日志跟踪
logger.info("CityServiceImpl.update(),数据从缓存中删除>>>>" + my);
}
return my;
}
}
3.9)控制类
@RestController
public class CityRestController {
// 注入业务
@Autowired
private CityService cityService;
/**
* 添加数据
*
* @param cityName
* @return
*/
@RequestMapping(value = "/your/city", method = RequestMethod.POST)
public Long addCity(@RequestBody City city) {
return cityService.add(city);
}
/**
*
* 更新数据
*
* @param cityName
* @return
*/
@RequestMapping(value = "/your/city", method = RequestMethod.PUT)
public void updateCity(@RequestBody City city) {
cityService.update(city);
}
/**
*
* 删除数据
*
* @param cityName
* @return
*/
@RequestMapping(value = "/your/city/{id}", method = RequestMethod.DELETE)
public Long updateCity(@PathVariable("id") Long id) {
return cityService.del(id);
}
/**
*
*
* @param cityName
* @return
*/
// @RequestMapping("/your/city")
// public List<City> allCity() {
// return cityService.list();
// }
/**
* 查询一条数据
*
* @param cityName
* @return
*/
@RequestMapping(value = "/your/city/{id}", method = RequestMethod.GET)
public City findCity(@PathVariable("id") Long id) {
return cityService.findCityName(id);
}
}
3.10)springBoot启动类忽略
3.11)JSP页面
<body>
<h3>查询界面</h3>
<input type="text" name="cityid" id="cityid"/>
<h3 id="c1"></h3>
<h3 id="c2"></h3>
<p><input type="button" value="查询" id="ajaxFind"/>
<input type="button" value="删除" id="ajaxDel"/>
<fieldset>
<legend>添加城市</legend>
pid: <input type="text" id="pid"/>
uname: <input type="text" id="uname"/>
desc: <input type="text" id="desc"/>
<input type="button" value="添加" id="ajaxAdd"/>
</fieldset>
<p/><p/><p/>
<fieldset>
<legend>修改城市</legend>
id: <input type="text" name="cityid" id="cid"/>
pid: <input type="text" id="ppid"/>
uname: <input type="text" id="puname"/>
desc: <input type="text" id="pdesc"/>
<input type="button" value="修改" id="ajaxPut"/>
</fieldset>
</p>
</body>
3.12)Web.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<display-name></display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- 配置 HiddenHttpMethodFilter 可以把POST请求转为 DELETE 和 PUT请求 -->
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>httpPutFormcontentFilter</filter-name>
<filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>httpPutFormcontentFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
3.13)Ajax无刷新
$(function() {
// 单击事件
$("#ajaxFind").click(function() {
var id = $("#cityid").val();
$.ajax({
url : "your/city/" + id,
data : {
"id" : id
},
type : "get",
dataType : "json",
success : function(data) {
$("#c1").html("名称:" + data.cityName);
$("#c2").html("描述:" + data.description);
$("#c2").show();
},
error : function() {
$("#c1").html("查询失败");
$("#c2").hide();
}
});
});
// 单击事件
$("#ajaxDel").click(function() {
var id = $("#cityid").val();
if (id!="" && confirm("您确定要删除吗?")) {
$.ajax({
url : "your/city/" + id,
data : {
"id" : id
},
type : "delete",
dataType : "json",
success : function(data) {
$("#c2").show();
},
error : function() {
$("#c2").hide();
}
});
}
});
// 单击事件
$("#ajaxAdd").click(function() {
// json数据
var city = {
"provinceId" : $("#pid").val(),
"cityName" : $("#uname").val(),
"description" : $("#desc").val()
};
$.ajax({
url : "your/city",
data : JSON.stringify(city),
type : "post",
contentType : 'application/json;charset=utf-8', // 指定类型
dataType : JSON.stringify(city), //
success : function(data) {
$("#c2").show();
},
error : function() {
$("#c2").hide();
}
});
});
// 单击事件
$("#ajaxPut").click(function() {
// json数据
var city = {
"id" : $("#cid").val(),
"provinceId" : $("#ppid").val(),
"cityName" : $("#puname").val(),
"description" : $("#pdesc").val()
};
$.ajax({
url : "your/city",
data : JSON.stringify(city),
type : "put",
contentType : 'application/json;charset=utf-8', // 指定类型
dataType : JSON.stringify(city), //
success : function(data) {
$("#c2").show();
},
error : function() {
$("#c2").hide();
}
});
});
});
运行效果:(注意不能写工程名哦! 必须启动Redis服务器)
数据库数据:
(1)查询
(2)添加
(3)删除
(4)修改
写入缓存中
https://github.com/yuanhangs/bearDay