網頁

2020/7/24

Spring Boot Ehcache 3 JSR-107 範例

Spring Boot Ehcache 3 JSR-107的簡單配置方法如下。

JSR-107(JCache)是Java的快取標準規格,僅提供標準的操作方法及API,並無快取機制的實作;而Ehache 3則是提供JSR-107的實作,又叫做Caching Provider。

範例環境:

  • Spring Boot版本2.2.1.RELEASE
  • Maven
  • Lombok

建立Spring Boot專案(參考IntelliJ IDEAEclipse STS)。

把下列快取相關依賴引入專案的pom.xml


pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.abc</groupId>
    <artifactId>spring-boot-cache-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-cache-demo</name>
    <description>Spring Boot Caching Demo</description>
    <packaging>jar</packaging>

    <properties>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <!-- 快取相關依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.ehcache</groupId>
            <artifactId>ehcache</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.cache</groupId>
            <artifactId>cache-api</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.properties設定spring.cache.jcache.config=classpath:ehcache.xml,也就是使用Ehcache配置檔ehcache.xml作為JCache的配置。

application.properties

#context path
server.servlet.context-path=/demo
#port
server.port=8080

#cache
spring.cache.jcache.config=classpath:ehcache.xml

範例的本機的應用程式路徑為http://localhost:8080/demo


在classpath根目錄src/main/resources新增ehcache.xml檔設定如下。

ehcache.xml

<config
        xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
        xmlns='http://www.ehcache.org/v3'>

    <cache alias="employee_cache"> <!-- cache的名稱為"employee_cache" -->
        <expiry>
            <ttl unit="seconds">5</ttl> <!-- cache建立後只會存在5秒 -->
        </expiry>
        <resources>
            <heap unit="entries">2000</heap> <!-- heap可存2000個快取物件 -->
            <offheap unit="MB">100</offheap> <!-- heap外空間可存100MB的快取 -->
        </resources>
    </cache>

</config>

參考XML配置說明


@Configuration類別名稱前加上@EnableCaching啟用快取機制。範例是加在@SpringBootApplication類別DemoApplication

DemoApplication

package com.abc.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@EnableCaching // 啟用快取
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

建立DemoController提供對外API接收外部請求,並轉呼叫EmployeService取得資源。

DemoController

package com.abc.demo.controller;

import com.abc.demo.model.Employee;
import com.abc.demo.service.EmployeeService;
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.RestController;

@RestController
public class DemoController {

    @Autowired
    private EmployeeService employeeService;

    @GetMapping("/employee/{id}")
    public Employee getEmployee(@PathVariable Long id) {
        return employeeService.getEmployeeById(id);
    }

}

Employee為被檢索的資料物件,也就是被快取的對象。注意快取除了放在記憶體中,也可能放在系統外如Redis硬碟,所以快取對象必須是可序列化的,也就是要實作Serializable,否則會拋出org.ehcache.spi.resilience.StoreAccessException: org.ehcache.spi.serialization.SerializerException: java.io.NotSerializableException錯誤。

Employee

package com.abc.demo.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee implements Serializable { // 序列化

    private Long id;
    private String name;
    private Integer age;

}

EmployeeService.getEmployeeById()方法用來查詢資料庫的員工資訊,這邊以簡單的Map物件模擬現有的員工資料。加上@Cacheable對此方法做快取,以相同的查訊條件再次呼叫getEmployeeById()時會從快取回傳先前的查詢結果。

@CacheablecacheNames屬性用來指定快取名稱,也就是ehcache.xml中設定的<cache alias="employee_cache">

EmployeeService

package com.abc.demo.service;

import com.abc.demo.model.Employee;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class EmployeeService {

    @Cacheable(cacheNames = "employee_cache")
    public Employee getEmployeeById(Long id) {
        Map<Long, Employee> currentEmployeeMap = getCurrentEmployeeMap();
        return currentEmployeeMap.get(id);
    }

    // 模擬資料庫的資料
    private Map<Long, Employee> getCurrentEmployeeMap() {
        Map<Long, Employee> currentEmployeeMap = new HashMap<>();
        currentEmployeeMap.put(1L, Employee.builder().id(1L).name("alan").age(33).build());
        currentEmployeeMap.put(2L, Employee.builder().id(2L).name("bill").age(45).build());
        currentEmployeeMap.put(3L, Employee.builder().id(3L).name("carl").age(24).build());
        currentEmployeeMap.put(4L, Employee.builder().id(4L).name("dave").age(31).build());
        return currentEmployeeMap;
    }

}

上面的EmployeeService.getEmployeeById()使用的@Cacheable是Spring的快取注釋;此外也可改用JSR-107的@CacheResult也可達到相同的快取效果(注意屬性名稱不同)。

@CacheResult(cacheName = "employee_cache") // JSR-107 @CacheResult annotation
public Employee getEmployeeById(Long id) {
    Map<Long, Employee> currentEmployeeMap = getCurrentEmployeeMap();
    return currentEmployeeMap.get(id);
}

完成以上後在EmployeeService.getEmployeeById()中下中斷點,然後以debug模式啟動。

用Postman呼叫http://localhost:8080/demo/employee/1,可以發現第一次呼叫時會進入EmployeeService.getEmployeeById(),但第二次呼叫時就不會進入了,這就是快取的效果。若呼叫的間隔時間超過5秒則快取會被清除,一樣會進入EmployeeService.getEmployeeById()


完整程式碼參考github


沒有留言:

張貼留言