任务描述
任务要求
使用IDEA开发工具构建一个项目多模块工程。study-springboot-chapter09学习关于Springboot集成redis缓存知识点
- 基于study-springboot工程,复制study-springboot-chapter09标准项目,坐标groupId(com.cbitedu)、artifactId(study-springboot-chapter09),其他默认
- 继承study-springboot工程依赖
- 详细学习Spring Mybatis。
- 引入缓存框架redis实现数据缓存
任务收获
- 如何集成第三方持久化技术Spring Mybatis
- 如何引入MySQL数据库依赖
- Spring Boot中整合redis
- 学会使用JUnit完成单元测试
- Redis缓存的使用
任务准备
环境要求
- JDK1.8+
- MySQL8.0.27+
- Maven 3.6.1+
- IDEA/VSCode
数据库准备
创建数据库platform,并创建城市表。
-- ----------------------------
-- Table structure for city
-- ----------------------------
DROP TABLE IF EXISTS `city`;
CREATE TABLE `city`
(
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`province_id` int NULL DEFAULT NULL COMMENT '所属省份',
`city_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '城市名称',
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '描述',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB
CHARACTER SET = utf8mb4
COLLATE = utf8mb4_0900_ai_ci
ROW_FORMAT = Dynamic;
工程目录要求
study-springboot-chapter09
任务实施
如何在Spring Boot的缓存支持中使用Redis实现数据缓存。
第一步:pom.xml中增加相关依赖:
<!-- Spring Boot Data Cache 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Spring Boot Redis 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Spring Boot Mybatis 依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
第二步:配置文件中增加配置信息,以本地运行为例,重点关注redis配置一段。比如:
#服务配置
server:
port: 81
# #设置日志相关打印sql 语句
logging:
level:
com.cbitedu.springboot: debug
org.springframework.web: debug
#关闭运行日志图标(banner)
mybatis:
# # 指定全局配置文件位置
config-location: classpath:mybatis/mybatis-config.xml
# # 指定sql映射文件位置
mapper-locations: classpath:mybatis/mapper/*.xml
spring:
datasource:
url: jdbc:mysql://localhost:3306/platform?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
#配置redis缓存
redis:
# Redis 数据库索引(默认为 0)
database: 0
# Redis 服务器地址
host: localhost
# Redis 服务器连接端口
port: 6379
password:
timeout: 10s
lettuce:
pool:
#连接池最大连接数(使用负值表示没有限制) 默认 8
max-active: 50
#连接池中的最大空闲连接 默认 8
max-idle: 8
#连接池中的最小空闲连接 默认 0
min-idle: 0
关于连接池的配置,注意几点:
- Redis的连接池配置在1.x版本中前缀为spring.redis.pool与Spring Boot 2.x有所不同。
- 在1.x版本中采用jedis作为连接池,而在2.x版本中采用了lettuce作为连接池
- 以上配置均为默认值,实际上生产需进一步根据部署情况与业务要求做适当修改.
第三步:完成任务准备中的数据库工作,创建表
第四步:分别在com.cbitedu.springboot下创建软件包
- 实体类:entity
- 接口层:mapper
- 服务层:service
- 控制台:controller
创建city表的映射对象City
( com.cbitedu.springboot.entity )
package com.cbitedu.springboot.entity; import java.io.Serializable; /** * 城市实体类 * * */ public class City implements Serializable { private static final long serialVersionUID = -1L; /** * 城市编号 */ private Long id; /** * 省份编号 */ private Long provinceId; /** * 城市名称 */ private String cityName; /** * 描述 */ private String description; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Long getProvinceId() { return provinceId; } public void setProvinceId(Long provinceId) { this.provinceId = provinceId; } public String getCityName() { return cityName; } public void setCityName(String cityName) { this.cityName = cityName; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } @Override public String toString() { return "City{" + "id=" + id + ", provinceId=" + provinceId + ", cityName='" + cityName + ''' + ", description='" + description + ''' + '}'; } }创建映射接口mapper( com.cbitedu.springboot.dao )
package com.cbitedu.springboot.dao; import com.cbitedu.springboot.entity.City; import org.apache.ibatis.annotations.Param; import java.util.List; /** * 城市 DAO 接口类 * * */ public interface CityDao { /** * 获取城市信息列表 * * @return */ List<City> findAllCity(); /** * 根据城市 ID,获取城市信息 * * @param id * @return */ City findById(@Param("id") Long id); Long saveCity(City city); Long updateCity(City city); Long deleteCity(Long id); }创建服务接口UserService ( com.cbitedu.springboot.service )
package com.cbitedu.springboot.service; import com.cbitedu.springboot.entity.City; import java.util.List; /** * 城市业务逻辑接口类 * * */ public interface CityService { /** * 根据城市 ID,查询城市信息 * * @param id * @return */ City findCityById(Long id); /** * 新增城市信息 * * @param city * @return */ Long saveCity(City city); /** * 更新城市信息 * * @param city * @return */ Long updateCity(City city); /** * 根据城市 ID,删除城市信息 * * @param id * @return */ Long deleteCity(Long id); }创建服务接口实现类UserServiceImpl ( com.cbitedu.springboot.service.impl )
package com.cbitedu.springboot.service.impl; import com.cbitedu.springboot.dao.CityDao; import com.cbitedu.springboot.entity.City; import com.cbitedu.springboot.service.CityService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Service; import java.util.concurrent.TimeUnit; /** * 城市业务逻辑实现类 * <p> * */ @Service public class CityServiceImpl implements CityService { private static final Logger LOGGER = LoggerFactory.getLogger(CityServiceImpl.class); @Autowired private CityDao cityDao; @Autowired private RedisTemplate redisTemplate; /** * 获取城市逻辑: * 如果缓存存在,从缓存中获取城市信息 * 如果缓存不存在,从 DB 中获取城市信息,然后插入缓存 */ public City findCityById(Long id) { // 从缓存中获取城市信息 String key = "city_" + id; ValueOperations<String, City> operations = redisTemplate.opsForValue(); // 缓存存在 boolean hasKey = redisTemplate.hasKey(key); if (hasKey) { City city = operations.get(key); LOGGER.info("CityServiceImpl.findCityById() : 从缓存中获取了城市 >> " + city.toString()); return city; } // 从 DB 中获取城市信息 City city = cityDao.findById(id); // 插入缓存 operations.set(key, city, 10, TimeUnit.SECONDS); LOGGER.info("CityServiceImpl.findCityById() : 城市插入缓存 >> " + city.toString()); return city; } @Override public Long saveCity(City city) { return cityDao.saveCity(city); } /** * 更新城市逻辑: * 如果缓存存在,删除 * 如果缓存不存在,不操作 */ @Override public Long updateCity(City city) { Long ret = cityDao.updateCity(city); // 缓存存在,删除缓存 String key = "city_" + city.getId(); boolean hasKey = redisTemplate.hasKey(key); if (hasKey) { redisTemplate.delete(key); LOGGER.info("CityServiceImpl.updateCity() : 从缓存中删除城市 >> " + city.toString()); } return ret; } @Override public Long deleteCity(Long id) { Long ret = cityDao.deleteCity(id); // 缓存存在,删除缓存 String key = "city_" + id; boolean hasKey = redisTemplate.hasKey(key); if (hasKey) { redisTemplate.delete(key); LOGGER.info("CityServiceImpl.deleteCity() : 从缓存中删除城市 ID >> " + id); } return ret; } }
再来试试单元测试:SpringCacheTest(redis)
package com.cbitedu.springboot.web;
import com.cbitedu.springboot.entity.City;
import com.cbitedu.springboot.service.CityService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringCacheTest {
private static final Logger logger = LoggerFactory.getLogger(SpringCacheTest.class);
@Autowired
CityService cityService;
@Test
public void contextLoads() {
}
@Test
public void TestInsertCity() {
City city=new City();
city.setCityName("长沙");
city.setDescription("中部城市");
city.setProvinceId(1l);
cityService.saveCity(city);
}
@Test
public void TestSelectCity() {
City city=new City();
city=cityService.findCityById(1l);
logger.info("第一次查询"+city.toString());
city=cityService.findCityById(1l);
logger.info("第二次查询"+city.toString());
}
}
执行测试输出可以得到:
可以看到:
- 第一行输出的CacheManager type为org.springframework.data.redis.cache.RedisCacheManager,执行了数据库查询。
- 第二次查询的时候,没有输出SQL语句,所以是走的Redis缓存获取
整合成功!
实验实训
1、场景预设-秒杀系统如何实现
缓存选型
在项目中使用redis做为缓存,还没有使用memcache,考虑因素主要有两点:
1.redis丰富的数据结构,其hash,list,set以及功能丰富的String的支持,对于实际项目中的使用有很大的帮忙。(可参考官网redis.io)
2.redis单点的性能也非常高效(利用项目中的数据测试优于memcache).