Hibernate Cache

Công Nghệ
Hibernate Cache
Bài viết được sự cho phép của tác giả Giang Phan 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. Hibernate Batch processing Hibernate Interceptor & StatementInspector 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...

Bài viết được sự cho phép của tác giả Giang Phan

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.

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.

Hibernate CacheHibernate Cache

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 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 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:

Bài viết gốc được đăng tải tại gpcoder.com

Có thể bạn quan tâm:

Xem thêm Việc làm Developer hấp dẫn trên Station D

Bài viết liên quan

Bộ cài đặt Laravel Installer đã hỗ trợ tích hợp Jetstream

Bộ cài đặt Laravel Installer đã hỗ trợ tích hợp Jetstream

Bài viết được sự cho phép của tác giả Chung Nguyễn Hôm nay, nhóm Laravel đã phát hành một phiên bản chính mới của “ laravel/installer ” bao gồm hỗ trợ khởi động nhanh các dự án Jetstream. Với phiên bản mới này khi bạn chạy laravel new project-name , bạn sẽ nhận được các tùy chọn Jetstream. Ví dụ: API Authentication trong Laravel-Vue SPA sử dụng Jwt-auth Cách sử dụng Laravel với Socket.IO laravel new foo --jet --dev Sau đó, nó sẽ hỏi bạn thích stack Jetstream nào hơn: Which Jetstream stack do you prefer? [0] Livewire [1] inertia > livewire Will your application use teams? (yes/no) [no]: ... Nếu bạn đã cài bộ Laravel Installer, để nâng cấp lên phiên bản mới bạn chạy lệnh: composer global update Một số trường hợp cập nhật bị thất bại, bạn hãy thử, gỡ đi và cài đặt lại nha composer global remove laravel/installer composer global require laravel/installer Bài viết gốc được đăng tải tại chungnguyen.xyz Có thể bạn quan tâm: Cài đặt Laravel Làm thế nào để chạy Sql Server Installation Center sau khi đã cài đặt xong Sql Server? Quản lý các Laravel route gọn hơn và dễ dàng hơn Xem thêm Tuyển dụng lập trình Laravel hấp dẫn trên Station D

By stationd
Principle thiết kế của các sản phẩm nổi tiếng

Principle thiết kế của các sản phẩm nổi tiếng

Tác giả: Lưu Bình An Phù hợp cho các bạn thiết kế nào ko muốn làm code dạo, design dạo nữa, bạn muốn cái gì đó cao hơn ở tầng khái niệm Nếu lập trình chúng ta có các nguyên tắc chung khi viết code như KISS , DRY , thì trong thiết kế cũng có những nguyên tắc chính khi làm việc. Những nguyên tắc này sẽ là kim chỉ nam, nếu có tranh cãi giữa các member trong team, thì cứ đè nguyên tắc này ra mà giải quyết (nghe hơi có mùi cứng nhắc, mình thì thích tùy cơ ứng biến hơn) Tìm các vị trí tuyển dụng designer lương cao cho bạn Nguyên tắc thiết kế của GOV.UK Đây là danh sách của trang GOV.UK Bắt đầu với thứ user cần Làm ít hơn Thiết kế với dữ liệu Làm mọi thứ thật dễ dàng Lặp. Rồi lặp lại lần nữa Dành cho tất cả mọi người Hiểu ngữ cảnh hiện tại Làm dịch vụ digital, không phải làm website Nhất quán, nhưng không hòa tan (phải có chất riêng với thằng khác) Cởi mở, mọi thứ tốt hơn Bao trừu tượng luôn các bạn, trang Gov.uk này cũng có câu tổng quát rất hay Thiết kế tốt là thiết kế có thể sử dụng. Phục vụ cho nhiều đối tượng sử dụng, dễ đọc nhất nhất có thể. Nếu phải từ bỏ đẹp tinh tế – thì cứ bỏ luôn . Chúng ta tạo sản phẩm cho nhu cầu sử dụng, không phải cho người hâm mộ . Chúng ta thiết kế để cả nước sử dụng, không phải những người đã từng sử dụng web. Những người cần dịch vụ của chúng ta nhất là những người đang cảm thấy khó sử dụng dịch...

By stationd
Hiểu về trình duyệt – How browsers work

Hiểu về trình duyệt – How browsers work

Bài viết được sự cho phép của vntesters.com Khi nhìn từ bên ngoài, trình duyệt web giống như một ứng dụng hiển thị những thông tin và tài nguyên từ server lên màn hình người sử dụng, nhưng để làm được công việc hiển thị đó đòi hỏi trình duyệt phải xử lý rất nhiều thông tin và nhiều tầng phía bên dưới. Việc chúng ta (Developers, Testers) tìm hiểu càng sâu tầng bên dưới để nắm được nguyên tắc hoạt động và xử lý của trình duyệt sẽ rất hữu ích trong công việc viết code, sử dụng các tài nguyên cũng như kiểm thử ứng dụng của mình. Cách để npm packages chạy trong browser Câu hỏi phỏng vấn mẹo về React: Component hay element được render trong browser? Khi hiểu được cách thức hoạt động của trình duyệt chúng ta có thể trả lời được rất nhiều câu hỏi như: Tại sao cùng một trang web lại hiển thị khác nhau trên hai trình duyệt? Tại sao chức năng này đang chạy tốt trên trình duyệt Firefox nhưng qua trình duyệt khác lại bị lỗi? Làm sao để trang web hiển thị nội dung nhanh và tối ưu hơn một chút?… Hy vọng sau bài này sẽ giúp các bạn có một cái nhìn rõ hơn cũng như giúp ích được trong công việc hiện tại. 1. Cấu trúc của một trình duyệt Trước tiên chúng ta đi qua cấu trúc, thành phần chung và cơ bản nhất của một trình duyệt web hiện đại, nó sẽ gồm các thành phần (tầng) như sau: Thành phần nằm phía trên là những thành phần gần với tương tác của người dùng, càng phía dưới thì càng sâu và nặng về xử lý dữ liệu hơn tương tác. Nhiệm...

By stationd
Thị trường EdTech Vietnam- Nhiều tiềm năng nhưng còn bị bỏ ngỏ tại Việt Nam

Thị trường EdTech Vietnam- Nhiều tiềm năng nhưng còn bị bỏ ngỏ tại Việt Nam

Lĩnh vực EdTech (ứng dụng công nghệ vào các sản phẩm giáo dục) trên toàn cầu hiện nay đã tương đối phong phú với nhiều tên tuổi lớn phân phối đều trên các hạng mục như Broad Online Learning Platforms (nền tảng cung cấp khóa học online đại chúng – tiêu biểu như Coursera, Udemy, KhanAcademy,…) Learning Management Systems (hệ thống quản lý lớp học – tiêu biểu như Schoology, Edmodo, ClassDojo,…) Next-Gen Study Tools (công cụ hỗ trợ học tập – tiểu biểu như Kahoot!, Lumosity, Curriculet,…) Tech Learning (đào tạo công nghệ – tiêu biểu như Udacity, Codecademy, PluralSight,…), Enterprise Learning (đào tạo trong doanh nghiệp – tiêu biểu như Edcast, ExecOnline, Grovo,..),… Hiện nay thị trường EdTech tại Việt Nam đã đón nhận khoảng đầu tư khoảng 55 triệu đô cho lĩnh vực này nhiều đơn vị nước ngoài đang quan tâm mạnh đến thị trường này ngày càng nhiều hơn. Là một trong những xu hướng phát triển tốt, và có doanh nghiệp đã hoạt động khá lâu trong ngành nêu tại infographic như Topica, nhưng EdTech vẫn chỉ đang trong giai đoạn sơ khai tại Việt Nam. Tại Việt Nam, hệ sinh thái EdTech trong nước vẫn còn rất non trẻ và thiếu vắng nhiều tên tuổi trong các hạng mục như Enterprise Learning (mới chỉ có MANA), School Administration (hệ thống quản lý trường học) hay Search (tìm kiếm, so sánh trường và khóa học),… Với chỉ dưới 5% số dân công sở có sử dụng một trong các dịch vụ giáo dục online, EdTech cho thấy vẫn còn một thị trường rộng lớn đang chờ được khai phá. *** Vừa qua Station D đã công bố Báo cáo Vietnam IT Landscape 2019 đem đến cái nhìn toàn cảnh về các ứng dụng công...

By stationd