Một trong những lợi thế của abstraction layer cơ sở dữ liệu sử dụng ORM Framework là khả năng lưu trữ dữ liệu bộ nhớ cache một cách trong suốt từ kho lưu trữ bên dưới. Điều này giúp loại bỏ chi phí truy cập cơ sở dữ liệu cho dữ liệu thường xuyên truy cập. Nhờ vậy mà hiệu suất của ứng dụng tăng. Hiệu suất đạt được có thể là đáng kể nếu tỷ lệ đọc / ghi của nội dung được lưu trong bộ nhớ cache cao, đặc biệt đối với các Entity bao gồm các Entity Graph lớn.
Trong bài này chúng ta sẽ cùng tìm hiểu về Hibernate Cache để improve performance của ứng dụng.
Nội dung
Cache là gì?
Cache hay bộ nhớ đệm là cơ chế lưu các dữ liệu trong phiên làm việc trước của các ứng dụng, nhằm giúp việc tải data trong các phiên làm việc sau được nhanh hơn.
Lợi ích đến từ cache:
- Cải thiện tốc độ, các yêu cầu gần như có thể đáp ứng tức thời.
- Giảm thiểu băng thông, giảm thiểu hoạt động bị lặp lại nhiều lần không cần thiết.
- Tăng hiệu suất phần cứng, tăng hiệu suất, giảm thiểu các xử lý phải thông qua CPU.
- Đáp ứng lưu lượng truy cập lớn.
Bộ nhớ cache trong Hibernate
Hibernate Cache (Bộ nhớ đệm) là nhằm tối ưu hóa hiệu của suất ứng dụng. Nó nằm giữa ứng dụng và cơ sở dữ liệu để tránh số lượng lượt truy cập cơ sở dữ liệu càng nhiều càng tốt để cung cấp cho một hiệu suất tốt hơn cho các ứng dụng.
Các kiểu bộ nhớ cache trong Hibernate:
- First-level Cache – L1 Cache (Bộ nhớ cache cấp một) : bộ nhớ cache ở mức Session.
- Second-level Cache – L2 Cache (Bộ nhớ cache cấp hai) : bộ nhớ cache ở mức Session Factory.
- Query Cache (Bộ nhớ cache cấp truy vấn) : cache các kết quả truy vấn kết hợp chặt chẽ với L2 Cache.
First-level Cache – L1 Cache (Bộ nhớ cache cấp một)
L1 Cache là bộ nhớ cache Session và là một bộ nhớ cache bắt buộc thông qua đó tất cả các yêu cầu phải vượt qua. Đối tượng Session giữ một đối tượng thuộc quyền sở hữu của nó trước khi commit nó vào cơ sở dữ liệu.
Nếu có nhiều yêu cầu cập nhật cho một đối tượng, Hibernate cố gắng trì hoãn việc cập nhật càng lâu càng tốt để giảm số lượng các câu lệnh SQL cập nhật đã gởi. Nếu chúng ta đóng session, tất cả các đối tượng được lưu trữ trong bộ nhớ cache cũng sẽ bị mất và vẫn tiếp tục lưu trữ hoặc cập nhật trong cơ sở dữ liệu.
Một số đặc điểm cần lưu ý:
- First Level Cache được kết hợp với đối tượng Session và các đối tượng session khác trong ứng dụng không thể “nhìn thấy” hay làm ảnh hưởng.
- Phạm vi của cách đối tượng cache này là session. Khi một session bị đóng lại, các đối tượng cache thuộc session đó sẽ vĩnh viễn bị mất đi.
- First Level Cache được enable mặc định trong Hibernate và không có cách nào để disable nó cả.
- Khi chúng ta truy vấn 1 Entity lần đầu tiên, nó sẽ được lấy về từ database và được lữu trữ trong bộ nhớ của first-level cache, cái mà được liên kết với đối tượng hibernate session.
- Nếu chúng ta truy vấn lại cùng 1 Entity với cùng session, nó sẽ được load từ trong cache thay vì việc thực thi lại câu truy vấn SQL.
- Entity được load có thể bị xóa khỏi session, khỏi bộ nhớ first level cache bằng việc sử dụng phuơng thức
evict(entity)
. Như vậy, vào lần tiếp theo ta truy vấn thực thể đó, nó sẽ được lấy từ database (thay vì bộ nhớ cache). - Toàn bộ bộ nhớ cache của session có thể bị xoá thông qua phuơng thức
clear()
. - Có thể kiểm tra Entity được cache trong session hay chưa thông qua phương thức
contains()
. - Do Hibernate cache tất cả các đối tượng vào L1 Cache của session, trong khi chạy truy vấn hàng loạt (bulk queries) hoặc cập nhật hàng loạt (batch updates), nó cần thiết để xóa bộ đệm theo các khoảng thời gian nhất định để tránh các vấn đề về bộ nhớ.
Ví dụ First-level Cache
Ví dụ lấy 1 Entity nhiều lần
try (Session session = HibernateUtils.getSessionFactory().openSession();) { session.beginTransaction(); for(int i = 0; i < 5; i++) { Category cat = session.get(Category.class, 1L); System.out.println(cat.getName()); } session.getTransaction().commit(); }
Output:
Hibernate: select category0_.id as id1_0_0_, category0_.name as name2_0_0_ from Category category0_ where category0_.id=? Hibernate cache by gpcoder Hibernate cache by gpcoder Hibernate cache by gpcoder Hibernate cache by gpcoder Hibernate cache by gpcoder
Như bạn thấy SQL chỉ được thực thi 1 lần, mặc dù chúng ta gọi đến 5 lần do Hibernate đã giúp chúng ta cache lại.
Ví dụ lấy 1 Entity trên nhiều Session khác nhau
try (Session session1 = HibernateUtils.getSessionFactory().openSession(); Session session2 = HibernateUtils.getSessionFactory().openSession();) { Category cat1_1st = session1.get(Category.class, 1L); System.out.println("Session 1 at 1st time: " + cat1_1st.getName()); Category cat1_2nd = session1.get(Category.class, 1L); System.out.println("Session 1 at 2nd time: " + cat1_2nd.getName()); Category cat2 = session2.get(Category.class, 1L); System.out.println("Session 2: " + cat2.getName()); }
Output:
Hibernate: select category0_.id as id1_0_0_, category0_.name as name2_0_0_ from Category category0_ where category0_.id=? Session 1 at 1st time: Hibernate cache by gpcoder Session 1 at 2nd time: Hibernate cache by gpcoder Hibernate: select category0_.id as id1_0_0_, category0_.name as name2_0_0_ from Category category0_ where category0_.id=? Session 2: Hibernate cache by gpcoder
Như bạn thấy có 2 SQL được thực thi cho mỗi Session khác nhau mặc dù truy xuất cùng 1 Entity.
Ví dụ xoá L1 cache
try (Session session = HibernateUtils.getSessionFactory().openSession();) { // Begin a unit of work session.beginTransaction(); User user1 = session.get(User.class, 15L); System.out.println("user1: " + user1.getFullname()); Category cat1 = session.get(Category.class, 1L); System.out.println("cat1: " + cat1.getName()); Category cat2 = session.get(Category.class, 1L); System.out.println("cat2: " + cat2.getName()); // Get from cache session.evict(cat1); // Remove Category Entity form cache User user2 = session.get(User.class, 15L); System.out.println("user2: " + user2.getFullname()); // Get from cache Category cat3 = session.get(Category.class, 1L); System.out.println("cat3: " + cat3.getName()); // Get from database because Category entity already evicted session.clear(); // Remove all Entities from cache User user3 = session.get(User.class, 15L); System.out.println("user3: " + user3.getFullname()); // Get from database because User entity already cleared Category cat4 = session.get(Category.class, 1L); System.out.println("cat4: " + cat4.getName()); // Get from database because Category entity already cleared }
Output:
Hibernate: select user0_.id as id1_3_0_, user0_.created_at as created_2_3_0_, user0_.fullname as fullname3_3_0_, user0_.modified_at as modified4_3_0_, user0_.password as password5_3_0_, user0_.username as username6_3_0_ from user user0_ where user0_.id=? user1: gpcoder user Hibernate: select category0_.id as id1_0_0_, category0_.name as name2_0_0_ from Category category0_ where category0_.id=? cat1: Hibernate cache by gpcoder cat2: Hibernate cache by gpcoder user2: gpcoder user Hibernate: select category0_.id as id1_0_0_, category0_.name as name2_0_0_ from Category category0_ where category0_.id=? cat3: Hibernate cache by gpcoder Hibernate: select user0_.id as id1_3_0_, user0_.created_at as created_2_3_0_, user0_.fullname as fullname3_3_0_, user0_.modified_at as modified4_3_0_, user0_.password as password5_3_0_, user0_.username as username6_3_0_ from user user0_ where user0_.id=? user3: gpcoder user Hibernate: select category0_.id as id1_0_0_, category0_.name as name2_0_0_ from Category category0_ where category0_.id=? cat4: Hibernate cache by gpcoder
Như bạn thấy, phương thức evict() có thể được sử dụng để xoá cache cho 1 đối tượng. Phương thức clear() có thể được sử dụng để xoá tất cả đối tượng.
Second-level Cache – L2 Cache (Bộ nhớ cache cấp hai)
L2 Cache là một bộ nhớ cache tùy chọn (optional), không được enable by default.
Phạm vi ảnh hưởng của L1 Cache là session, nghĩa là khi gọi get một đối tượng trong session, thì nó sẽ chỉ tìm đối tượng trong session đó và trong trường hợp session không có, nó sẽ thực thi câu lệnh truy vấn tới database nếu không có L2 Cache. Trong trường hợp chúng ta có cấu hình L2 Cache thì Hibrernate sẽ thực hiện xác định vị trí một đối tượng trong L2 Cahce trước khi thực thi câu lệnh truy vấn đến database.
Phạm vi lưu trữ đối tượng được mở rộng hơn trong L2 Cache, sang mức SessionFactory thay vì Session như L1 Cache.
Hibernate cung cấp một interface org.hibernate.cache.CacheProvider, bên thứ 3 cần implements để cung cấp Hibernate với một xử lý để cài đăt bộ nhớ cache.
Việc setup L2 Cache sẽ được thực hiện trong 2 bước:
- Đầu tiên là việc define cache concurrency strategy (chiến lược truy cập đồng thời) nào được sử dụng.
- Tiếp theo, đó là việc cầu hình các attribute cho cache expiration (hết hạn) và cache vật lý bằng cách sử dụng cache provider.
Cache Concurrency Strategy
Chiến lược truy cập đồng thời là bộ điều chỉnh có trách nhiệm lưu trữ các mục dữ liệu trong bộ nhớ cache và lấy chúng từ bộ nhớ cache. Nếu muốn kích hoạt bộ nhớ cache cấp hai, bạn sẽ phải quyết định, đối với mỗi lớp và collection persistent, mà chiến lược truy cập đồng thời vào bộ nhớ cache để sử dụng.
- Read-only (READ_ONLY): Chiến lược lưu trữ này nên được sử dụng cho các đối tượng liên tục sẽ luôn đọc nhưng không bao giờ cập nhật. Nó phù hợp cho việc đọc và lưu cấu hình ứng dụng và các dữ liệu tĩnh khác không bao giờ được cập nhật. Đây là chiến lược đơn giản nhất với hiệu suất tốt nhất vì không có quá trình tải để kiểm tra xem đối tượng có được cập nhật trong cơ sở dữ liệu hay không.
- Nonstrict-read-write (NONSTRICT_READ_WRITE): cơ chế này không đảm bảo tính nhất quán giữa bộ nhớ cache và cơ sở dữ liệu. Sử dụng chiến lược này nếu dữ liệu hầu như không thay đổi và trong trường hợp thay đổi thì sự không nhất quán đó cũng không phải là vấn đề.
- Read-write (READ_WRITE):
- Cơ chế này đảm bảo tính nhất quán dữ liệu cao bằng việc sử dụng soft lock. Khi một Entity đã cache bị update, một soft lock được lưu lại trong cache cho entity và nó sẽ được giải phóng (release) khi transaction được commit. Tất cả các transaction nếu truy cập vào các đối tượng đang bị soft block sẽ được lấy trực tiếp từ cơ sở dữ liệu.
- Loại này phù hợp cho các đối tượng liên tục có thể được cập nhật bởi ứng dụng Hibernate. Tuy nhiên, nếu dữ liệu được cập nhật thông qua backend hoặc các application khác, thì không có cách nào Hibernate biết về nó và dữ liệu có thể bị cũ. Vì vậy, trong khi sử dụng chiến lược này, hãy đảm bảo bạn đang sử dụng API Hibernate để cập nhật dữ liệu.
- Transactional (TRANSACTIONAL): Cung cấp cấp bộ đệm transaction đầy đủ, nghĩa là một thay đổi trong Entity được lưu trong bộ nhớ cache được được đảm bảo khôi phục trong cả cơ sở dữ liệu và bộ đệm trong cùng một transaction.
Cache provider
Bước tiếp theo sau khi lựa chọn Cache Concurrency Strategy, chúng ta cần chọn lựa một Cache Provider duy nhất cho toàn bộ ứng dụng.
Một số Cache Provider thông dụng:
- EH Cache : Nó có thể cache trong bộ nhớ RAM hoặc trên đĩa cứng và clustered caching và nó hỗ trợ bộ nhớ cache kết quả truy vấn Hibernate tuỳ chọn.
- OS Cache : Hỗ trợ bộ nhớ đệm vào bộ nhớ RAM và đĩa cứng trong một JVM duy nhất, với một tập hợp đầy đủ các chính sách hết hạn và hỗ trợ bộ nhớ truy vấn.
- Swarm Cache : Một bộ nhớ cache cluster dựa trên JGroups. Nó sử dụng huỷ bỏ hiệu lực clustered nhưng không hỗ trợ bộ nhớ cache truy vấn Hibernate.
- JBoss Cache : Một bộ nhớ cache cluster được sao chép hoàn toàn hợp lệ được transaction dựa trên thư viện đa nhóm JGroups. Nó hỗ trợ nhân bản hoặc hủy bỏ hiệu lực, giao tiếp đồng bộ hoặc không đồng bộ, và optimistic, pessimistic locking. Bộ nhớ truy vấn cache Hibernate được hỗ trợ.
Mỗi Cache Provider không tương thích với mọi Cache Concurrency Strategy. Ma trận sẽ giúp bạn có một sự chọn phù hợp:
Provider \ Strategy | Read-only | Nonstrictread-write | Read-write | Transactional |
---|---|---|---|---|
EH Cache | X | X | X | |
OS Cache | X | X | X | |
Swarm Cache | X | X | ||
JBoss Cache | X | X |
Cách thức hoạt động L2 Cache
- Bất cứ khi nào hibernate session cố gắng load một Entity, nơi đầu tiên nó tìm L1 Cache (được liên kết với session cụ thể).
- Nếu Entity được lưu trong L1 Cache, nó được trả về như là kết quả của phương thức load.
- Nếu không tìm thấy, thì L2 Cache sẽ được tìm kiếm.
- Nếu tìm thấy trong L2 Cache, nó được trả về do kết quả của phương thức load. Nhưng, trước khi trả lại Entity, nó cũng được lưu L1 Cache để lệnh gọi tiếp theo sẽ trả về thực thể từ chính L1 Cache và sẽ không cần phải chuyển sang tìm ở L2 Cache nữa.
- Nếu không tìm thấy trong L2 Cache, thì truy vấn SQL đến cơ sở dữ liệu được thực thi và Entity được lưu trữ trong cả hai cấp bộ đệm, trước khi trả về dưới dạng phản hồi của phương thức load.
- L2 Cache xác nhận chính nó cho các Entity được sửa đổi, nếu việc sửa đổi đã được thực hiện thông qua các API Hibernate Session.
- Nếu một số người dùng hoặc quá trình thực hiện thay đổi trực tiếp trong cơ sở dữ liệu, thì không có cách nào mà L2 Cache cập nhật cho đến khi thời gian của ToToLiveSeconds đã vượt qua cho vùng bộ đệm đó. Trong trường hợp này, bạn nên vô hiệu hóa toàn bộ bộ đệm và để hibernate xây dựng bộ đệm của nó một lần nữa.
Enable L2 Cache
Để bật L2 Cache trong Hibernate, chỉ cần cấu hình 2 thuộc tính:
- hibernate.cache.use_second_level_cache : để báo với Hibernate rằng chúng ta có dùng L2 Cache hay không.
- hibernate.cache.region.factory_class : để chỉ định tên lớp Region Factory của Cache Provider.
Nếu muốn Query Cache, chúng ta có thể thêm thuộc tính: hibernate.cache.use_query_cache
Ví dụ Second-level Cache sử dụng EHCache
Trong bài này, chúng ta chọn EHCache làm Cache Provider cho ứng dụng.
Thêm EHCache dependency library:
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>5.4.7.Final</version> </dependency> <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-ehcache --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-ehcache</artifactId> <version>5.4.7.Final</version> </dependency>
Mở file cấu hình hibernate.cfg.xml và thêm cấu hình cache như sau:
<hibernate-configuration> <session-factory> <!-- Database setting --> <!-- enable second level cache --> <property name="cache.use_second_level_cache">true</property> <property name="cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property> </session-factory> </hibernate-configuration>
Tiếp theo, chúng ta cần phải báo với Hibernate rằng các Entity của mình có thể được sử dụng với L2 Cache. Chẳng hạn tôi cần cache Category như sau:
package com.gpcoder.entities; import lombok.Data; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; import javax.persistence.*; import java.util.Set; @Data @Entity @Table @Cacheable @Cache(usage= CacheConcurrencyStrategy.READ_ONLY) public class Category implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column private String name; @OneToMany(fetch = FetchType.LAZY, mappedBy = "category") @OrderBy("title") private Set<Post> posts; }
Chạy lại Ví dụ lấy 1 Entity trên nhiều Session khác nhau ở trên:
try (SessionFactory sessionFactory = HibernateUtils.getSessionFactory(); Session session1 = sessionFactory.openSession(); Session session2 = sessionFactory.openSession();) { Category cat1_1st = session1.get(Category.class, 1L); System.out.println("Session 1 at 1st time: " + cat1_1st.getName()); Category cat1_2nd = session1.get(Category.class, 1L); System.out.println("Session 1 at 2nd time: " + cat1_2nd.getName()); Category cat2 = session2.get(Category.class, 1L); System.out.println("Session 2: " + cat2.getName()); }
Output:
Hibernate: select category0_.id as id1_0_0_, category0_.name as name2_0_0_ from Category category0_ where category0_.id=? Session 1 at 1st time: Hibernate cache by gpcoder Session 1 at 2nd time: Hibernate cache by gpcoder Session 2: Hibernate cache by gpcoder
Như bạn thấy, bây giờ SQL chỉ được thực thi một lần, mặc dù ở 2 session khác nhau do nó đã được Cache ở L2.
Lưu ý: Collection mặc định sẽ không được cache, chúng ta cần khai báo tuờng minh nếu chúng ta cần cache nó lại. Chẳng hạn, list posts của Category trên sẽ không được Cache.
Ví dụ cơ chế hoạt động L2 Cache
Trong ví dụ này, mình cần enable thống kê (Statistic) của Hibernate L2 Cache:
- getSecondLevelCacheHitCount() : được sử dụng để lấy số lần Entity được lấy từ L2 Cache.
- getSecondLevelCachePutCount() : được sử dụng để lấy số lần Entity được đưa vào L2 Cache.
public static void main(String[] args) { try (SessionFactory sessionFactory = HibernateUtils.getSessionFactory(); Session session1 = sessionFactory.openSession(); Session session2 = sessionFactory.openSession();) { Statistics stats = sessionFactory.getStatistics(); System.out.println("Stats enabled = " + stats.isStatisticsEnabled()); stats.setStatisticsEnabled(true); System.out.println("Stats enabled = " + stats.isStatisticsEnabled()); printStats(stats); System.out.println("--- 1 ---"); Category cat1 = session1.get(Category.class, 1L); System.out.println("cat1: " + cat1.getName()); // Get from db printStats(stats); System.out.println("--- 2 ---"); Category cat2 = session1.get(Category.class, 1L); System.out.println("cat2: " + cat2.getName()); // Get from L1 Cache printStats(stats); System.out.println("--- 3 ---"); Category cat3 = session2.get(Category.class, 1L); System.out.println("cat3: " + cat3.getName()); // Get from L2 cache printStats(stats); System.out.println("--- 4 ---"); Category cat4 = session2.get(Category.class, 1L); System.out.println("cat4: " + cat4.getName()); // Get from L1 cache printStats(stats); System.out.println("--- 5 ---"); Category cat5 = session1.get(Category.class, 2L); System.out.println("cat5: " + cat5.getName()); // Get from db printStats(stats); System.out.println("--- 6 ---"); Category cat6 = session2.get(Category.class, 2L); System.out.println("cat6: " + cat6.getName()); // Get from L2 cache printStats(stats); } } private static void printStats(Statistics stats) { System.out.println("Second Level Hit Count = " + stats.getSecondLevelCacheHitCount()); System.out.println("Second Level Put Count = " + stats.getSecondLevelCachePutCount()); }
Output:
Stats enabled = false Stats enabled = true Fetch Count = 0 Second Level Hit Count = 0 Second Level Put Count = 0 --- 1 --- Hibernate: select category0_.id as id1_0_0_, category0_.name as name2_0_0_ from Category category0_ where category0_.id=? cat1: Hibernate cache by gpcoder Fetch Count = 0 Second Level Hit Count = 0 Second Level Put Count = 1 --- 2 --- cat2: Hibernate cache by gpcoder Fetch Count = 0 Second Level Hit Count = 0 Second Level Put Count = 1 --- 3 --- cat3: Hibernate cache by gpcoder Fetch Count = 0 Second Level Hit Count = 1 Second Level Put Count = 1 --- 4 --- cat4: Hibernate cache by gpcoder Fetch Count = 0 Second Level Hit Count = 1 Second Level Put Count = 1 --- 5 --- Hibernate: select category0_.id as id1_0_0_, category0_.name as name2_0_0_ from Category category0_ where category0_.id=? cat5: Hibernate Framework Fetch Count = 0 Second Level Hit Count = 1 Second Level Put Count = 2 --- 6 --- cat6: Hibernate Framework Fetch Count = 0 Second Level Hit Count = 2 Second Level Put Count = 2
Như bạn thấy:
- (1) : cả L1 và L2 cache không có gì, nên SQL được thực thì để lấy dữ liệu từ database. Sau đó nó được lưu tại L1 Cache của session1 và L2 Cache.
- (2) : Dữ liệu được lấy trực tiếp của L1 Cache, nên Hit=0, Put=1.
- (3) : Dữ liệu lấy từ L2 Cache. Do session2 không chứa Entity đã lấy từ session1, nó tìm ở L2 Cache và lấy từ Cache, không truy xuất Database. Sau đó, dữ liệu được cache lại ở L1 Cache của session2. Bây giờ: Hit=1, Put=1.
- (4) : Lần tiếp theo dữ liệu đã được cache lại ở L1 Cache của session2, nó không cần tìm ở L2 Cache, nên Hit=1, Put=1.
- (5) : Cả L1 và L2 cache không chứa Category=2, nên lấy dữ liệu từ database. Sau đó nó được lưu tại L1 Cache của session1 và L2 Cache. Bây giờ: Hit=1, Put=2.
- (6) : Tương tự, L1 Cache của session2 không chứa dữ liệu Category=2, nên nó tìm và truy xuất từ L2 Cache. Bây giờ: Hit=2, Put=2.
Ví dụ xoá L2 Cache
try (SessionFactory sessionFactory = HibernateUtils.getSessionFactory(); Session session = sessionFactory.openSession();) { // Begin a unit of work session.beginTransaction(); Category cat1 = session.get(Category.class, 1L); System.out.println("cat1: " + cat1.getName()); Category cat2 = session.get(Category.class, 1L); System.out.println("cat2: " + cat2.getName()); // Get from cache session.evict(cat1); // Remove Category Entity from L1 cache Category cat3 = session.get(Category.class, 1L); System.out.println("cat3: " + cat3.getName()); // Get from cache sessionFactory.getCache().evict(Category.class, cat3); // Clear cache of one Category Entity Category cat4 = session.get(Category.class, 1L); System.out.println("cat4: " + cat4.getName()); // Get from database because Category entity already cleared }
Output:
Hibernate: select category0_.id as id1_0_0_, category0_.name as name2_0_0_ from Category category0_ where category0_.id=? cat1: Hibernate cache by gpcoder cat2: Hibernate cache by gpcoder cat3: Hibernate cache by gpcoder Hibernate: select posts0_.category_id as category4_1_0_, posts0_.id as id1_1_0_, posts0_.id as id1_1_1_, posts0_.category_id as category4_1_1_, posts0_.content as content2_1_1_, posts0_.title as title3_1_1_, posts0_.user_id as user_id5_1_1_ from Post posts0_ where posts0_.category_id=? order by posts0_.title cat4: Hibernate cache by gpcoder
Như bạn thấy phương thức session.evict() không có tác dụng với L2 Cache, để xoá cache ở L2 Cache chúng ta cần gọi evict() từ cache của Session Factory.
Cache Management
Nếu không cấu hình chính sách hết hạn và gỡ bỏ, bộ nhớ cache có thể phát triển vô hạn và cuối cùng tiêu thụ hết bộ nhớ hiện có. Trong hầu hết các trường hợp, Hibernate trao nhiệm vụ đó cho Cache Provider.
EHCache có file cấu hình riêng của mình, đó là ehcache.xml nên để chỉ định các thuộc tính của vùng nhớ cache, thời gian lưu giữ, số lượng instance, …
Ví dụ:
ehcache.xml
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true" monitoring="autodetect" dynamicConfig="true"> <diskStore path="java.io.tmpdir/ehcache" /> <defaultCache maxEntriesLocalHeap="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" diskSpoolBufferSizeMB="30" maxEntriesLocalDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" statistics="true"> <persistence strategy="localTempSwap" /> </defaultCache> <cache name="com.gpcoder.entities.Category" maxEntriesLocalHeap="10000" eternal="false" timeToIdleSeconds="2" timeToLiveSeconds="10"> <persistence strategy="localTempSwap" /> </cache> <cache name="org.hibernate.cache.internal.StandardQueryCache" maxEntriesLocalHeap="5" eternal="false" timeToLiveSeconds="120"> <persistence strategy="localTempSwap" /> </cache> </ehcache>
EHCache bao gồm rất nhiều cấu hình, tôi chỉ giới thiệu với các bạn một vài cấu hình cơ bản, còn rất nhiều cấu hình khác các bạn xem thêm trên EHCache Configuration document.
- diskStore : EHCache lưu dữ liệu vào bộ nhớ nhưng khi nó bắt đầu đầy, nó bắt đầu ghi dữ liệu vào file system. Chúng ta sử dụng thuộc tính này để xác định vị trí nơi EHCache sẽ ghi dữ liệu khi đầy.
- defaultCache : được sử dụng khi một đối tượng cần được lưu trữ và không có vùng lưu trữ được xác định cho riêng cho nó.
- timeToIdleSeconds : bộ đệm sẽ hết hạn sau một khoảng thời gian cố định sau thời gian chúng được truy cập lần cuối.
- timeToLiveSeconds : bộ đệm sẽ hết hạn sau một khoảng thời gian cố định sau khi tạo.
- eternal: bộ đệm sẽ không bao giờ hết hạn.
- cache name=”com.gpcoder.entities.Category” : được sử dụng để xác định cấu hình riêng cho một Entity.
- org.hibernate.cache.internal.StandardQueryCache : Cấu hình vùng đệm cho Query Cache.
Tiếp theo, mở file cấu hình hibernate.cfg.xml và thêm cấu hình EHCache như sau:
<!-- enable second level cache --> <property name="cache.use_second_level_cache">true</property> <property name="cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property> <property name="net.sf.ehcache.configurationResourceName">/myehcache.xml</property>
Ví dụ: chúng ta cấu hình Cache cho Category sẽ hết hạn sau 2 giây sau thời gian chúng được truy cập lần cuối và 10 giây sau khi tạo.
private static void printStats(Statistics stats) { System.out.println("Second Level Hit Count = " + stats.getSecondLevelCacheHitCount()); System.out.println("Second Level Put Count = " + stats.getSecondLevelCachePutCount()); } public static void main(String[] args) { try (SessionFactory sessionFactory = HibernateUtils.getSessionFactory(); Session session1 = sessionFactory.openSession(); Session session2 = sessionFactory.openSession();) { Statistics stats = sessionFactory.getStatistics(); System.out.println("Stats enabled = " + stats.isStatisticsEnabled()); stats.setStatisticsEnabled(true); System.out.println("Stats enabled = " + stats.isStatisticsEnabled()); printStats(stats); System.out.println("--- 1 ---"); Category cat1 = session1.get(Category.class,1L); System.out.println(cat1.getName()); printStats(stats); System.out.println("--- 2 ---"); Category cat2 = session2.get(Category.class,1L); System.out.println(cat2.getName()); printStats(stats); TimeUnit.SECONDS.sleep(3); System.out.println("--- 3 ---"); Category cat3 = session1.get(Category.class,3L); System.out.println(cat3.getName()); printStats(stats); } catch (InterruptedException e) { e.printStackTrace(); } }
Output:
Stats enabled = false Stats enabled = true Second Level Hit Count = 0 Second Level Put Count = 0 --- 1 --- Hibernate: select category0_.id as id1_0_0_, category0_.name as name2_0_0_ from Category category0_ where category0_.id=? Hibernate cache by gpcoder Second Level Hit Count = 0 Second Level Put Count = 1 --- 2 --- Hibernate cache by gpcoder Second Level Hit Count = 1 Second Level Put Count = 1 --- 3 --- Hibernate: select category0_.id as id1_0_0_, category0_.name as name2_0_0_ from Category category0_ where category0_.id=? Java Second Level Hit Count = 1 Second Level Put Count = 2
Như bạn thấy:
- (1) : Dữ liệu được lấy từ database và lưu vào L2 Cache.
- (2) : Dữ liệu được lấy từ L2 Cache.
- (3) : Dữ liệu được lấy từ database, do sau khoảng 3 giây sau mới có request lấy dữ liệu, nên nó đã bị xoá khỏi cache từ trước (timeToIdleSeconds=2).
Query Cache
Cache các kết quả truy vấn kết hợp chặt chẽ với L2 Cache.
Đây là tính năng tùy chọn và yêu cầu thêm hai vùng bộ nhớ cache vật lý giữ kết quả truy vấn và các mốc thời gian khi một bảng được cập nhật lần cuối. Điều này chỉ hữu ích cho các truy vấn được chạy thường xuyên với các tham số giống nhau.
Để sử dụng Query Cache, mở file cấu hình hibernate.cfg.xml và thêm cấu hình sau:
<property name="cache.use_query_cache">true</property>
Ví dụ:
try (SessionFactory sessionFactory = HibernateUtils.getSessionFactory(); Session session1 = sessionFactory.openSession();) { String hql = "FROM Category cat WHERE cat.id = :id"; Query<Category> query = session1.createQuery(hql, Category.class); query.setCacheable(true); query.setCacheRegion(Category.class.getCanonicalName()); Category cat1 = query.setParameter("id", 1L).uniqueResult(); System.out.println("cat1: " + cat1.getName()); Category cat2 = query.setParameter("id", 1L).uniqueResult(); System.out.println("cat2: " + cat2.getName()); }
Output:
Hibernate: select category0_.id as id1_0_, category0_.name as name2_0_ from Category category0_ where category0_.id=? cat1: Hibernate cache by gpcoder cat2: Hibernate cache by gpcoder
Bài viết đến đây là hết. Hy vọng giúp ích cho các bạn improve performance của ứng dụng mình tốt hơn. Hẹn gặp lại ở các bài viết tiếp theo!!!
Tài liệu tham khảo:
- https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#caching
- https://www.tutorialspoint.com/hibernate/hibernate_caching.htm
- https://www.baeldung.com/hibernate-second-level-cache
- https://howtodoinjava.com/hibernate/understanding-hibernate-first-level-cache-with-example/
- https://howtodoinjava.com/hibernate/how-hibernate-second-level-cache-works/