如何使用Spring Boot Caching

132 阅读6分钟

开始使用Spring Boot缓存

缓存是一种用于提高系统性能的机制。它作为应用程序和持久性数据库之间的一个临时存储器。

缓存内存只存储最近使用的数据项。这有助于尽可能地减少数据库的点击次数。

从内存中访问数据总是比从数据库、文件系统或其他服务调用等外部存储器中获取数据要快。

我们可以使用Hazelcast、Ehcache或Redis等技术在Spring Boot应用中设置缓存。它们是缓存提供者,因为它们提供了一个密钥数据来存储缓存数据。

在这篇文章中,我们将学习如何在Spring Boot REST应用程序中使用Ehcache作为缓存提供者实现缓存。我们将建立一个Todo REST APIs,然后在REST端点上添加缓存功能。

前提条件

  1. 在你的电脑上安装[JDK]。
  2. 对[Spring Boot]的了解。

项目设置

我们将使用Spring Initializr来启动我们的应用程序。

  1. 访问Spring Initializr,并输入项目名称为SpringBootCaching
  2. 添加Spring Web,H2 database,Spring data JPA, 和Lombok 作为项目依赖。
  3. 点击Generate Project 按钮,下载项目模板代码为zip 文件。
  4. 提取zip 文件,在你喜欢的IDE中打开未压缩的文件。
  5. pom.xml 文件中的依赖项部分,再添加一些依赖项,如下图所示。
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>

    <dependency>
        <groupId>javax.cache</groupId>
        <artifactId>cache-api</artifactId>
    </dependency>

    <dependency>
        <groupId>org.ehcache</groupId>
        <artifactId>ehcache</artifactId>
    </dependency>
</dependencies>

上述依赖项添加了spring-boot-starter-cache ,一个由Spring实现的用于缓存操作的抽象层和Ehcache ,作为我们将在应用程序中使用的缓存提供者。

领域层

  1. 在根文件夹中创建一个名为domain 的包。
  2. 创建一个名为Todo.java 的Java文件并添加以下代码。
@Entity // Makes this class to be a JPA entity
@Getter // Creates setters for all the fields in the class
@Setter // Creates getters for all the fields in the class
@Builder // Creates a builder pattern for this class
@NoArgsConstructor // Creates a constructor with no arguements for this class
@AllArgsConstructor // Creates a constructor with all arguements for this class
class Todo {
    @Id // Marks the Id as the primary key
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id", nullable = false)
    private Long id;

    @Column(name = "title", nullable = false)
    private String title;

    @Column(name = "description", nullable = false)
    private String description;

}

在上面的代码片段中,我们创建了一个Todo 模型,它将在数据库中持有一个todo 的实例。

存储库层

  1. 在根项目包中创建一个名为repository 的新包。
  2. 在上面创建的repository 包中创建一个名为TodoRepository 的新的Java接口。添加下面的代码片断。
public interface TodoRepository extends JpaRepository<Todo, Long> {
}

在上面的代码片断中,我们创建了一个TodoRepository 接口,该接口扩展了JpaRepository 接口的所有方法,以执行基本的CRUD数据库操作。

服务层

  1. 在项目根目录下创建一个名为service 的新包。
  2. 在上面创建的service 包中,创建一个名为TodoService.java 的新的Java接口。

将下面的代码片段添加到TodoService 接口中。

// Interface that will be implemented to provide database operations
public interface TodoService {
    List<Todo> getAllTodos();
    Todo getTodoById(Long id);
    Todo updateTodo(Todo todo, Long id);
    void createTodo(Todo todo);
    void deletedTodo(Long id);
}

service 包中,创建一个新的名为TodoServiceImpl.java 的Java文件,并添加下面的代码片断。

@Service
@AllArgsConstructor
public class TodoServiceImpl implements TodoService {
    private final TodoRepository todoRepository;
    // Gets a list of todos from the database
    @Override
    public List<Todo> getAllTodos() {
        return todoRepository.findAll();
    }
    // Gets a todo with the specified Id from the database
    @Override
    public Todo getTodoById(Long id) {
        return todoRepository.getById(id);
    }
    // Updates todo with the specified Id
    @Override
    public Todo updateTodo(Todo todo, Long id) {
        Todo newTodo = Todo
                .builder()
                .id(id)
                .description(todo.getDescription())
                .title(todo.getTitle())
                .build();
        todoRepository.save(newTodo);
        return newTodo;
    }
    // Creates a new todo in the database
    @Override
    public void createTodo(Todo todo) {
        todoRepository.save(todo);
    }
    // Deletes a todo with the specified Id in the database
    @Override
    public void deletedTodo(Long id) {
        todoRepository.deleteById(id);
    }
}

控制器层

  1. 在根项目包中创建一个名为controllers 的新包。
  2. controllers 包中,创建一个名为TodoController.java 的新文件。

TodoController.java 文件中添加下面的代码片断。

@RestController
@RequestMapping("/api/todos")
@AllArgsConstructor
public class TodoController {
    private final TodoService todoService;
    // API endpoint returning a list of Todos
    @GetMapping({"", "/"})
    public ResponseEntity<List<Todo>> getAllTodos() {
        return new ResponseEntity<>(todoService.getAllTodos(), HttpStatus.OK);
    }
    // API endpoint returning a todo with the specied Id
    @GetMapping("/{id}")
    public ResponseEntity<Todo> getTodoById(@PathVariable Long id) {
        return new ResponseEntity<>(todoService.getTodoById(id), HttpStatus.OK);
    }
    // API endpoint that creates a todo in the database with the request body
    @PostMapping("")
    public ResponseEntity<String> createTodo(Todo todo) {
        todoService.createTodo(todo);
        return new ResponseEntity<>("Todo Created successfully", HttpStatus.CREATED);
    }
    // API endpoint that allows for updating a Todo with a specified Id
    @PutMapping("{id}")
    public ResponseEntity<Todo> updateTodo(@PathVariable Long id, @RequestBody Todo todo) {
        return new ResponseEntity<>(todoService.updateTodo(todo, id), HttpStatus.OK);
    }
    // API endpoint that allows users to delete Todo wit specified Id
    @DeleteMapping("/{id}")
    public ResponseEntity<String> deleteTodoById(@PathVariable Long id) {
        todoService.deletedTodo(id);
        return new ResponseEntity<>("Todo deleted successfully", HttpStatus.NO_CONTENT);
    }
}

运行该应用程序,以确认一切都在按预期工作。

配置层

现在我们有了一个功能齐全的CRUD REST API应用程序,我们将学习如何在我们的应用程序中添加缓存支持。

首先,我们需要创建一个配置类来启用缓存。

  1. 在项目根包中,创建一个名为config 的新包。
  2. 在上面创建的config 包中,创建一个名为CacheConfig.java 的新的Java文件。

将下面的代码片段添加到CacheConfig.java 文件中。

@EnableCaching
@Configuration
public class CacheConfig {
}
  • @EnableCaching 注解在我们的应用程序中启用Spring Boot缓存抽象层。
  • @Configuration 注解将 类标记为Spring配置类。CacheConfig

Ehcache缓存提供者需要一个独立的配置才能正常运行。

在资源文件夹中,创建一个名为ehcache.xml 的新的XML文件,并添加下面的代码片断。

<config
    xmlns:jsr107='http://www.ehcache.org/v3/jsr107'
    xmlns='http://www.ehcache.org/v3'>
<service>
    <jsr107:defaults enable-management="true" enable-statistics="true"/>
</service>

<cache alias="todos" uses-template="config-cache"/>
<cache alias="todo" uses-template="config-cache"/>

<cache-template name="config-cache">
    <expiry>
        <ttl unit="minutes">5</ttl>
    </expiry>
    <resources>
        <heap>1</heap>
        <offheap unit="MB">1</offheap>
    </resources>
</cache-template>
</config>

在上面的代码片断中,我们设置了缓存内容更新所需的时间和缓存所使用的内存量。

我们需要将Ehcache配置文件的路径告知我们的应用程序。

为了确保XML被添加到我们的配置中,在application.properties 文件中添加以下一行。

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

缓存中的CRUD操作

将数据添加到缓存中

TodoServiceImpl.java 文件中,用下面的代码片断更新getAllTodos() 方法。

@Cacheable(cacheNames = "todos")
@Override
public List<Todo> getAllTodos() {
    return todoRepository.findAll();
}

上述方法返回一个todos 的列表。这里,它被注解为@Cacheable(cacheNames = "todos") 注解。这就抓取了从这个方法返回的数据,并将它们存储在一个键为todos 的缓存中。

缓存中的数据是以键值模式存储的。key是存储在cacheName变量中的名称,而value是由@Cacheable注解的方法返回的数据。

为了在缓存中添加一个单独的项目,我们需要添加id ,该方法将用于检索该项目并在缓存中更新它。

用下面的代码片断更新getTodoById() 方法。

@Cacheable(cacheNames = "todo", key = "#id")
@Override
public Todo getTodoById(Long id) {
  return todoRepository.getById(id);
}
  • @Cacheable(cacheNames = "todo", key = "#id") 从缓存中获得一个带有所提供的 的 。id todo

注意:我们把id 作为键来传递,因为缓存是以键值对的形式存储数据的。当我们传递todoid ,我们期待一个与该键相关的todo 数据。

更新缓存中的数据

TodoServiceImpl.java 文件中,用下面的代码片断更新updateTodo() 方法。

// Updates a todo with the specified Id in the cache and in the database. `#todo.id` gets the Id property from the `todo` passed to the `updateTodo` function. 
@CachePut(value = "todos", key = "#todo.id")
@Override
public Todo updateTodo(Todo todo, Long id) {
    Todo newTodo = Todo
    .builder()
    .id(id)
    .description(todo.getDescription())
    .title(todo.getTitle())
    .build();
    todoRepository.save(newTodo);
    return newTodo;
}

为了更新缓存中的数据,我们使用@CachePut 注释来更新项目的键。

从缓存中删除数据

TodoServiceImpl.java 文件中,用下面的代码片断更新deleteTodo()

@CacheEvict(value = "todo", key = "#id")
@Override
public void deletedTodo(Long id) {
    todoRepository.deleteById(id);
}

要从缓存中删除数据,我们使用@CacheEvict 注解,传递要删除的特定项目的键。

要从缓存中删除所有项目,我们将属性allEntries 设为true。

总结

基于我们所学到的,缓存减少了对数据库的读写操作的数量。在使用像Google Cloud SQL这样的付费云数据库的情况下,计费将基于对数据库的读写操作。

随着读写操作次数的减少,使用管理数据库的成本也会降低。