Bài viết được sự cho phép của tác giả Giang Phan
1. Hibernate Interceptor
1.1. Hibernate Interceptor là gì?
Interceptor là một tính năng rất hữu ích cho ứng dụng để kiểm soát đối tượng với các sự kiện nhất định xảy ra bên trong Hibernate.
Một đối tượng đi qua các giai đoạn khác nhau trong lifecycle của nó. Interface Interceptor trong Hibernate cung cấp các phương thức có thể được gọi ở các giai đoạn khác nhau, để thực hiện một số nhiệm vụ được yêu cầu.
Các Interceptor được đăng ký dưới dạng callback và cung cấp các liên kết liên lạc giữa session và application của Hibernate. Với một callback như vậy, một ứng dụng có thể kiểm tra và thay đổi các thuộc tính của một đối tượng persistent trước khi nó được save, update, delete or load.
Có 2 cách để tạo một Interceptor:
- Implement org.hibernate.Interceptor interface.
- extend org.hibernate.EmptyInterceptor class.
Nếu không có yêu cầu đặc biệt, chúng ta nên extends class EmptyInterceptor và chỉ override các phương thức được yêu cầu.
Interface org.hibernate.Interceptor bao gồm các phương thức sau:
- instantiate() : Phương thức này được gọi khi một lớp persistent được khởi tạo.
- onLoad() : Phương thức này được gọ khi một đối tượng được load từ database và khởi tạo.
- onSave() : Phương thức này được gọ khi một đối tượng được lưu.
- onDelete() : Phương thức này được gọi khi một đối tượng bị xóa.
- preFlush() : Phương thức này được gọi trước khi flush.
- postFlush() : Phương thức này được gọi sau khi flush và một đối tượng đã được update trong bộ nhớ.
- isUnsaved() : Phương thức này được gọi khi một đối tượng được truyền vào phương thức saveOrUpdate() .
- findDirty() : Phương thức này được gọi khi phương thức flush() được gọi trên một đối tượng Session.
- onFlushDirty() : Phương thức này được gọi khi Hibernate phát hiện ra rằng một đối tượng là dirty (tức là đã được thay đổi) trong quá trình flush.
- Ngoài ra còn nhiều phương thức khác, các bạn tham khảo thêm Hibernate Interceptor javadoc.
Một Interceptor có thể được sử dụng ở: Session hoặc SessionFactory scope.
1.2. Ví dụ sử dụng Hibernate Interceptor
1.2.1. Create Interceptor
package com.gpcoder; import com.gpcoder.entities.Tag; import lombok.extern.log4j.Log4j2; import org.hibernate.EmptyInterceptor; import org.hibernate.type.Type; import java.io.Serializable; import java.util.Arrays; import java.util.Iterator; @Log4j2 public class LoggingInterceptor extends EmptyInterceptor { @Override public String onPrepareStatement(String sql) { log.debug("onPrepareStatement: {}", sql); return super.onPrepareStatement(sql); } @Override public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { log.debug("onSave: {}", entity); return super.onSave(entity, id, state, propertyNames, types); } @Override public void preFlush(Iterator entities) { log.debug("preFlush: {}", entities.next()); super.preFlush(entities); } @Override public void postFlush(Iterator entities) { log.debug("postFlush: {}", entities.next()); super.postFlush(entities); } }
1.2.2. Register Interceptor
Cấu hình Interceptor ở mức Session:
Session session = sessionFactory .withOptions() .interceptor(new LoggingInterceptor() ) .openSession();
Trường hợp muốn cấu hình Interceptor ở mức SessionFactory, nghĩa là tất cả Session đều áp dụng Interceptor này:
package com.gpcoder.utils; import com.gpcoder.LoggingInterceptor; import org.hibernate.SessionFactory; import org.hibernate.boot.Metadata; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.service.ServiceRegistry; public class HibernateUtils { private static final SessionFactory sessionFactory = buildSessionFactory(); private HibernateUtils() { super(); } private static SessionFactory buildSessionFactory() { ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder() // .configure() // Load hibernate.cfg.xml from resource folder by default .build(); Metadata metadata = new MetadataSources(serviceRegistry).getMetadataBuilder().build(); return metadata.getSessionFactoryBuilder() .applyInterceptor( new LoggingInterceptor() ) // Register interceptor .build(); } public static SessionFactory getSessionFactory() { return sessionFactory; } public static void close() { getSessionFactory().close(); } }
Trong trường hợp muốn đảm bảo thread-safe, chúng ta cần xác định session context trong file hibernate.cfg.xml:
<
property
name
=
"current_session_context_class"
>org.hibernate.context.internal.ThreadLocalSessionContext</
property
>
1.2.3. Tạo lớp ứng dụng
package com.gpcoder; import com.gpcoder.entities.Tag; import com.gpcoder.utils.HibernateUtils; import lombok.extern.log4j.Log4j2; import org.hibernate.Session; import org.hibernate.SessionFactory; @Log4j2 public class HibernateInterceptorExample { public static void main(String[] args) { try (SessionFactory sessionFactory = HibernateUtils.getSessionFactory(); Session session = sessionFactory .withOptions() .interceptor(new LoggingInterceptor()) // Register interceptor .openSession(); ) { session.getTransaction().begin(); Tag tag = new Tag(); tag.setName("Hibernate Interceptor"); session.persist(tag); session.getTransaction().commit(); } } }
Output:
2020-Apr-02 22:41:38 PM [main] DEBUG com.gpcoder.LoggingInterceptor - onPrepareStatement: select nextval ('tag_id_seq') 2020-Apr-02 22:41:38 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 22:41:38 PM [main] DEBUG com.gpcoder.LoggingInterceptor - onSave: Tag(id=43, name=Hibernate Interceptor) 2020-Apr-02 22:41:38 PM [main] DEBUG com.gpcoder.LoggingInterceptor - preFlush: Tag(id=43, name=Hibernate Interceptor) 2020-Apr-02 22:41:38 PM [main] DEBUG com.gpcoder.LoggingInterceptor - onPrepareStatement: insert into Tag (name, id) values (?, ?) 2020-Apr-02 22:41:38 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 22:41:38 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Interceptor] 2020-Apr-02 22:41:38 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [43] 2020-Apr-02 22:41:38 PM [main] DEBUG com.gpcoder.LoggingInterceptor - postFlush: Tag(id=43, name=Hibernate Interceptor)
2. Hibernate StatementInspector
Một tính năng Hibernate rất hữu ích nhưng ít được biết đến là khả năng chặn (Interceptor) và sửa đổi bất kỳ câu lệnh SQL nào được tạo tự động bằng tiện ích Hibernate StatementInspector. Trong phần tiếp theo của bài viết này, chúng ta sẽ xem cơ chế Hibernate StatementInspector hoạt động như thế nào.
2.1. Create StatementInspector
Interface StatementInspector chỉ gồm 1 phương thức duy nhất inspect(String sql). Phương thức nhận một câu lệnh SQL mà Hibernate sắp thực hiện và cho phép bạn sửa đổi câu lệnh SQL và trả nó về Hibernate tiếp tục thực thi.
Ví dụ: tạo SqlCommentStatementInspector để remove các auto comment của Hiberate.
package com.gpcoder; import lombok.extern.log4j.Log4j2; import org.hibernate.resource.jdbc.spi.StatementInspector; import java.util.regex.Pattern; @Log4j2 public class SqlCommentStatementInspector implements StatementInspector { private static final Pattern SQL_COMMENT_PATTERN = Pattern.compile("/*.*?*/s*"); @Override public String inspect(String sql) { log.debug("Executing SQL query: {}", sql); String sqlAfterRemovedComment = SQL_COMMENT_PATTERN.matcher(sql).replaceAll(""); log.debug("After removed: {}", sqlAfterRemovedComment); return sqlAfterRemovedComment; } }
2.2. Register StatementInspector
Để đăng ký một implment của StatementInspector, mở file hibernate.cfg.xml và thêm cấu hình sau:
<!-- Enable SQL comment -->
<
property
name
=
"use_sql_comments"
>true</
property
>
<!-- egister an implementation of the StatementInspector interface -->
<
property
name
=
"session_factory.statement_inspector"
>com.gpcoder.SqlCommentStatementInspector</
property
>
Lưu ý:
- use_sql_comments : enable option này lên để cho phép Hibernate auto generate comment để minh hoạ cho ví dụ của chúng ta. Trong ứng dụng thực tế không cần enable option này cho StatementInspector cụ thể.
Một cách khác để đăng ký là thông qua SessionFactoryBuilder:
package com.gpcoder.utils; import com.gpcoder.LoggingInterceptor; import com.gpcoder.SqlCommentStatementInspector; import org.hibernate.SessionFactory; import org.hibernate.boot.Metadata; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.service.ServiceRegistry; public class HibernateUtils { private static final SessionFactory sessionFactory = buildSessionFactory(); private HibernateUtils() { super(); } private static SessionFactory buildSessionFactory() { ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder() // .configure() // Load hibernate.cfg.xml from resource folder by default .build(); Metadata metadata = new MetadataSources(serviceRegistry).getMetadataBuilder().build(); return metadata.getSessionFactoryBuilder() .applyStatementInspector( new SqlCommentStatementInspector() ) // Register StatementInspector .applyInterceptor( new LoggingInterceptor() ) // Register interceptor .build(); } public static SessionFactory getSessionFactory() { return sessionFactory; } public static void close() { getSessionFactory().close(); } }
2.3. Tạo lớp ứng dụng
package com.gpcoder; import com.gpcoder.entities.Tag; import com.gpcoder.utils.HibernateUtils; import lombok.extern.log4j.Log4j2; import org.hibernate.Session; import org.hibernate.SessionFactory; @Log4j2 public class HibernateInterceptorExample { public static void main(String[] args) { try (SessionFactory sessionFactory = HibernateUtils.getSessionFactory(); Session session = sessionFactory.openSession(); ) { session.getTransaction().begin(); Tag tag = new Tag(); tag.setName("Hibernate Interceptor"); session.persist(tag); session.getTransaction().commit(); } } }
Output:
2020-Apr-02 23:33:44 PM [main] DEBUG com.gpcoder.SqlCommentStatementInspector - Executing SQL query: select nextval ('tag_id_seq') 2020-Apr-02 23:33:44 PM [main] DEBUG com.gpcoder.SqlCommentStatementInspector - After removed: select nextval ('tag_id_seq') 2020-Apr-02 23:33:44 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 23:33:44 PM [main] DEBUG com.gpcoder.LoggingInterceptor - onSave: Tag(id=50, name=Hibernate Interceptor) 2020-Apr-02 23:33:44 PM [main] DEBUG com.gpcoder.LoggingInterceptor - preFlush: Tag(id=50, name=Hibernate Interceptor) 2020-Apr-02 23:33:44 PM [main] DEBUG com.gpcoder.SqlCommentStatementInspector - Executing SQL query: /* insert com.gpcoder.entities.Tag */ insert into Tag (name, id) values (?, ?) 2020-Apr-02 23:33:44 PM [main] DEBUG com.gpcoder.SqlCommentStatementInspector - After removed: insert into Tag (name, id) values (?, ?) 2020-Apr-02 23:33:44 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 23:33:44 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Interceptor] 2020-Apr-02 23:33:44 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [50] 2020-Apr-02 23:33:44 PM [main] DEBUG com.gpcoder.LoggingInterceptor - postFlush: Tag(id=50, name=Hibernate Interceptor)
Như bạn thấy, comment của hibernate /* insert com.gpcoder.entities.Tag */
đã được loại bỏ một cách tự động nhờ vào StatementInspector.
Một số trường hợp khác có thể sử dụng StatementInspector: capture tất cả câu lệnh SQL được call bởi Hibernate, quản lý số lần SQL được gọi để phục vụ cho testing, reporting, …
Tài liệu tham khảo:
- https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#events
- https://vladmihalcea.com/hibernate-statementinspector/
Bài viết gốc được đăng tải tại gpcoder.com
Có thể bạn quan tâm:
- Cho phép tùy chọn Giao diện trong Spring Web MVC framework
- Sử dụng ứng dụng HTTP Client của Angular v4
- Hướng dẫn inspect animation với Chrome DevTools
Xem thêm Việc làm Developer hấp dẫn trên TopDev