开始使用Spring Boot缓存
缓存是一种用于提高系统性能的机制。它作为应用程序和持久性数据库之间的一个临时存储器。
缓存内存只存储最近使用的数据项。这有助于尽可能地减少数据库的点击次数。
从内存中访问数据总是比从数据库、文件系统或其他服务调用等外部存储器中获取数据要快。
我们可以使用Hazelcast、Ehcache或Redis等技术在Spring Boot应用中设置缓存。它们是缓存提供者,因为它们提供了一个密钥数据来存储缓存数据。
在这篇文章中,我们将学习如何在Spring Boot REST应用程序中使用Ehcache作为缓存提供者实现缓存。我们将建立一个Todo REST APIs,然后在REST端点上添加缓存功能。
前提条件
- 在你的电脑上安装[JDK]。
- 对[Spring Boot]的了解。
项目设置
我们将使用Spring Initializr来启动我们的应用程序。
- 访问Spring Initializr,并输入项目名称为
SpringBootCaching。 - 添加
Spring Web,H2 database,Spring data JPA, 和Lombok作为项目依赖。 - 点击
Generate Project按钮,下载项目模板代码为zip文件。 - 提取
zip文件,在你喜欢的IDE中打开未压缩的文件。 - 在
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 ,作为我们将在应用程序中使用的缓存提供者。
领域层
- 在根文件夹中创建一个名为
domain的包。 - 创建一个名为
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 的实例。
存储库层
- 在根项目包中创建一个名为
repository的新包。 - 在上面创建的
repository包中创建一个名为TodoRepository的新的Java接口。添加下面的代码片断。
public interface TodoRepository extends JpaRepository<Todo, Long> {
}
在上面的代码片断中,我们创建了一个TodoRepository 接口,该接口扩展了JpaRepository 接口的所有方法,以执行基本的CRUD数据库操作。
服务层
- 在项目根目录下创建一个名为
service的新包。 - 在上面创建的
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);
}
}
控制器层
- 在根项目包中创建一个名为
controllers的新包。 - 在
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应用程序,我们将学习如何在我们的应用程序中添加缓存支持。
首先,我们需要创建一个配置类来启用缓存。
- 在项目根包中,创建一个名为
config的新包。 - 在上面创建的
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")从缓存中获得一个带有所提供的 的 。idtodo
注意:我们把
id作为键来传递,因为缓存是以键值对的形式存储数据的。当我们传递todo的id,我们期待一个与该键相关的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这样的付费云数据库的情况下,计费将基于对数据库的读写操作。
随着读写操作次数的减少,使用管理数据库的成本也会降低。