Hibernate Caching - Bài 1: First Level Cache

Chào mừng các bạn đến với chuỗi bài hướng dẫn của mình về Hibernate Caching.

0. Giới thiệu

Hibernate Cache rất hữu ích trong việc tăng hiệu suất của ứng dụng nếu được sử dụng đúng cách. Ý tưởng của nó là giảm số lượng truy vấn tới cơ sở dữ liệu, dẫn tới giảm tải thời gian thông lượng của ứng dụng.

Hibernate Framework mang tới cho chúng ta 3 kiểu cache khác nhau. Bao gồm

  1. First Level Cache: Hibernate First Level Cache được kết hợp với đối tợi Session (phiên làm việc). FLC được mặc định sử dụng trong Hibernate ta không có cách nào để tắt nó đi. Tuy nhiên, Hibernate cung cấp các phuơng thức mà thông qua nó, chúng ta có thể xóa bỏ các đối tượng được lựa chọn từ bộ nhớ cache hay giải phóng bộ nhớ cache 1 cách hoàn toàn. Bất cứ đối tượng đuợc cách nào trong 1 session (phiên làm việc) sẽ không bị ảnh hưởng bởi các session khác và khi session đó bị đóng lại, tất cả các đối tượng được cache đó cũng sẽ bị mất.

  2. Second Level Cache: Chúng ta sẽ tìm hiểu trong bài số 2 của series này nhé

  3. Query Cache: Chúng ta sẽ tìm hiểu trong bài số 3 của series này nhé

1 . First Level Cache

Hibernate First Level Cache được kết hợp với đối tượng Session (phiên làm việc). First Level Cache được mặc định sử dụng trong Hibernate và bạn chẳng cần phải làm gì để bật nó lên cũng như không có cách nào để tắt nó đi cả. Tuy nhiên, Hibernate cung cấp các phuơng thức mà thông qua nó, chúng ta có thể xóa bỏ các đối tượng được lựa chọn từ bộ nhớ cache hay giải phóng bộ nhớ cache 1 cách hoàn toàn. Bất cứ đối tượng đuợc cách nào trong 1 session (phiên làm việc) sẽ không bị ảnh hưởng bởi các session khác và khi session đó bị đóng lại, tất cả các đối tượng được cache đó cũng sẽ bị mất.

CÁC ĐẶC ĐIỂM QUAN TRỌNG CẦN CHÚ Ý

  1. 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
  2. Phạm vi của cách đối tượng cache này là session (phiên). 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.
  3. First Level Cache là mặc định trong Hibernate và không có cách nào để disable nó cả.
  4. Khi chúng ta truy vấn 1 thực thể (abc) 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
  5. Nếu chúng ta truy vấn lại cùng 1 đối tượng (abc) 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
  6. Thực thể (abc) đượ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).
  7. Toàn bộ bộ nhớ cache cúa session có thể bị làm trống với việc sử dụng phuơng thức clear(). Điều này có nghĩa là các thực thể được lưu trữ trong bộ nhớ cache cũng sẽ bị xóa bỏ.

Để dễ hiểu hơn, bạn hãy tưởng tượng câu chuyện duới đây

Hôm nay là bạn có cỗ mà nhà bạn thì lại thiếu quá nhiều thứ cho việc nấu nướng.........

Sáng sớm, bạn chạy sang nhà hàng xóm mượn 1 con dao với 1 cái thớt. 
Đầu tiên, bạn lấy con dao để bổ củi, dùng xong bạn để ở dưới bếp nhà mình để trưa thái thịt bằng dao và thớt.
Sáng tới trưa, bạn cần dao và thớt để thái thịt, bạn chỉ việc chạy xuống bếp,, thay vì phải sang nhà hàng xóm mượn lại. Tiện quá đúng ko?
Giờ không cần tới thớt nữa, bạn cũng lại nhớ ra là mình cần cái mâm, thế rồi bạn cầm cái thớt sang trả và mượn cái mâm về.

Đệch, nhưng mà gà luộc còn chưa chặt, giờ méo có thớt thì chặt kiêủ gì nhỉ? 
Thế là bạn lại lóc cóc sang nhà hàng xóm mượn lại cái thớt. 
Bạn chợt nghĩ. "Đê ma ma! Vừa mà ko trả ngay thì chỉ cần phải xuống dưới bếp là có thớt rồi không"!

Sang giờ chiều, bạn mang tất cả chúng sang trả lại nhà hàng xóm.

Lần sau có cỗ, lại mượn tiếp mấy cái đó :)

Trong ví dụ trên

  • Nhà hàng xóm của bạn đóng vai trò là Database
  • Nhà bếp nhà bạn đóng vai trò là bộ nhớ cache của First Level Cache
  • Con dao là đối tượng thực thể ta sử dụng entity
  • Khi bạn trả các thớt, bạn đã sử dụng evict(cái thớt) loại bỏ cái thớt khỏi cái bếp (bộ nhớ cache)
  • Tới chiều, bạn trả toàn bộ các đồ bạn đã mượn, bạn vừa sử dụng hàm clear() và đóng cái session đó lại.
  • Hôm sau nếu có cỗ, bạn lại đi mượn, lúc đó, session mới lại được mở và bắt đầu 😃

Dễ hiểu đúng ko? Giờ, ta cùng xem ví dụng về First Level Cache để hiểu hơn nhé.

Ví dụ 1.1: Lấy cùng 1 thực thể nhiều lần trong cùng 1 session

// Mở một sesion
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
 
// Lấy đối tượng department lần đầu tiên
DepartmentEntity department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());
 
// Lấy đối tượng department thêm lần nữa
department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());
 
 // Lấy thêm nhiều lần nữa
 for(int i = 0; i < 5; i++) {
     department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
     System.out.println(department.getName());
 }
 
session.getTransaction().commit();
HibernateUtil.shutdown();

Theo dõi console log của bạn nhé.

Hibernate: select department0_.ID as ID0_0_, department0_.NAME as NAME0_0_ from DEPARTMENT department0_ where department0_.ID=?
Human Resource
Human Resource
Human Resource
Human Resource
Human Resource

Bạn thấy gì không nào. log báo hibenate thực hiện câu truy vấn chỉ hiển thị 1 lần. Rõ ràng là hibernate lúc này chỉ thực thi câu truy vấn 1 lần tới database. Lần thứ 2, thứ 3, .v.v. nó lấy từ bộ nhớ cache. 😃 Hay ho đúng ko? (hehe)


Ví dụ 1.2: Lấy cùng 1 thực thể trong các session khác nhau

// Mở 1 sessionA
Session sessionA = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
 // Mở 1 sessionB khác
Session sessionB = HibernateUtil.getSessionFactory().openSession();
sessionTemp.beginTransaction();
try
{
    // Lấy đối tượng department lần đầu tiên bằng sessionA
    DepartmentEntity department = (DepartmentEntity) sessionA.load(DepartmentEntity.class, new Integer(1));
    System.out.println(department.getName());
     
    // Lấy đối tượng department thêm 1 lần nữa 
    department = (DepartmentEntity) sessionA.load(DepartmentEntity.class, new Integer(1));
    System.out.println(department.getName());
     
     // Lấy đối tượng department thêm 1 lần nữa nhưng bằng sesionB
    department = (DepartmentEntity) sessionB.load(DepartmentEntity.class, new Integer(1));
    System.out.println(department.getName());
} finally {
    sessionA.getTransaction().commit();
    sessionA.getTransaction().commit();
    HibernateUtil.shutdown();
}

Giờ thì xem console log của chúng ta thôi

Hibernate: select department0_.ID as ID0_0_, department0_.NAME as NAME0_0_ from DEPARTMENT department0_ where department0_.ID=?
Human Resource
Human Resource
 
Hibernate: select department0_.ID as ID0_0_, department0_.NAME as NAME0_0_ from DEPARTMENT department0_ where department0_.ID=?
Human Resource

Bạn có thể thấy rằng cho dù đối tượng entity của chúng ta đã được lưu trữ trong sessionA, nó vẫn sẽ được lấy từ database khi chúng ta sử lấy bằng 1 sessionB khác.


Ví dụ 1.3: Xóa các đối tượng cache khỏi bộ nhớ cache

Dù ta không có cách nào để disable First Level Cache trong Hibernate, nhưng nó ta có thể có thể xóa 1, 1 vài hoặc tất cả các đối tượng được cache khỏi First Level Cache bằng cách sử dụng

  • sessionA.eviect(entityA) để xóa đối tượng entityA
  • sessionA.clear() Để xóa toàn bộ mọi đối tượng trong First Level Cache
//Open the hibernate session
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
try
{
    // Lấy đối tượng department lần đầu tiên
    DepartmentEntity department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
    System.out.println(department.getName());
     
    // Lấy tiếp lần thứ 2
    department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
    System.out.println(department.getName());
     
     // Xóa bỏ khỏi session - hay First Level Cache
    session.evict(department);
    //session.clear();
     
     // Lấy tiếp đối tượng department lần nữa
    department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
    System.out.println(department.getName());
} finally {
    session.getTransaction().commit();
    HibernateUtil.shutdown();
}

Console log của chúng ta như sau

Hibernate: select department0_.ID as ID0_0_, department0_.NAME as NAME0_0_ from DEPARTMENT department0_ where department0_.ID=?
Human Resource
Human Resource
 
Hibernate: select department0_.ID as ID0_0_, department0_.NAME as NAME0_0_ from DEPARTMENT department0_ where department0_.ID=?
Human Resource

Rõ ràng là phuơng thức evict(entity) đã xóa đối tượng department khỏi bộ nhớ cache nên các bạn thấy rằng, lần thứ 3 chúng ta phải thực hiện truy vấn tới database.

Vậy thì, câu hỏi đặt ra là khi nào bạn cần xóa bỏ 1, 1 vài, thậm chí là tất cả các đối tượng đặt trong First Level Cache? Theo cá nhân mình, thì là do phiên làm việc quá dài, không thể đảm bảo được rằng đối tượng ta đang cache trong First Level Cache luôn luôn khớp với đối tượng đó trong database trong khi ta luôn cần lấy thông tin đó 1 cách chính xác nhất. 😃 Quan điểm đó là của các nhân mình thôi, các bạn hãy tự khám phá thêm nhé.

Cảm ơn các bạn đã dành thời gian để theo dõi bài viết này. Link source code: Mình sẽ update sau nhé (Thời điểm viết bài này máy móc mình đang có chút vấn đề. Với lại First Level Cache đơn giản, dễ hiểu nên mình sẽ tập trung source cho Second Level Cache và Query Cache) Đón xem bài viết Hibernate Caching - Bài 2: First Level Cache