
本文共 15588 字,大约阅读时间需要 51 分钟。
文章目录
准备
IDE
Spring Tool Suite 4Version: 4.4.0.RELEASE
JDK
$ java -versionjava version "1.8.0_241"Java(TM) SE Runtime Environment (build 1.8.0_241-b07)Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, mixed mode)
MySQL 数据库
建表语句以及预设数据:
CREATE TABLE `commodity` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '商品标识符', `name` varchar(16) NOT NULL COMMENT '商品名称', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';INSERT INTO `commodity` (id, name) VALUES (DEFAULT, '李子');INSERT INTO `commodity` (id, name) VALUES (DEFAULT, '栗子');INSERT INTO `commodity` (id, name) VALUES (DEFAULT, '梨子');
项目搭建
新建 Spring Starter Project:

修改 pom.xml 文件,引入 Druid、MyBatis Plus 等依赖:
4.0.0 org.springframework.boot spring-boot-starter-parent 2.3.4.RELEASE com.mk spring-boot-cache 1.0.0 spring-boot-cache 缓存 1.8 1.1.21 3.3.1.tmp org.springframework.boot spring-boot-starter-cache org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-devtools runtime true mysql mysql-connector-java runtime org.springframework.boot spring-boot-configuration-processor true org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.junit.vintage junit-vintage-engine com.alibaba druid-spring-boot-starter ${druid-spring-boot-starter.version} com.baomidou mybatis-plus-boot-starter ${mybatis-plus-boot-starter.version} org.springframework.boot spring-boot-maven-plugin
新建 application.yml 文件,配置 Druid、MyBatis-Plus 等参数:
server: port: 8080 servlet: session: timeout: 12hspring: datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.168.88.158:3306/spring-boot?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=true username: root password: 123456 initial-size: 5 min-idle: 5 max-active: 20 max-wait: 30000 # 获取连接等待超时的时间 time-between-eviction-runs-millis: 2000 # 间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 min-evictable-idle-time-millis: 600000 # 一个连接在池中最小生存的时间,单位是毫秒 max-evictable-idle-time-millis: 900000 validation-query: select '1' test-while-idle: true test-on-borrow: false test-on-return: false pool-prepared-statements: true max-open-prepared-statements: 20 max-pool-prepared-statement-per-connection-size: 20 filters: stat,wall,slf4j web-stat-filter: # WebStatFilter 配置 enabled: true url-pattern: /* exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' stat-view-servlet: # StatViewServlet 配置 enabled: true url-pattern: /druid/* reset-enable: false login-username: admin login-password: 123456# allow: 127.0.0.1# deny: 192.168.1.6# aop-patterns: # Spring 监控配置# - com.mk.service.* filter: stat: # 慢 SQL 记录 slow-sql-millis: 3000 log-slow-sql: truelogging: level: com.mk: DEBUGmybatis-plus:# mapper-locations:# - classpath:mapper/*Mapper.xml# global-config:# db-config:# id-type: AUTO # 数据库的表的主键生成策略 configuration: log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl # 日志 type-aliases-package: com.mk.pojo
新建 MyBatis-Plus 配置类:
package com.mk.configuration;import org.mybatis.spring.annotation.MapperScan;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.transaction.annotation.EnableTransactionManagement;import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;@Configuration@MapperScan(basePackages = { "com.mk.mapper" }) // 扫描 Mapper 接口@EnableTransactionManagement // 开启事务管理public class MyBatisPlusConfiguration { /** *分页拦截器 * @return */ @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); }}
启动类:
package com.mk;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class SpringBootCacheApplication { public static void main(String[] args) { SpringApplication.run(SpringBootCacheApplication.class, args); }}
商品表实体类:
package com.mk.pojo;import java.io.Serializable;import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.annotation.TableId;import com.baomidou.mybatisplus.extension.activerecord.Model;import lombok.Data;import lombok.EqualsAndHashCode;import lombok.experimental.Accessors;@Data@EqualsAndHashCode(callSuper = false)@Accessors(chain = true)public class Commodity extends Model{ private static final long serialVersionUID=1L; /** * 商品标识符 */ @TableId(value = "id", type = IdType.AUTO) private Integer id; /** * 商品名称 */ private String name; /** * 创建时间 */ private String createTime; /** * 更新时间 */ private String updateTime; @Override protected Serializable pkVal() { return this.id; }}
商品表 Mapper 接口及其映射文件:
package com.mk.mapper;import com.mk.pojo.Commodity;import com.baomidou.mybatisplus.core.mapper.BaseMapper;public interface CommodityMapper extends BaseMapper{ }
id, name, create_time, update_time
商品表服务接口及其实现类:
package com.mk.service;import com.mk.pojo.Commodity;import com.baomidou.mybatisplus.extension.service.IService;public interface CommodityService extends IService{ }
package com.mk.service.impl;import com.mk.pojo.Commodity;import com.mk.mapper.CommodityMapper;import com.mk.service.CommodityService;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import org.springframework.stereotype.Service;@Servicepublic class CommodityServiceImpl extends ServiceImplimplements CommodityService { }
商品表前端控制器:
package com.mk.controller;import java.time.LocalDateTime;import java.util.HashMap;import java.util.Map;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import com.mk.service.CommodityService;@RestController@RequestMapping("/commodity")public class CommodityController { @Autowired private CommodityService commodityService; /** *根据标识符查询 * @param id * @return */ @GetMapping("/{id}") public Map
getById(@PathVariable("id") Integer id) { Map result = new HashMap<>(); result.put("datetime", LocalDateTime.now()); result.put("data", this.commodityService.getById(id)); return result; }}
运行启动类,访问 ,若能返回预期的数据即可:
使用 @Cacheable 缓存数据
修改商品服务实现类,重写 getById
方法,添加 @Cacheable
注解,实现查询缓存或缓存数据:
// ... 其他,略import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service;@Servicepublic class CommodityServiceImpl extends ServiceImplimplements CommodityService { @Override @Cacheable(cacheNames = "commodity", key = "#id") // cacheNames 指定缓存容器的名称,一个缓存容器中可以有多条缓存数据。key 指定缓存数据的键值。 public Commodity getById(Serializable id) { return super.getById(id); }}
重启应用,清空控制台输出,多次访问 http://localhost:8080/commodity/1,观察控制台输出,可以看到只有第一次访问是查询了数据库,随后几次都没有,说明随后的查询是直接从缓存中获取数据,而非数据库:
2020-09-20 16:10:04.404 DEBUG 10644 --- [nio-8080-exec-1] c.mk.mapper.CommodityMapper.selectById : ==> Preparing: SELECT id,name,create_time,update_time FROM commodity WHERE id=? 2020-09-20 16:10:04.405 DEBUG 10644 --- [nio-8080-exec-1] c.mk.mapper.CommodityMapper.selectById : ==> Parameters: 1(Integer)2020-09-20 16:10:04.407 DEBUG 10644 --- [nio-8080-exec-1] c.mk.mapper.CommodityMapper.selectById : <== Total: 1
底层配置与实现(暂不分析)
线索:CacheAutoConfiguration
,SimpleCacheConfiguration
使用 @CachePut 更新缓存数据
在没有使用 @CachePut
的情况下更新数据库中的数据,是不会同步更新到缓存中的。如果要实现同步更新到缓存,就需要使用 @CachePut
。
修改商品服务接口,添加 updateById
方法:
public interface CommodityService extends IService{ /** * 根据标识符更新 * @param id * @param entity * @return * @throws Exception */ Commodity updateById(Serializable id, Commodity entity) throws Exception;}
修改商品服务实现类,实现 updateById
方法:
// ... 其他,略import org.springframework.cache.annotation.CachePut;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service;@Servicepublic class CommodityServiceImpl extends ServiceImplimplements CommodityService { // ... 其他,略 @Override @CachePut(cacheNames = "commodity", key = "#id") // @CachePut 在方法执行完成之后起作用,其使用方法的返回值做为缓存数据,key 指定更新缓存容器中的那一条数据。 public Commodity updateById(Serializable id, Commodity entity) throws Exception { entity.setId((Integer) id); boolean result = super.updateById(entity); Commodity record = null; if (result) { record = super.getById(id); } else { throw new Exception("更新失败"); } return record; }}
修改商品前端控制器类,添加 updateById
方法:
// ... 其他,略@RestController@RequestMapping("/commodity")public class CommodityController { // ... 其他,略 /** *根据标识符更新数据 * @param entity * @return */ @PostMapping("") public Map
updateById(Commodity entity) { Map result = new HashMap<>(); result.put("datetime", LocalDateTime.now()); try { result.put("data", this.commodityService.updateById(entity.getId(), entity)); } catch (Exception e) { result.put("message", e.getMessage()); } return result; }}
重启应用,使用 Postman 向 发起一个 POST 请求,更新 id = 1 的数据:
更新成功之后,数据库表中的数据:
mysql> select * from commodity where id = 1;+----+--------+---------------------+---------------------+| id | name | create_time | update_time |+----+--------+---------------------+---------------------+| 1 | 苹果 | 2020-09-20 02:37:59 | 2020-09-20 09:50:30 |+----+--------+---------------------+---------------------+1 row in set (0.00 sec)
控制台返回的数据:
2020-09-20 17:50:46.070 DEBUG 11452 --- [nio-8080-exec-8] c.mk.mapper.CommodityMapper.updateById : ==> Preparing: UPDATE commodity SET name=? WHERE id=? 2020-09-20 17:50:46.071 DEBUG 11452 --- [nio-8080-exec-8] c.mk.mapper.CommodityMapper.updateById : ==> Parameters: 苹果(String), 1(Integer)2020-09-20 17:50:46.074 DEBUG 11452 --- [nio-8080-exec-8] c.mk.mapper.CommodityMapper.updateById : <== Updates: 12020-09-20 17:50:46.075 DEBUG 11452 --- [nio-8080-exec-8] c.mk.mapper.CommodityMapper.selectById : ==> Preparing: SELECT id,name,create_time,update_time FROM commodity WHERE id=? 2020-09-20 17:50:46.076 DEBUG 11452 --- [nio-8080-exec-8] c.mk.mapper.CommodityMapper.selectById : ==> Parameters: 1(Integer)2020-09-20 17:50:46.079 DEBUG 11452 --- [nio-8080-exec-8] c.mk.mapper.CommodityMapper.selectById : <== Total: 1
完成以上操作之后,清空控制台信息,访问 ,查询 id = 1 的数据:
观察控制台的输出,可以发现,控制台并没有任何输出,说明数据来自缓存,并且该数据与更新结果一致,说明更新的数据同步到缓存中。
使用 @CacheEvict 删除缓存数据
在没有使用 @CacheEvict
的情况下删除数据库中的数据,是不会同步删除缓存数据。如果要实现同步删除缓存数据,就需要使用 @CacheEvict
。
修改商品服务实现类,重写 removeById
方法,添加 @CacheEvict
注解,指定缓存容器和将要删除的缓存数据的键值:
// ... 其他,略import org.springframework.cache.annotation.CacheEvict;@Servicepublic class CommodityServiceImpl extends ServiceImplimplements CommodityService { // ... 其他,略 @Override @CacheEvict(cacheNames = "commodity", key = "#id") public boolean removeById(Serializable id) { return super.removeById(id); }}
修改商品前端控制器,添加 deleteById
方法:
// ... 其他,略@RestController@RequestMapping("/commodity")public class CommodityController { @Autowired private CommodityService commodityService; // ... 其他,略 /** *根据标识符删除 * @return */ @DeleteMapping("/{id}") public Map
deleteById(@PathVariable("id") Integer id) { Map result = new HashMap<>(); result.put("datetime", LocalDateTime.now()); result.put("data", this.commodityService.removeById(id)); return result; }}
重启应用,使用浏览器访问 ,使 id = 1 的数据被缓存。
使用 Postman 向 发起一个 DELETE 请求,删除 id = 1 的数据:
再次使用浏览器访问 ,可以看到返回的数据为空:
观察控制台输出的信息,可以看到 Spring Boot 在数据库查询数据,说明缓存中的数据已经被删除了。
2020-09-20 21:09:34.752 DEBUG 4824 --- [nio-8080-exec-8] c.mk.mapper.CommodityMapper.selectById : ==> Preparing: SELECT id,name,create_time,update_time FROM commodity WHERE id=? 2020-09-20 21:09:34.752 DEBUG 4824 --- [nio-8080-exec-8] c.mk.mapper.CommodityMapper.selectById : ==> Parameters: 1(Integer)2020-09-20 21:09:34.754 DEBUG 4824 --- [nio-8080-exec-8] c.mk.mapper.CommodityMapper.selectById : <== Total: 0
整合 Redis 缓存
整合 Redis 之前,需要准备 Redis 服务器,参考:
接着,在 pom.xml 文件引入以下依赖:
org.springframework.boot spring-boot-starter-data-redis org.apache.commons commons-pool2
修改 application.yml 配置文件,添加 Redis 配置:
spring: redis: database: 0 # Database index used by the connection factory. host: 192.168.88.158 # Redis server host. port: 6379 # Redis server port. password: 123456 # Login password of the redis server. timeout: 3000 # Connection timeout. lettuce: pool: max-active: 8 max-wait: 1 min-idle: 0 max-idle: 8debug: true
Spring Boot 默认使用 SimpleCacheConfiguration
配置,引入 Redis 依赖之后,Spring Boot 将使用 Redis 做为缓存,控制台将输出如下信息:
RedisCacheConfiguration matched: - @ConditionalOnClass found required class 'org.springframework.data.redis.connection.RedisConnectionFactory' (OnClassCondition) - Cache org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration automatic cache type (CacheCondition) - @ConditionalOnBean (types: org.springframework.data.redis.connection.RedisConnectionFactory; SearchStrategy: all) found bean 'redisConnectionFactory'; @ConditionalOnMissingBean (types: org.springframework.cache.CacheManager; SearchStrategy: all) did not find any beans (OnBeanCondition)
发表评论
最新留言
关于作者
