上一篇,我们已经讲过了,在Windows-64位系统下的redis3.0环境的搭建,其实很简单,就是一个解压缩文件的时间加上鼠标click几下的功夫就可以嗨皮的使用redis了,任何技术都是服务于应用的,没有应用场景,技术也敢叫技术?因此,本篇将结合上一篇,利用Spring-Boot框架,集成mybatis(数据操作用mybatis的通用mapper)+redis(数据缓存)来实现一个简单的中等数据量的查询且如何做到查询的优化以及减少数据库open session的开销。
本篇有点长,因为贴代码很占篇幅。
一、项目目录结构图
demo资源会在文章最后,放在GitHub上
二、Pom依赖
pom.xml(默认是jar包,习惯性,我打成war包)
4.0.0
com.appleyk
spring-boot-redis
0.0.1-SNAPSHOT
war
spring-boot 集成redis,并利用AOP切面(切注解)的方式实现数据缓存操作
org.springframework.boot
spring-boot-starter-parent
1.5.9.RELEASE
1.8
org.springframework.boot
spring-boot-starter-web
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.1.1
com.alibaba
fastjson
1.2.41
com.fasterxml.jackson.core
jackson-core
mysql
mysql-connector-java
com.github.pagehelper
pagehelper-spring-boot-starter
1.1.1
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-devtools
true
junit
junit
tk.mybatis
mapper-spring-boot-starter
1.1.5
org.springframework.boot
spring-boot-starter-aop
com.alibaba
druid
1.0.20
org.springframework.boot
spring-boot-starter-redis
1.3.2.RELEASE
src/main/resources
src/main/resources
**/*.properties
**/*.xml
false
src/main/java
**/*.properties
**/*.xml
false
org.springframework.boot
spring-boot-maven-plugin
org.apache.maven.plugins
maven-surefire-plugin
**/*Documentation.java
三、资源文件
(1)
(2)xxx.properties
application-dev.properties
#####开发环境
server.port=8081
server.session.timeout=10
server.tomcat.uri-encoding=utf8
#随机字符串
appleyk.name = ${random.value}
#随机整数
appleyk.age = ${random.int}
#10以内的随机数
appleyk.size = ${random.int(10)}
spring.datasource.max-idle=10
spring.datasource.max-wait=10000
spring.datasource.min-idle=5
spring.datasource.initial-size=5
######MySql连接参数#############
jdbc.url=jdbc\:mysql\://localhost\:3306/test?useUnicode\=true&autoReconnect=true&useSSL=false&characterEncoding\=utf-8&useSSL=true
jdbc.username=root
jdbc.password=root
jdbc.driverClassName=com.mysql.jdbc.Driver
application-prod.properties
#####生产环境
server.port=8088
server.session.timeout=10
server.tomcat.uri-encoding=utf8
#随机字符串
appleyk.name = ${random.value}
#随机整数
appleyk.age = ${random.int}
#10以内的随机数
appleyk.size = ${random.int(10)}
# 主数据源,默认的
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc\:mysql\://localhost\:3306/taotao?useUnicode\=true&autoReconnect=true&useSSL=false&characterEncoding\=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=root
## Redis缓存
spring.cache.type=REDIS
spring.redis.database=0
spring.redis.host=localhost
spring.redis.password=
spring.redis.port=6379
spring.redis.pool.max-idle=8
spring.redis.pool.min-idle=0
spring.redis.pool.max-active=100
spring.redis.pool.max-wait=-1
#是否开启redis缓存
spring.redis.cache.on = true
# 下面为连接池的补充设置,应用到上面所有数据源中
# 初始化大小,最小,最大
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
# 配置获取连接等待超时的时间
spring.datasource.maxWait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=true
spring.datasource.testOnReturn=false
#在application.properties文件中引入日志配置文件
#===================================== log =============================
logging.config=classpath:logback-boot.xml
application-test.properties
#####测试环境
server.port=8082
server.session.timeout=10
server.tomcat.uri-encoding=utf8
spring.datasource.max-idle=10
spring.datasource.max-wait=10000
spring.datasource.min-idle=5
spring.datasource.initial-size=5
######MySql连接参数#############
jdbc.url=jdbc\:mysql\://localhost\:3306/taotao?useUnicode\=true&autoReconnect=true&useSSL=false&characterEncoding\=utf-8&useSSL=true
jdbc.username=root
jdbc.password=root
jdbc.driverClassName=com.mysql.jdbc.Driver
application.properties
#SpringApplication将从以下位置加载application.properties文件, 并把它们添加到Spring Environment中:
#1. 当前目录下的一个/config子目录
#2. 当前目录
#3. 一个classpath下的/config包
#4. classpath根路径(root)
#这个列表是按优先级排序的(列表中位置高的将覆盖位置低的) 。
#注:你可以使用YAML('.yml') 文件替代'.properties'
#Spring-Boot多环境配置 -- prod:生产环境
spring.profiles.active = prod
logback-boot.xml(配置日志,控制台和日志记录文件的权限调成error级别,屏蔽掉info和debug信息)
%d %p (%file:%line\)- %m%n
UTF-8
opt/spring-boot-web/logs/sys.log
log/sys.%d.%i.log
30
10MB
%d %p (%file:%line\)- %m%n
UTF-8
四、annotation(缓存注解)
(1)
(2)CacheNameSpace.java
package com.appleyk.result;
/**
* 缓存key的拼接前缀
* @author [email protected]
* @blob http://blog.csdn.net/appleyk
* @date 2018年3月1日-上午11:22:06
*/
public enum CacheNameSpace {
ITEM
}
(2)DeleteCache.java
package com.appleyk.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.appleyk.result.CacheNameSpace;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DeleteCache {
CacheNameSpace nameSpace();
}
(3)QueryCache.java
package com.appleyk.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.appleyk.result.CacheNameSpace;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface QueryCache{
CacheNameSpace nameSpace();
}
(4)QueryCacheKey.java
package com.appleyk.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 注解 QueryCacheKey 是参数级别的注解,用来标注要查询数据的主键,会和上面的nameSpace组合做缓存的key值
* @author [email protected]
* @blob http://blog.csdn.net/appleyk
* @date 2018年2月28日-下午2:01:52
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
@Documented
public @interface QueryCacheKey {
}
关于缓存注解的使用,可以先看一张图(提前放送)
方法加了注解,接下来,如何进行数据缓存操作呢? 别急,AOP切面编程马上来帮忙
五、AOP(本篇精髓所在)
(1)
(2)先来看第一个,ControllerInterceptor.java
package com.appleyk.aop;
import java.util.HashMap;
import java.util.Map;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
@Aspect
@Component
/**
* 拦截器
* @author [email protected]
* @blob http://blog.csdn.net/appleyk
* @date 2018年3月1日-下午1:04:56
* 开启对AspectJ自动代理技术
*
* boolean proxyTargetClass() default false;
描述:启用cglib代理,proxyTargetClass默认为false。
boolean exposeProxy() default false;
描述:如果在一个方法中,调用内部方法,需要在调用内部方法时也能够进行代理,比如内部调用时,使用
(IService)AopContext.currentProxy().sayHello(),需要将exposeProx设置为true,默认为false。
*/
@EnableAspectJAutoProxy(exposeProxy=true,proxyTargetClass=true)
public class ControllerInterceptor {
static Logger logger = LoggerFactory.getLogger(ControllerInterceptor.class);
//ThreadLocal 维护变量 避免同步
//ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
ThreadLocal startTime = new ThreadLocal<>();// 开始时间
/**
* map1存放方法被调用的次数O
*/
ThreadLocal
以上功能有两个,一个是环绕切面,根据切点表达式:execution(* com.appleyk.*..*(..))我们可以看出来,方法
比如,我们放开日志的权限,调成info级别如下
当我们启动Spring-Boot项目的时候,我们的第一个切面方法doAround就起作用了(方法分析统计),效果如下:
如果我们把logback的日志输出级别调成:error
则我们关掉项目,再次启动的时候,就看不到之前上面的切面输出的效果了(因为error级别比info高,info不会出现)
以上说明,logback日志权限级别不是乱设置的,是有讲究的,哈哈,我们继续
上面是第一个切点的编程,第二是什么呢?
上述图中注释说明的很清楚了,两个注解,一个是前置通知@Before,在方法调用前,记录开始时间,一个是方法执行后的通知@AfterReturning,这个实现方法的效率统计,比如xxx方法总共被调用了多少次、耗费了多少时间(毫秒),效果如下:
我们继续看另一个AOP编程,本篇的重点
(3)RedisCacheAspect.java
package com.appleyk.aop;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import com.appleyk.annotation.DeleteCache;
import com.appleyk.annotation.QueryCache;
import com.appleyk.annotation.QueryCacheKey;
import com.appleyk.result.CacheNameSpace;
@Aspect
@Component
/**
* 利用AOP配合注解,实现redis缓存的写入和删除
* @author [email protected]
* @blob http://blog.csdn.net/appleyk
* @date 2018年3月1日-下午1:35:30
*/
public class RedisCacheAspect {
static Logger logger = LoggerFactory.getLogger(ControllerInterceptor.class);
@Autowired
private RedisTemplate redisTemplate;
/**
* 是否开启redis缓存,将查询的结果写入value
*/
@Value("${spring.redis.cache.on}")
private boolean isOn;
/**
* 定义拦截规则:拦截所有@QueryCache注解的方法 -- 查询。
*/
@Pointcut("@annotation(com.appleyk.annotation.QueryCache)")
public void queryCachePointcut() {
}
/**
* 拦截器具体实现
*
* @param point
* @return
* @throws Throwable
*/
@Around("queryCachePointcut()")
public Object InterceptorByQuery(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
if (!isOn) {
// 如果不开启redis缓存的话,直接走原方法进行查询
Object object = point.proceed();
return object;
}
System.err.println("AOP 缓存切面处理 >>>> start ");
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod(); // 获取被拦截的方法
// 拿到方法上标注的注解的namespace的值
CacheNameSpace cacheType = method.getAnnotation(QueryCache.class).nameSpace();
String key = null;
int i = 0;
// 循环所有的参数
for (Object value : point.getArgs()) {
MethodParameter methodParam = new SynthesizingMethodParameter(method, i);
Annotation[] paramAnns = methodParam.getParameterAnnotations();
// 循环参数上所有的注解
for (Annotation paramAnn : paramAnns) {
if (paramAnn instanceof QueryCacheKey) { //
QueryCacheKey requestParam = (QueryCacheKey) paramAnn;
key = cacheType.name() + "_" + value; // 取到QueryCacheKey的标识参数的值
}
}
i++;
}
/**
* 如果没有参数的话,设置Key值
*/
if (key == null) {
key = cacheType.name();
}
// 获取不到key值,抛异常
if (StringUtils.isEmpty(key))
throw new Exception("缓存key值不存在");
ValueOperations operations = redisTemplate.opsForValue();
boolean hasKey = redisTemplate.hasKey(key);
if (hasKey) {
// 缓存中获取到数据,直接返回。
Object object = operations.get(key);
System.err.println("本次查询缓存命中,从缓存中获取到数据 >>>> key = " + key);
System.err.println("AOP 缓存切面处理 >>>> end 耗时:" + (System.currentTimeMillis() - beginTime) + "ms");
return object;
}
// 缓存中没有数据,调用原始方法查询数据库
Object object = point.proceed();
operations.set(key, object, 30, TimeUnit.SECONDS); // 设置超时时间30s
System.err.println("本次查询缓存未命中,DB取到数据并存入缓存 >>>> key =" + key);
System.err.println("AOP 缓存切面处理 >>>> end 耗时:" + (System.currentTimeMillis() - beginTime) + "ms");
// redisTemplate.delete(key);
return object;
}
/**
* 定义拦截规则:拦截所有@DeleteCache注解的方法 -- 用于修改表数据时,删除redis缓存中的key值。
* 也可以使用切面表达式execution(* com.appleyk.*..*(..)) 切和更新数据相关的方法
*/
@Pointcut("@annotation(com.appleyk.annotation.DeleteCache)")
public void deleteCachePointcut() {
}
/**
* 拦截器具体实现
*
* @param point
* @return
* @throws Throwable
*/
@Around("deleteCachePointcut()")
public Object InterceptorBySave(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
if (!isOn) {
// 如果不开启redis缓存的话,直接走原方法进行查询
Object object = point.proceed();
return object;
}
System.err.println("AOP 缓存切面处理 【清除key】>>>> start ");
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod(); // 获取被拦截的方法
// 拿到方法上标注的注解的namespace的值
CacheNameSpace cacheType = method.getAnnotation(DeleteCache.class).nameSpace();
String key = cacheType.name();
ValueOperations operations = redisTemplate.opsForValue();
boolean hasKey = redisTemplate.hasKey(key);
if (hasKey) {
System.err.println("key存在,执行删除 >>>> key = " + key);
/**
* 删除key
*/
redisTemplate.delete(key);
}
System.err.println("AOP 缓存切面处理 【清除key】>>>> end 耗时:" + (System.currentTimeMillis() - beginTime) + "ms");
Object object = point.proceed();
return object;
}
}
注意以下几点
A.
RedisTemplate -- spring 封装了 RedisTemplate 对象来进行对redis的各种操作,它支持所有的 redis 原生的 api。
这里我们可以大胆的使用@Autowired注入我们需要的RedisTemplate 对象,是因为我们在Pom文件中依赖了如下
而Spring-Boot启动的时候,也会将redis相关的资源添加到Spring容器中(至于资源是否存在,还有待验证)
我们可以看一下spring-boot-starter-redis的依赖树,如下
B.
#是否开启redis缓存
spring.redis.cache.on = true
C.
redis缓存key的生成策略(当然下面是简单的组成,实际应用中,key的值要比下面的复杂的多,本篇知道就行)
D.
缓存命中与否
E.
删除缓存(保证,在修改数据的时候,重刷相应redis缓存,避免缓存数据和实际DB数据不同步)
说完AOP,我们在来说一下,本篇的数据源配置
六、Mybatis数据源配置(Bean注入)
(1)
(2)MybatisConfig.java
package com.appleyk.config;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import com.github.pagehelper.PageInterceptor;
@Configuration
@EnableTransactionManagement//开启事务
@EnableConfigurationProperties(DataSourceProperties.class)
//扫描一切和Mapper有关的bean,因此,下面对整个项目进行"全身"扫描
@MapperScan("com.appleyk")
public class MybatisConfig {
@Bean(name = "dataSource")
//Spring 允许我们通过 @Qualifier注释指定注入 Bean 的名称
@Qualifier(value = "dataSource")
@ConfigurationProperties(prefix="spring.datasource")
@Primary
public DataSource dataSource()
{
return DataSourceBuilder.create().build();
}
//创建SqlSessionFactory
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactoryBean(@Qualifier("dataSource") DataSource dataSource){
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
//1.设置数据源
bean.setDataSource(dataSource);
//2.给包中的类注册别名,注册后可以直接使用类名,而不用使用全限定的类名(就是不用包含包名)
bean.setTypeAliasesPackage("com.appleyk.database");
// 设置MyBatis分页插件 【PageHelper 5.0.1设置方法】
PageInterceptor pageInterceptor = new PageInterceptor();
Properties properties = new Properties();
properties.setProperty("helperDialect", "mysql");
properties.setProperty("offsetAsPageNum", "true");
properties.setProperty("rowBoundsWithCount", "true");
pageInterceptor.setProperties(properties);
//添加插件
bean.setPlugins(new Interceptor[]{pageInterceptor});
//添加XML目录,进行Mapper.xml扫描
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
//项目中的xxxMapper.xml位于包com.appleyk.mapepr下面
bean.setMapperLocations(resolver.getResources("classpath*:com/appleyk/mapepr/*.xml"));
return bean.getObject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
//创建SqlSessionTemplate
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean(name = "transactionManager")
@Primary
public DataSourceTransactionManager testTransactionManager(@Qualifier("dataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
这个没什么好说的,不懂的地方,留言备注
七、MySql数据示例
(1)使用taotao数据库里面的tb_item表里的数据作为本篇的查询依据
(2)没有的,可以自己新建一个表,批量插入XXX条数据,扩展自己的查询数据集
八、使用Mybatis通用mapper完成DAO层的设计和编写
(1)依赖的包,在Pom文件中体现如下:
(2)tb_item表Java实体类映射
Tbitem.java
package com.appleyk.entity;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
import org.apache.ibatis.type.JdbcType;
import tk.mybatis.mapper.annotation.ColumnType;
@Table(name = "tb_item")
public class TbItem implements Serializable{
@Id
@Column(name = "id")
@ColumnType(jdbcType = JdbcType.BIGINT)
private Long id;
private String title;
private String sell_point;
private Long price;
private Integer num;
private String barcode;
private String image;
private Integer cid;
private Integer status;
private Date created;
private Date updated;
public TbItem(){
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getSell_point() {
return sell_point;
}
public void setSell_point(String sell_point) {
this.sell_point = sell_point;
}
public Long getPrice() {
return price;
}
public void setPrice(Long price) {
this.price = price;
}
public Integer getNum() {
return num;
}
public void setNum(Integer num) {
this.num = num;
}
public String getBarcode() {
return barcode;
}
public void setBarcode(String barcode) {
this.barcode = barcode;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public Date getUpdated() {
return updated;
}
public void setUpdated(Date updated) {
this.updated = updated;
}
}
需要注意的地方
其他的比葫芦画瓢,可以扩展自己的实体类,
(3)tb_item实体类对应的mapper操作
TbItemMapper.java
package com.appleyk.mapper;
import com.appleyk.entity.TbItem;
import tk.mybatis.mapper.common.Mapper;
public interface TbItemMapper extends Mapper{
}
什么叫通用mapper,也就是帮我们省去了基本的增删改查语句,无需配置mapepr.xml,无需我们写一句代码,mybatis就可以帮助我们实现tb_item这个表的简单数据操作(其实也不能说是简单,因为单表操作也就那回事,)
有了DAO层,接下来,我们就需要借助Service层来调用了
九、Service层(缓存注解的使用)
(1)
(2)TbItemService.java
package com.appleyk.service;
import java.util.List;
import com.appleyk.entity.TbItem;
public interface TbItemService {
/**
* 查询全部商品
* @return
*/
List GetTbItems();
/**
* 根据商品ID查询
* @param id
* @return
*/
TbItem GetTbItem(Long id);
/**
* 保存商品
* @param tbItem
* @return
*/
boolean SaveTbItems(TbItem tbItem);
}
(3)TbItemServiceImpl.java
package com.appleyk.service.Impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import com.appleyk.annotation.DeleteCache;
import com.appleyk.annotation.QueryCache;
import com.appleyk.entity.TbItem;
import com.appleyk.mapper.TbItemMapper;
import com.appleyk.result.CacheNameSpace;
import com.appleyk.service.TbItemService;
import tk.mybatis.mapper.entity.Example;
@Service
@Primary
public class TbItemServiceImpl implements TbItemService {
@Autowired
private TbItemMapper tbItemMapper ;
/**
*
* 获取商品: 如果缓存存在,从缓存中获取商品信息 如果缓存不存在,从DB中获取商品信息,然后插入缓存
*
*/
@Override
@QueryCache(nameSpace = CacheNameSpace.ITEM)
public List GetTbItems() {
Example example = new Example(TbItem.class);
List tbItems = tbItemMapper.selectByExample(example);
return tbItems;
}
@Override
public TbItem GetTbItem(Long id) {
//直接根据主键返回商品实体
return tbItemMapper.selectByPrimaryKey(id);
}
@Override
@DeleteCache(nameSpace = CacheNameSpace.ITEM)
public boolean SaveTbItems(TbItem tbItem) {
/**
* 这里不做操作,只是模拟,到这一步的时候,切面执行删除查询的时候写入缓存中的key
*/
return true;
}
}
这个没什么好说的,就是两个查询,和一个没有实现保存效果的方法(写代码很累的.....)
但是别小看他们,他们可是加了缓存注解的,你要知道,我们上面再讲AOP的时候,可提到过,有一个AOP编程切的就是带有这个缓存注解的方法,从而实现redis缓存操作的,不信,我们继续往下走,Service层有了,该轮到我们的Controller层了
十、Controller层(提供Restful API风格的接口)
(1)
(1)TbItemController.java
package com.appleyk.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.appleyk.entity.TbItem;
import com.appleyk.result.ResponseMessage;
import com.appleyk.result.ResponseResult;
import com.appleyk.service.TbItemService;
@RestController
@RequestMapping("/rest/v1.0.1/database/tbitem")
public class TbItemController {
@Autowired
private TbItemService itemService;
@GetMapping("/query")
public ResponseResult GetTbItems() {
List result = itemService.GetTbItems();
return new ResponseResult(200, "查询成功,size = " + result.size(), result);
}
@PostMapping("/save")
public ResponseResult SaveTbItem() {
TbItem tbItem = new TbItem();
if (itemService.SaveTbItems(tbItem)) {
return new ResponseResult(ResponseMessage.OK);
}
return new ResponseResult(ResponseMessage.INTERNAL_SERVER_ERROR);
}
}
这个也没有上面好说的,万事俱备,只欠东风!
接下来,我们实际演示一下,走个调用
十一、Spring-Boot启动(run)
(1)前提一定要保证,本机的redis-server是开着的
十二、API专业测试工具Insomnia的使用
(1)先来个查询的
第一次查询,我们后台AOP切入的结果输出:
第二次查询,我们后台AOP切入的结果输出:
第三次查询,我们后台AOP切入的结果输出:
如果我们在第一次查询数据的时候,紧接着来了一个保存商品信息的操作,会看到如下效果输出(具体调用不在放出)
十三、关闭缓存支持
(1)
(2)效果如下
项目GitHUb资源链接:https://github.com/kobeyk/appleyk-spring-boot.git