upload,cache-redis,cache-ehcache

spring-boot-demo-upload

文件上传以及如何上传文件至七牛云平台。

pom.xml


<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>

yml

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

UploadConfig.java

@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());
   }
}

UploadController.java

@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", "文件上传失败");
      }
   }
}

QiNiuServiceImpl.java

@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);
   }
}

index.html

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>

参考

  1. Spring 官方文档:https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#howto-multipart-file-upload-configuration
  2. 七牛云官方文档:https://developer.qiniu.com/kodo/sdk/1239/java#5

spring-boot-demo-cache-redis

整合redis,操作redis中的数据,并使用redis缓存数据。连接池使用 Lettuce。

pom.xml


<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>

application.yml

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

RedisConfig.java

@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();
    }
}

UserServiceImpl.java

@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);
    }
}

RedisTest.java

主要测试使用 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);
    }
}

UserServiceTest.java


@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);
    }

}

参考

  • spring-data-redis 官方文档:https://docs.spring.io/spring-data/redis/docs/2.0.1.RELEASE/reference/html/
  • redis 文档:https://redis.io/documentation
  • redis 中文文档:http://www.redis.cn/commands.html

spring-boot-demo-cache-ehcache

集成 ehcache 使用缓存。

pom.xml


<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>

SpringBootDemoCacheEhcacheApplication.java

@SpringBootApplication
@EnableCaching
public class SpringBootDemoCacheEhcacheApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoCacheEhcacheApplication.class, args);
    }
}

application.yml

spring:
  cache:
    type: ehcache
    ehcache:
      config: classpath:ehcache.xml
logging:
  level:
    com.xkcoding: debug

ehcache.xml


<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>

UserServiceImpl.java

@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);
    }
}

UserServiceTest.java

@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);
    }
}

参考

  • Ehcache 官网:http://www.ehcache.org/documentation/
  • Spring Boot 官方文档:https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#boot-features-caching-provider-ehcache2
  • 博客:https://juejin.im/post/5b308de9518825748b56ae1d

你可能感兴趣的:(redis,java,spring)