Spring JPA Architecture Part 1
Trong thế giới phát triển ứng dụng Java, việc tương tác với cơ sở dữ liệu một cách hiệu quả và dễ dàng là một yếu tố then chốt. Spring Data JPA nổi bật như một giải pháp mạnh mẽ giúp lập trình viên không chỉ đơn giản hóa quá trình truy vấn mà còn tối ưu hóa việc quản lý dữ liệu. Trong bài viết này chúng ta sẽ khám phá cùng nhau những đặc điểm nổi bật của Spring JPA và cách nó hỗ trợ trong việc xây dựng các ứng dụng với khả năng tương tác tốt hơn với cơ sở dữ liệu.
I. ORM (Object Relation Mapping)
Khái niệm đầu tiên mà chúng ta cần phải nắm được trước khi tìm hiểu về JPA đó là cơ chế ORM. ORM là 1 kỹ thuật lập trình giúp ánh xạ các bản ghi trong cơ sở dữ liệu sang dạng đối tượng của một class ta định nghĩa. Khi sử dụng ORM, chúng ta có thể làm việc với cơ sở dữ liệu thông qua các đối tượng, thay vì phải sử dụng các câu truy vấn SQL trực tiếp.
Mục tiêu của ORM:
Giảm thiểu sự phụ thuộc vào SQL.
Giảm bớt việc viết thủ công các câu lệnh truy vấn.
Tự động hóa quá trình chuyển đổi dữ liệu giữa hệ thống và cơ sở dữ liệu.
Ưu điểm của ORM:
Giảm thiểu mã SQL thủ công: ORM tự động sinh ra các truy vấn SQL cho các thao tác đơn giản, giúp bạn tập trung vào logic nghiệp vụ hơn là các thao tác truy vấn.
Tính nhất quán: ORM cung cấp một cách tiếp cận chuẩn hóa cho việc làm việc với dữ liệu, làm cho mã nguồn dễ đọc và bảo trì.
Tối ưu hóa hiệu suất: ORM hỗ trợ các tính năng như lazy loading (tải chậm), caching (bộ nhớ đệm), và batch processing (xử lý hàng loạt), giúp cải thiện hiệu suất khi làm việc với dữ liệu lớn.
Một vài ORM phổ biến:
Hibernate (Java) - Thư viện ORM phổ biến nhất cho Java, thường được sử dụng cùng với JPA trong Spring Framework.
Entity Framework (C#/.NET) - ORM chính thức của .NET, giúp làm việc dễ dàng với các cơ sở dữ liệu SQL.
Django ORM (Python) - ORM tích hợp trong Django, hỗ trợ truy vấn dữ liệu dễ dàng trong ứng dụng Python.
SQLAlchemy (Python) - Một ORM mạnh mẽ và linh hoạt, cho phép truy vấn và ánh xạ dữ liệu chi tiết.
Active Record (Ruby on Rails) - ORM tích hợp trong Rails, cung cấp cú pháp thân thiện cho các thao tác cơ sở dữ liệu.
Sequelize (JavaScript/Node.js) - ORM phổ biến cho Node.js, hỗ trợ nhiều loại cơ sở dữ liệu như MySQL, PostgreSQL, SQLite.
II. JPA và Hibernate
Tại sao khi nói về JPA thì chúng ta cần nhắc đến Hibernate thì các bạn hãy xem ảnh sau:
JPA (Java Persistence API) là một đặc tả (specification) của Java nhằm hỗ trợ ánh xạ giữa các đối tượng trong mã Java và các bảng trong cơ sở dữ liệu quan hệ. JPA giúp cho các lập trình viên dễ dàng quản lý dữ liệu trong cơ sở dữ liệu mà không cần phải viết các câu lệnh SQL phức tạp. Thay vào đó, bạn có thể thao tác dữ liệu qua các đối tượng Java, tăng tính linh hoạt và giảm thiểu độ phức tạp khi chuyển đổi giữa các hệ thống lưu trữ dữ liệu khác nhau. Theo như định nghĩa thì các bạn có thể hiểu ngay rằng JPA cũng sử dụng cơ chế ORM và như trên ảnh đã nói nó sử dụng Hibernate để triển khai.
Tóm gọn lại chúng ta có thể hiểu như sau JPA là một tập hợp các interface định nghĩa các phương thức để thao tác với cơ sở dữ liệu. Các interface và phương thức này chỉ là quy định chung, không có logic cụ thể để thực hiện. Việc triển khai thực tế các phương thức đó được thực hiện bởi các framework như Hibernate, EclipseLink, hoặc OpenJPA. Trong đó, Hibernate là một trong những framework phổ biến nhất để triển khai JPA
III. JPA Architecture
1. EntityManagerFactory:
Vai trò: Đây là một factory để tạo ra các đối tượng EntityManager.
Chức năng chính:
Được khởi tạo một lần khi ứng dụng Spring Boot bắt đầu chạy, EntityManagerFactory quản lý cấu hình và các thuộc tính liên quan đến kết nối cơ sở dữ liệu (thông qua các thiết lập trong application.properties hoặc application.yml).
Nó chứa các thông tin cần thiết để kết nối và tương tác với database như URL, tên database, tài khoản truy cập.
Thông qua EntityManagerFactory, ta có thể tạo ra nhiều EntityManager để xử lý các yêu cầu của từng phiên giao dịch.
Đặc điểm: Thường là một singleton (chỉ có một đối tượng EntityManagerFactory cho mỗi ứng dụng), do đó, nó được khởi tạo một lần và sử dụng cho cả ứng dụng.
2. EntityManager:
Vai trò: Là lớp trung tâm trong JPA để thực hiện các thao tác với database.
Chức năng chính:
EntityManager cung cấp API để thao tác với các đối tượng Entity như thêm mới, cập nhật, xóa hoặc truy vấn dữ liệu.
Ví dụ, khi muốn lưu một thực thể User, bạn có thể dùng entityManager.persist(user).
EntityManager cũng quản lý vòng đời của các thực thể, bao gồm các trạng thái Transient (tạm thời), Managed (quản lý), và Detached (tách rời),…
Tính năng:
Nó cũng đảm bảo các thay đổi trên đối tượng Entity sẽ được đồng bộ với cơ sở dữ liệu khi một giao dịch được commit (khóa).
EntityManager có khả năng cache, lưu trữ các đối tượng đã truy xuất để giảm thiểu số lần truy vấn đến cơ sở dữ liệu, nhờ đó tối ưu hiệu năng.
Ví dụ sử dụng:
entityManager.persist(entity) để thêm mới thực thể vào database.
entityManager.merge(entity) để cập nhật thực thể nếu nó đã tồn tại.
entityManager.remove(entity) để xóa thực thể khỏi database.
entityManager.find(Entity.class, primaryKey) để tìm thực thể theo khóa chính.
3. EntityTransaction:
Vai trò: Quản lý các giao dịch của EntityManager.
Chức năng chính:
Trong các thao tác dữ liệu, giao dịch (transaction) là yếu tố quan trọng để đảm bảo tính nhất quán của dữ liệu. EntityTransaction đảm bảo rằng các thay đổi được lưu vào database khi giao dịch được commit.
Mỗi thao tác insert, update, hoặc delete đều cần một transaction để đảm bảo tính nhất quán dữ liệu. Nếu có lỗi xảy ra, bạn có thể rollback để huỷ các thay đổi.
Cách sử dụng:
begin(): Bắt đầu một giao dịch mới.
commit(): Kết thúc giao dịch, lưu thay đổi vào database.
rollback(): Hủy giao dịch nếu có lỗi xảy ra.
Ví dụ sử dụng:
EntityTransaction transaction = entityManager.getTransaction(); transaction.begin(); entityManager.persist(entity); transaction.commit();
4. Persistence:
Vai trò: Là một lớp tiện ích cho phép khởi tạo EntityManagerFactory.
Chức năng chính:
Persistence thường được sử dụng trong các ứng dụng không dùng Spring Boot để khởi tạo EntityManagerFactory.
Khi dùng Spring Boot, cấu hình EntityManagerFactory thường được thực hiện tự động qua các tệp cấu hình, nhưng với các ứng dụng JPA thuần (không có Spring), Persistence cung cấp cách trực tiếp để thiết lập kết nối.
Thường sử dụng trong ứng dụng JPA khi cần tạo EntityManagerFactory từ file cấu hình persistence.xml.
Ví dụ:
EntityManagerFactory emf = Persistence.createEntityManagerFactory("my-persistence-unit"); EntityManager em = emf.createEntityManager();
5. Query:
Vai trò: Là công cụ để thực thi các câu truy vấn tới database.
Chức năng chính:
Thực hiện các truy vấn động và tĩnh để lấy hoặc cập nhật dữ liệu trong database.
Cung cấp cả JPQL (Java Persistence Query Language) và native SQL để truy vấn dữ liệu.
JPQL cho phép truy vấn trên các đối tượng Entity thay vì các bảng như SQL. Đây là một điểm mạnh của JPA vì nó giúp code có tính linh hoạt và dễ bảo trì.
Ví dụ:
Query query = entityManager.createQuery("SELECT u FROM User u WHERE u.email = :email"); query.setParameter("email", email); User user = (User) query.getSingleResult();
6. Quy trình làm việc tổng quát trong JPA (Spring Boot):
Tạo EntityManager:
Spring Boot tự động tạo EntityManagerFactory dựa trên các thiết lập database. Khi ứng dụng cần thực hiện các thao tác CRUD, một EntityManager được lấy từ EntityManagerFactory.
Quản lý thực thể (Entity):
Các đối tượng Entity là đại diện cho các bảng trong database. Mỗi đối tượng Entity được ánh xạ tới một bảng và các trường của nó đại diện cho các cột trong bảng.
Khi bạn thực hiện thao tác trên một Entity, JPA sẽ ánh xạ và đồng bộ các thay đổi trên Entity này với bảng tương ứng trong database.
Thao tác với giao dịch (Transaction):
Trước khi thực hiện các thao tác thay đổi dữ liệu, một giao dịch (EntityTransaction) sẽ được bắt đầu. Sau khi thực hiện xong các thao tác (chẳng hạn như thêm mới, cập nhật hoặc xóa), giao dịch sẽ được commit.
Nếu có lỗi xảy ra trong quá trình thực hiện, rollback sẽ được gọi để đảm bảo rằng database không bị thay đổi bởi giao dịch không thành công.
Thực hiện truy vấn:
Bạn có thể sử dụng Query để tìm kiếm hoặc lọc dữ liệu theo yêu cầu. JPA hỗ trợ cả JPQL và native SQL. JPQL là một ngôn ngữ truy vấn hướng đối tượng, cho phép truy vấn trên các thực thể thay vì bảng, điều này giúp code trở nên mạnh mẽ và linh hoạt hơn.
Đồng bộ với Database:
Mọi thao tác trên Entity thông qua EntityManager sẽ được đồng bộ với database. Khi commit giao dịch, các thay đổi sẽ được lưu vào database.
7. Entity
1. Khái niệm
Entity là một khái niệm quan trọng đại diện cho một đối tượng trong ứng dụng có thể được ánh xạ (mapping) tới một bảng trong cơ sở dữ liệu. Đây là một phần của mô hình ORM (Object-Relational Mapping) trong JPA, giúp chuyển đổi dữ liệu giữa hệ thống đối tượng (Java objects) và cơ sở dữ liệu quan hệ.
Khi các bạn làm việc với JPA, chắc chắn các bạn sẽ gặp đến hai khái niệm @Entity và @Table. Đây là hai chú thích (annotation) quan trọng trong việc định nghĩa một lớp Java như một đối tượng mapping tới một bảng trong cơ sở dữ liệu. Chúng ta hãy cùng nhau phân biệt chúng:
@Entity: như đã nói ở trên Entity đại diện cho một đối tượng trong ứng dụng có thể được ánh xạ tới một bảng trong cở sở dữ liệu. Với một class được đánh dấu @Entity các instance của class đó sẽ là được quản lí bởi JPA, với mỗi đối tượng đó sẽ tương đương với một bản ghi trong cơ sở dữ liệu
@Table: Annotation này cung cấp các chi tiết về bảng trong cơ sở dữ liệu mà một entity sẽ ánh xạ tới. Trong khi @Entity chỉ định rằng lớp đó là một thực thể, @Table sẽ cho phép bạn thiết lập tên bảng cụ thể và các cấu hình bổ sung nếu cần.
Khi không sử dụng @Table, JPA sẽ tự động ánh xạ entity tới bảng có tên giống tên lớp. Tuy nhiên, khi tên bảng khác với tên lớp hoặc bạn muốn chỉ định thêm các thuộc tính khác như schema, catalog, hoặc uniqueConstraints (ràng buộc duy nhất trên các cột), @Table là cần thiết.
2. Entity Life Cycle
Transient
Khi một đối tượng entity mới được khởi tạo, nó ở trạng thái transient. Đây là trạng thái ban đầu của đối tượng khi nó không được kết nối với cơ sở dữ liệu và không có bất kỳ ràng buộc nào với persistence context (ngữ cảnh quản lý dữ liệu tạm thời) của JPA. Điều này có nghĩa là đối tượng vẫn chưa được lưu (persisted) vào cơ sở dữ liệu, do đó, không có bản ghi nào trong cơ sở dữ liệu tương ứng với đối tượng này.
Đặc điểm của trạng thái transient:
Persistence context không biết gì về đối tượng. Do đó, nó không thực hiện tự động các thao tác như INSERT hoặc theo dõi các thay đổi trên đối tượng.
Đối tượng trong trạng thái này giống như một Plain Old Java Object (POJO), không có bất kỳ đặc điểm nào liên quan đến JPA hay các hoạt động lưu trữ trong cơ sở dữ liệu.
Ví dụ:
Author author = new Author();
author.setFirstName("Thorben");
author.setLastName("Janssen");Managed
Một đối tượng entity sẽ chuyển sang trạng thái managed khi nó được lưu vào cơ sở dữ liệu hoặc được persistence context quản lý. Trong trạng thái này, persistence provider (chẳng hạn như Hibernate) sẽ theo dõi mọi thay đổi trên đối tượng và đồng bộ các thay đổi này với cơ sở dữ liệu. Các thao tác lưu (INSERT) hoặc cập nhật (UPDATE) sẽ được thực hiện tự động khi persistence context được "flush".
Cách chuyển đối tượng sang trạng thái managed:
Gọi persist(): Khi gọi EntityManager.persist với một đối tượng entity mới, JPA sẽ bắt đầu theo dõi đối tượng và sẽ tự động thực hiện các thao tác lưu trữ cần thiết.
Author author = new Author(); author.setFirstName("Thorben"); author.setLastName("Janssen"); em.persist(author); // Tạo bản ghi mới trong cơ sở dữ liệu.Tải đối tượng từ cơ sở dữ liệu: Khi sử dụng EntityManager.find, truy vấn JPQL, CriteriaQuery hoặc truy vấn SQL gốc để tải một entity từ cơ sở dữ liệu, đối tượng sẽ tự động ở trạng thái managed vì persistence context đã biết về đối tượng này.
Author author = em.find(Author.class, 1L);Merge một đối tượng detached: Khi gọi merge() trên EntityManager với một đối tượng đã detached (tách ra), đối tượng sẽ chuyển sang trạng thái managed và JPA sẽ tiếp tục theo dõi các thay đổi trên đó.\
3. Detached
Một entity được gọi là detached khi nó đã từng được quản lý (ở trạng thái managed) nhưng hiện tại không còn gắn với persistence context. Điều này thường xảy ra khi persistence context đóng lại, như sau khi một transaction hoàn thành hoặc khi request của ứng dụng kết thúc.
Đặc điểm của trạng thái detached:
Persistence context không còn theo dõi đối tượng này. Do đó, bất kỳ thay đổi nào thực hiện trên đối tượng detached sẽ không được tự động đồng bộ với cơ sở dữ liệu.
Để đồng bộ các thay đổi lên cơ sở dữ liệu, đối tượng cần phải được gắn lại (reattached) vào persistence context.
Khi nào một đối tượng bị tách ra?
Khi persistence context kết thúc, như sau khi một request hoàn tất hoặc sau khi commit một transaction.
Bạn cũng có thể tách một đối tượng ra một cách thủ công bằng cách sử dụng EntityManager.detach.
Reattaching an Entity
Nếu bạn muốn cập nhật thông tin của một đối tượng detached trong cơ sở dữ liệu, bạn cần phải gắn lại (reattach) nó vào persistence context để đưa đối tượng trở lại trạng thái managed.
Cách gắn lại đối tượng detached:
Gọi merge(): Khi gọi EntityManager.merge, đối tượng detached sẽ được gắn lại vào persistence context. merge() sẽ tạo ra một bản sao của đối tượng detached và sau đó cập nhật các thay đổi vào cơ sở dữ liệu.
Gọi update() trên Hibernate Session: Tương tự như merge, update() trên session của Hibernate cũng gắn lại một đối tượng detached để nó trở thành managed.
Removed
Khi gọi phương thức remove() trên EntityManager, đối tượng entity sẽ chuyển sang trạng thái removed. Trạng thái này đánh dấu rằng đối tượng sẽ bị xóa khỏi cơ sở dữ liệu trong lần flush tiếp theo. Tuy nhiên, đối tượng không bị xóa ngay lập tức khỏi cơ sở dữ liệu.
Cách thức hoạt động của trạng thái removed:
Đánh dấu cho thao tác xóa: Khi một đối tượng ở trạng thái removed, persistence context chỉ đơn giản là đánh dấu đối tượng này để xóa trong lần flush tiếp theo. Đối tượng vẫn tồn tại trong bộ nhớ cho đến khi thao tác flush hoặc commit được thực hiện.
Tự động thực hiện lệnh DELETE: Trong lần flush kế tiếp, persistence provider (như Hibernate) sẽ tự động tạo câu lệnh DELETE SQL để xóa đối tượng này khỏi cơ sở dữ liệu.
Vậy là mình đã trình bày xong về kiến trúc của JPA. Trong phần tiếp theo, chúng ta sẽ đi sâu hơn vào Hibernate, một trong những triển khai phổ biến nhất của JPA. Chúng ta cũng sẽ so sánh hiệu suất giữa JPA và JDBC, từ đó đánh giá xem khi nào nên sử dụng từng công nghệ để tối ưu hiệu suất ứng dụng. Hãy cùng tiếp tục khám phá!





