Bài 6: Caching trong Spring Data JPA
1. Giới thiệu về Caching
Caching là gì? Caching là một kỹ thuật tối ưu hóa hiệu suất bằng cách lưu trữ tạm thời các dữ liệu thường được truy cập để giảm thiểu số lần truy vấn trực tiếp đến cơ sở dữ liệu. Điều này giúp tăng tốc độ truy cập dữ liệu và giảm tải cho cơ sở dữ liệu.
Lợi ích của Caching
Tăng hiệu suất: Giảm thiểu thời gian truy xuất dữ liệu từ cơ sở dữ liệu bằng cách lấy dữ liệu từ cache.
Giảm tải cho cơ sở dữ liệu: Giảm số lượng truy vấn đến cơ sở dữ liệu, giúp giảm tải và tăng tuổi thọ của cơ sở dữ liệu.
Cải thiện trải nghiệm người dùng: Tăng tốc độ phản hồi của ứng dụng, cung cấp trải nghiệm người dùng mượt mà hơn.
Các loại Cache trong Spring Data JPA
First Level Cache: Được quản lý bởi Hibernate Session, mặc định được bật và không cần cấu hình.
Second Level Cache: Là cache toàn cục và có thể được chia sẻ giữa nhiều Hibernate Session. Cần được cấu hình và bật.
2. First Level Cache
First Level Cache là gì? First Level Cache là cache cấp session và được quản lý bởi Hibernate. Nó lưu trữ các entity trong phạm vi một Hibernate Session. Mỗi session sẽ có cache riêng biệt và không chia sẻ dữ liệu với các session khác.
Cách hoạt động của First Level Cache Khi bạn truy vấn một entity bằng cách sử dụng session.get() hoặc session.load(), Hibernate sẽ kiểm tra cache cấp session trước. Nếu entity đã tồn tại trong cache, Hibernate sẽ trả về entity từ cache mà không thực hiện truy vấn đến cơ sở dữ liệu.
Ví dụ cụ thể
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction transaction = session.beginTransaction();
User user1 = session.get(User.class, 1L); // Truy vấn từ cơ sở dữ liệu
User user2 = session.get(User.class, 1L); // Lấy từ cache của session
transaction.commit();
session.close();
Trong ví dụ này, truy vấn thứ hai không thực hiện truy vấn đến cơ sở dữ liệu mà lấy dữ liệu từ cache của session.
3. Second Level Cache
Second Level Cache là gì? Second Level Cache là cache toàn cục và có thể được chia sẻ giữa nhiều Hibernate Session. Nó cần được cấu hình và bật để sử dụng.
Cấu hình Second Level Cache Để cấu hình Second Level Cache, bạn cần làm theo các bước sau:
Bước 1: Thêm dependency cho caching provider Ví dụ, sử dụng EHCache:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>5.4.32.Final</version>
</dependency>
Bước 2: Cấu hình Hibernate để sử dụng Second Level Cache Thêm các thuộc tính cấu hình vào application.properties:
spring.jpa.properties.hibernate.cache.use_second_level_cache=true
spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory
spring.jpa.properties.hibernate.cache.use_query_cache=true
Bước 3: Cấu hình EHCache Tạo file cấu hình ehcache.xml trong src/main/resources:
<ehcache>
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"/>
<cache name="com.example.demo.entity.User"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"/>
</ehcache>
Bước 4: Đánh dấu các entity để sử dụng cache Sử dụng annotation @Cacheable và @Cache:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
@Entity
@Table(name = "users")
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
// Getters and Setters
}
Ví dụ cụ thể về sử dụng Second Level Cache
Session session1 = HibernateUtil.getSessionFactory().openSession();
Transaction transaction1 = session1.beginTransaction();
User user1 = session1.get(User.class, 1L); // Truy vấn từ cơ sở dữ liệu và lưu vào Second Level Cache
transaction1.commit();
session1.close();
Session session2 = HibernateUtil.getSessionFactory().openSession();
Transaction transaction2 = session2.beginTransaction();
User user2 = session2.get(User.class, 1L); // Lấy từ Second Level Cache
transaction2.commit();
session2.close();
4. Sử dụng annotation @Cacheable, @CachePut, @CacheEvict
Annotation @Cacheable Annotation @Cacheable được sử dụng để đánh dấu phương thức hoặc lớp sẽ sử dụng cache.
Ví dụ:
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
return userRepository.findById(id).orElse(null);
}
}
Trong ví dụ này, phương thức getUserById sẽ lưu kết quả vào cache với tên "users" và khóa là giá trị id.
Annotation @CachePut Annotation @CachePut được sử dụng để cập nhật cache mỗi khi phương thức được gọi.
Ví dụ:
import org.springframework.cache.annotation.CachePut;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
return userRepository.save(user);
}
}
Trong ví dụ này, phương thức updateUser sẽ cập nhật cache với tên "users" mỗi khi được gọi.
Annotation @CacheEvict Annotation @CacheEvict được sử dụng để xóa cache mỗi khi phương thức được gọi.
Ví dụ:
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
}
Trong ví dụ này, phương thức deleteUser sẽ xóa cache với tên "users" khi được gọi.
5. Ví dụ cụ thể về caching
Tạo Entity User với caching
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
@Entity
@Table(name = "users")
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
// Getters and Setters
}
Cấu hình caching trong application.properties
spring.jpa.properties.hibernate.cache.use_second_level_cache=true
spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory
spring.jpa.properties.hibernate.cache.use_query_cache=true
spring.cache.ehcache.config=classpath:ehcache.xml
Tạo Service với caching
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;
import com.example.demo.repository.UserRepository;
import com.example.demo.entity.User;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
return userRepository.findById(id).orElse(null);
}
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
return userRepository.save(user);
}
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
}
Tạo Controller
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
@RestController
@RequestMapping("/api/users")
public class UserController {
@
Autowired
private UserService userService;
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.getUserById(id);
if (user != null) {
return ResponseEntity.ok(user);
} else {
return ResponseEntity.notFound().build();
}
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User userDetails) {
userDetails.setId(id);
User updatedUser = userService.updateUser(userDetails);
if (updatedUser != null) {
return ResponseEntity.ok(updatedUser);
} else {
return ResponseEntity.notFound().build();
}
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.noContent().build();
}
}
Kết luận
Bài viết này đã giới thiệu chi tiết về caching trong Spring Data JPA, bao gồm First Level Cache, Second Level Cache, và cách sử dụng các annotation @Cacheable, @CachePut, @CacheEvict để quản lý cache. Những kiến thức này sẽ giúp bạn tối ưu hóa hiệu suất ứng dụng của mình bằng cách giảm thiểu truy vấn đến cơ sở dữ liệu và tăng tốc độ truy xuất dữ liệu. Các bài viết tiếp theo sẽ tiếp tục đi sâu vào các khía cạnh nâng cao hơn của Spring Data JPA như tối ưu hóa hiệu suất, xử lý các vấn đề phổ biến, và tích hợp với các công nghệ khác.