文件上传以及如何上传文件至七牛云平台。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<artifactId>spring-boot-demo-uploadartifactId>
<version>1.0.0-SNAPSHOTversion>
<packaging>jarpackaging>
<parent>
<groupId>com.xkcodinggroupId>
<artifactId>spring-boot-demoartifactId>
<version>1.0.0-SNAPSHOTversion>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
dependency>
<dependency>
<groupId>com.qiniugroupId>
<artifactId>qiniu-java-sdkartifactId>
<version>[7.2.0, 7.2.99]version>
dependency>
dependencies>
<build>
<finalName>spring-boot-demo-uploadfinalName>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
server:
port: 8080
servlet:
context-path: /demo
qiniu:
## 此处填写你自己的七牛云 access key
accessKey:
## 此处填写你自己的七牛云 secret key
secretKey:
## 此处填写你自己的七牛云 bucket
bucket:
## 此处填写你自己的七牛云 域名
prefix:
spring:
servlet:
multipart:
enabled: true
location: /Users/yangkai.shen/Documents/code/back-end/spring-boot-demo/spring-boot-demo-upload/tmp
file-size-threshold: 5MB
max-file-size: 20MB
@Configuration
@ConditionalOnClass({Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class})
@ConditionalOnProperty(prefix = "spring.http.multipart", name = "enabled", matchIfMissing = true)
@EnableConfigurationProperties(MultipartProperties.class)
public class UploadConfig {
@Value("${qiniu.accessKey}")
private String accessKey;
@Value("${qiniu.secretKey}")
private String secretKey;
@Autowired
private final MultipartProperties multipartProperties;
/**
* 上传配置
*/
@Bean
@ConditionalOnMissingBean
public MultipartConfigElement multipartConfigElement() {
return this.multipartProperties.createMultipartConfig();
}
/**
* 注册解析器
*/
@Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
@ConditionalOnMissingBean(MultipartResolver.class)
public StandardServletMultipartResolver multipartResolver() {
StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
return multipartResolver;
}
/**
* 华东机房
*/
@Bean
public com.qiniu.storage.Configuration qiniuConfig() {
return new com.qiniu.storage.Configuration(Zone.zone0());
}
/**
* 构建一个七牛上传工具实例
*/
@Bean
public UploadManager uploadManager() {
return new UploadManager(qiniuConfig());
}
/**
* 认证信息实例
*/
@Bean
public Auth auth() {
return Auth.create(accessKey, secretKey);
}
/**
* 构建七牛空间管理实例
*/
@Bean
public BucketManager bucketManager() {
return new BucketManager(auth(), qiniuConfig());
}
}
@RestController
@Slf4j
@RequestMapping("/upload")
public class UploadController {
@Value("${spring.servlet.multipart.location}")
private String fileTempPath;
@Value("${qiniu.prefix}")
private String prefix;
@Autowired
private final IQiNiuService qiNiuService;
@PostMapping(value = "/local", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Dict local(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return Dict.create().set("code", 400).set("message", "文件内容为空");
}
String fileName = file.getOriginalFilename();
String rawFileName = StrUtil.subBefore(fileName, ".", true);
String fileType = StrUtil.subAfter(fileName, ".", true);
String localFilePath = StrUtil.appendIfMissing(fileTempPath, "/") + rawFileName + "-" + DateUtil.current(false) + "." + fileType;
try {
file.transferTo(new File(localFilePath));
} catch (IOException e) {
log.error("【文件上传至本地】失败,绝对路径:{}", localFilePath);
return Dict.create().set("code", 500).set("message", "文件上传失败");
}
log.info("【文件上传至本地】绝对路径:{}", localFilePath);
return Dict.create().set("code", 200).set("message", "上传成功").set("data", Dict.create().set("fileName", fileName).set("filePath", localFilePath));
}
@PostMapping(value = "/yun", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Dict yun(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return Dict.create().set("code", 400).set("message", "文件内容为空");
}
String fileName = file.getOriginalFilename();
String rawFileName = StrUtil.subBefore(fileName, ".", true);
String fileType = StrUtil.subAfter(fileName, ".", true);
String localFilePath = StrUtil.appendIfMissing(fileTempPath, "/") + rawFileName + "-" + DateUtil.current(false) + "." + fileType;
try {
file.transferTo(new File(localFilePath));
Response response = qiNiuService.uploadFile(new File(localFilePath));
if (response.isOK()) {
JSONObject jsonObject = JSONUtil.parseObj(response.bodyString());
String yunFileName = jsonObject.getStr("key");
String yunFilePath = StrUtil.appendIfMissing(prefix, "/") + yunFileName;
FileUtil.del(new File(localFilePath));
log.info("【文件上传至七牛云】绝对路径:{}", yunFilePath);
return Dict.create().set("code", 200).set("message", "上传成功").set("data", Dict.create().set("fileName", yunFileName).set("filePath", yunFilePath));
} else {
log.error("【文件上传至七牛云】失败,{}", JSONUtil.toJsonStr(response));
FileUtil.del(new File(localFilePath));
return Dict.create().set("code", 500).set("message", "文件上传失败");
}
} catch (IOException e) {
log.error("【文件上传至七牛云】失败,绝对路径:{}", localFilePath);
return Dict.create().set("code", 500).set("message", "文件上传失败");
}
}
}
@Service
@Slf4j
public class QiNiuServiceImpl implements IQiNiuService, InitializingBean {
private final UploadManager uploadManager;
private final Auth auth;
@Value("${qiniu.bucket}")
private String bucket;
private StringMap putPolicy;
@Autowired
public QiNiuServiceImpl(UploadManager uploadManager, Auth auth) {
this.uploadManager = uploadManager;
this.auth = auth;
}
/**
* 七牛云上传文件
*
* @param file 文件
* @return 七牛上传Response
* @throws QiniuException 七牛异常
*/
@Override
public Response uploadFile(File file) throws QiniuException {
Response response = this.uploadManager.put(file, file.getName(), getUploadToken());
int retry = 0;
while (response.needRetry() && retry < 3) {
response = this.uploadManager.put(file, file.getName(), getUploadToken());
retry++;
}
return response;
}
@Override
public void afterPropertiesSet() {
this.putPolicy = new StringMap();
putPolicy.put("returnBody", "{\"key\":\"$(key)\",\"hash\":\"$(etag)\",\"bucket\":\"$(bucket)\",\"width\":$(imageInfo.width), \"height\":${imageInfo.height}}");
}
/**
* 获取上传凭证
*
* @return 上传凭证
*/
private String getUploadToken() {
return this.auth.uploadToken(bucket, null, 3600, putPolicy);
}
}
doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>spring-boot-demo-uploadtitle>
<script src="https://cdn.bootcss.com/vue/2.5.17/vue.min.js">script>
<link href="https://cdn.bootcss.com/iview/3.1.4/styles/iview.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/iview/3.1.4/iview.min.js">script>
head>
<body>
<div id="app">
<Row :gutter="16" style="background:#eee;padding:10%">
<i-col span="12">
<Card style="height: 300px">
<p slot="title">
<Icon type="ios-cloud-upload">Icon>
本地上传
p>
<div style="text-align: center;">
<Upload
:before-upload="handleLocalUpload"
action="/demo/upload/local"
ref="localUploadRef"
:on-success="handleLocalSuccess"
:on-error="handleLocalError"
>
<i-button icon="ios-cloud-upload-outline">选择文件i-button>
Upload>
<i-button
type="primary"
@click="localUpload"
:loading="local.loadingStatus"
:disabled="!local.file">
{{ local.loadingStatus ? '本地文件上传中' : '本地上传' }}
i-button>
div>
<div>
<div v-if="local.log.status != 0">状态:{{local.log.message}}div>
<div v-if="local.log.status === 200">文件名:{{local.log.fileName}}div>
<div v-if="local.log.status === 200">文件路径:{{local.log.filePath}}div>
div>
Card>
i-col>
<i-col span="12">
<Card style="height: 300px;">
<p slot="title">
<Icon type="md-cloud-upload">Icon>
七牛云上传
p>
<div style="text-align: center;">
<Upload
:before-upload="handleYunUpload"
action="/demo/upload/yun"
ref="yunUploadRef"
:on-success="handleYunSuccess"
:on-error="handleYunError"
>
<i-button icon="ios-cloud-upload-outline">选择文件i-button>
Upload>
<i-button
type="primary"
@click="yunUpload"
:loading="yun.loadingStatus"
:disabled="!yun.file">
{{ yun.loadingStatus ? '七牛云文件上传中' : '七牛云上传' }}
i-button>
div>
<div>
<div v-if="yun.log.status != 0">状态:{{yun.log.message}}div>
<div v-if="yun.log.status === 200">文件名:{{yun.log.fileName}}div>
<div v-if="yun.log.status === 200">文件路径:{{yun.log.filePath}}div>
div>
Card>
i-col>
Row>
div>
<script>
new Vue({
el: '#app',
data: {
local: {
// 选择文件后,将 beforeUpload 返回的 file 保存在这里,后面会用到
file: null,
// 标记上传状态
loadingStatus: false,
log: {
status: 0,
message: "",
fileName: "",
filePath: ""
}
},
yun: {
// 选择文件后,将 beforeUpload 返回的 file 保存在这里,后面会用到
file: null,
// 标记上传状态
loadingStatus: false,
log: {
status: 0,
message: "",
fileName: "",
filePath: ""
}
}
},
methods: {
// beforeUpload 在返回 false 或 Promise 时,会停止自动上传,这里我们将选择好的文件 file 保存在 data里,并 return false
handleLocalUpload(file) {
this.local.file = file;
return false;
},
// 这里是手动上传,通过 $refs 获取到 Upload 实例,然后调用私有方法 .post(),把保存在 data 里的 file 上传。
// iView 的 Upload 组件在调用 .post() 方法时,就会继续上传了。
localUpload() {
this.local.loadingStatus = true; // 标记上传状态
this.$refs.localUploadRef.post(this.local.file);
},
// 上传成功后,清空 data 里的 file,并修改上传状态
handleLocalSuccess(response) {
this.local.file = null;
this.local.loadingStatus = false;
if (response.code === 200) {
this.$Message.success(response.message);
this.local.log.status = response.code;
this.local.log.message = response.message;
this.local.log.fileName = response.data.fileName;
this.local.log.filePath = response.data.filePath;
this.$refs.localUploadRef.clearFiles();
} else {
this.$Message.error(response.message);
this.local.log.status = response.code;
this.local.log.message = response.message;
}
},
// 上传失败后,清空 data 里的 file,并修改上传状态
handleLocalError() {
this.local.file = null;
this.local.loadingStatus = false;
this.$Message.error('上传失败');
},
// beforeUpload 在返回 false 或 Promise 时,会停止自动上传,这里我们将选择好的文件 file 保存在 data里,并 return false
handleYunUpload(file) {
this.yun.file = file;
return false;
},
// 这里是手动上传,通过 $refs 获取到 Upload 实例,然后调用私有方法 .post(),把保存在 data 里的 file 上传。
// iView 的 Upload 组件在调用 .post() 方法时,就会继续上传了。
yunUpload() {
this.yun.loadingStatus = true; // 标记上传状态
this.$refs.yunUploadRef.post(this.yun.file);
},
// 上传成功后,清空 data 里的 file,并修改上传状态
handleYunSuccess(response) {
this.yun.file = null;
this.yun.loadingStatus = false;
if (response.code === 200) {
this.$Message.success(response.message);
this.yun.log.status = response.code;
this.yun.log.message = response.message;
this.yun.log.fileName = response.data.fileName;
this.yun.log.filePath = response.data.filePath;
this.$refs.yunUploadRef.clearFiles();
} else {
this.$Message.error(response.message);
this.yun.log.status = response.code;
this.yun.log.message = response.message;
}
},
// 上传失败后,清空 data 里的 file,并修改上传状态
handleYunError() {
this.yun.file = null;
this.yun.loadingStatus = false;
this.$Message.error('上传失败');
}
}
})
script>
body>
html>
整合redis,操作redis中的数据,并使用redis缓存数据。连接池使用 Lettuce。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<artifactId>spring-boot-demo-cache-redisartifactId>
<version>1.0.0-SNAPSHOTversion>
<packaging>jarpackaging>
<parent>
<groupId>com.xkcodinggroupId>
<artifactId>spring-boot-demoartifactId>
<version>1.0.0-SNAPSHOTversion>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-pool2artifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jsonartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.google.guavagroupId>
<artifactId>guavaartifactId>
dependency>
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
dependencies>
<build>
<finalName>spring-boot-demo-cache-redisfinalName>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
spring:
redis:
host: localhost
# 连接超时时间(记得添加单位,Duration)
timeout: 10000ms
# Redis默认情况下有16个分片,这里配置具体使用的分片
# database: 0
lettuce:
pool:
# 连接池最大连接数(使用负值表示没有限制) 默认 8
max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
max-wait: -1ms
# 连接池中的最大空闲连接 默认 8
max-idle: 8
# 连接池中的最小空闲连接 默认 0
min-idle: 0
cache:
# 一般来说是不用配置的,Spring Cache 会根据依赖的包自行装配
type: redis
logging:
level:
com.xkcoding: debug
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@EnableCaching
public class RedisConfig {
/**
* 默认情况下的模板只能支持RedisTemplate,也就是只能存入字符串,因此支持序列化
*/
@Bean
public RedisTemplate<String, Serializable> redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Serializable> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}
/**
* 配置使用注解的时候缓存配置,默认是序列化反序列化的形式,加上此配置则为 json 形式
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
// 配置序列化
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory).cacheDefaults(redisCacheConfiguration).build();
}
}
@Service
@Slf4j
public class UserServiceImpl implements UserService {
/**
* 模拟数据库
*/
private static final Map<Long, User> DATABASES = Maps.newConcurrentMap();
/**
* 初始化数据
*/
static {
DATABASES.put(1L, new User(1L, "user1"));
DATABASES.put(2L, new User(2L, "user2"));
DATABASES.put(3L, new User(3L, "user3"));
}
/**
* 保存或修改用户
*
* @param user 用户对象
* @return 操作结果
*/
@CachePut(value = "user", key = "#user.id")
@Override
public User saveOrUpdate(User user) {
DATABASES.put(user.getId(), user);
log.info("保存用户【user】= {}", user);
return user;
}
/**
* 获取用户
*
* @param id key值
* @return 返回结果
*/
@Cacheable(value = "user", key = "#id")
@Override
public User get(Long id) {
// 我们假设从数据库读取
log.info("查询用户【id】= {}", id);
return DATABASES.get(id);
}
/**
* 删除
*
* @param id key值
*/
@CacheEvict(value = "user", key = "#id")
@Override
public void delete(Long id) {
DATABASES.remove(id);
log.info("删除用户【id】= {}", id);
}
}
主要测试使用
RedisTemplate
操作Redis
中的数据:
- opsForValue:对应 String(字符串)
- opsForZSet:对应 ZSet(有序集合)
- opsForHash:对应 Hash(哈希)
- opsForList:对应 List(列表)
- opsForSet:对应 Set(集合)
- opsForGeo:** 对应 GEO(地理位置)
@Slf4j
public class RedisTest extends SpringBootDemoCacheRedisApplicationTests {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedisTemplate<String, Serializable> redisCacheTemplate;
/**
* 测试 Redis 操作
*/
@Test
public void get() {
// 测试线程安全,程序结束查看redis中count的值是否为1000
ExecutorService executorService = Executors.newFixedThreadPool(1000);
IntStream.range(0, 1000).forEach(i -> executorService.execute(() -> stringRedisTemplate.opsForValue().increment("count", 1)));
stringRedisTemplate.opsForValue().set("k1", "v1");
String k1 = stringRedisTemplate.opsForValue().get("k1");
log.debug("【k1】= {}", k1);
// 以下演示整合,具体Redis命令可以参考官方文档
String key = "xkcoding:user:1";
redisCacheTemplate.opsForValue().set(key, new User(1L, "user1"));
// 对应 String(字符串)
User user = (User) redisCacheTemplate.opsForValue().get(key);
log.debug("【user】= {}", user);
}
}
@Slf4j
public class UserServiceTest extends SpringBootDemoCacheRedisApplicationTests {
@Autowired
private UserService userService;
/**
* 获取两次,查看日志验证缓存
*/
@Test
public void getTwice() {
// 模拟查询id为1的用户
User user1 = userService.get(1L);
log.debug("【user1】= {}", user1);
// 再次查询
User user2 = userService.get(1L);
log.debug("【user2】= {}", user2);
// 查看日志,只打印一次日志,证明缓存生效
}
/**
* 先存,再查询,查看日志验证缓存
*/
@Test
public void getAfterSave() {
userService.saveOrUpdate(new User(4L, "测试中文"));
User user = userService.get(4L);
log.debug("【user】= {}", user);
// 查看日志,只打印保存用户的日志,查询是未触发查询日志,因此缓存生效
}
/**
* 测试删除,查看redis是否存在缓存数据
*/
@Test
public void deleteUser() {
// 查询一次,使redis中存在缓存数据
userService.get(1L);
// 删除,查看redis是否存在缓存数据
userService.delete(1L);
}
}
集成 ehcache 使用缓存。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<artifactId>spring-boot-demo-cache-ehcacheartifactId>
<version>1.0.0-SNAPSHOTversion>
<packaging>jarpackaging>
<parent>
<groupId>com.xkcodinggroupId>
<artifactId>spring-boot-demoartifactId>
<version>1.0.0-SNAPSHOTversion>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
<dependency>
<groupId>net.sf.ehcachegroupId>
<artifactId>ehcacheartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>com.google.guavagroupId>
<artifactId>guavaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<finalName>spring-boot-demo-cache-ehcachefinalName>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
@SpringBootApplication
@EnableCaching
public class SpringBootDemoCacheEhcacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoCacheEhcacheApplication.class, args);
}
}
spring:
cache:
type: ehcache
ehcache:
config: classpath:ehcache.xml
logging:
level:
com.xkcoding: debug
<ehcache
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="user.home/base_ehcache"/>
<defaultCache
maxElementsInMemory="20000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"/>
<cache name="user"
maxElementsInMemory="20000"
eternal="true"
overflowToDisk="true"
diskPersistent="false"
timeToLiveSeconds="0"
diskExpiryThreadIntervalSeconds="120"/>
ehcache>
@Service
@Slf4j
public class UserServiceImpl implements UserService {
/**
* 模拟数据库
*/
private static final Map<Long, User> DATABASES = Maps.newConcurrentMap();
/**
* 初始化数据
*/
static {
DATABASES.put(1L, new User(1L, "user1"));
DATABASES.put(2L, new User(2L, "user2"));
DATABASES.put(3L, new User(3L, "user3"));
}
/**
* 保存或修改用户
*
* @param user 用户对象
* @return 操作结果
*/
@CachePut(value = "user", key = "#user.id")
@Override
public User saveOrUpdate(User user) {
DATABASES.put(user.getId(), user);
log.info("保存用户【user】= {}", user);
return user;
}
/**
* 获取用户
*
* @param id key值
* @return 返回结果
*/
@Cacheable(value = "user", key = "#id")
@Override
public User get(Long id) {
// 我们假设从数据库读取
log.info("查询用户【id】= {}", id);
return DATABASES.get(id);
}
/**
* 删除
*
* @param id key值
*/
@CacheEvict(value = "user", key = "#id")
@Override
public void delete(Long id) {
DATABASES.remove(id);
log.info("删除用户【id】= {}", id);
}
}
@Slf4j
public class UserServiceTest extends SpringBootDemoCacheEhcacheApplicationTests {
@Autowired
private UserService userService;
/**
* 获取两次,查看日志验证缓存
*/
@Test
public void getTwice() {
// 模拟查询id为1的用户
User user1 = userService.get(1L);
log.debug("【user1】= {}", user1);
// 再次查询
User user2 = userService.get(1L);
log.debug("【user2】= {}", user2);
// 查看日志,只打印一次日志,证明缓存生效
}
/**
* 先存,再查询,查看日志验证缓存
*/
@Test
public void getAfterSave() {
userService.saveOrUpdate(new User(4L, "user4"));
User user = userService.get(4L);
log.debug("【user】= {}", user);
// 查看日志,只打印保存用户的日志,查询是未触发查询日志,因此缓存生效
}
/**
* 测试删除,查看redis是否存在缓存数据
*/
@Test
public void deleteUser() {
// 查询一次,使ehcache中存在缓存数据
userService.get(1L);
// 删除,查看ehcache是否存在缓存数据
userService.delete(1L);
}
}