Mẫu DAO trong Java

1. Khái quát chung

Mẫu Đối tượng truy cập dữ liệu (DAO) là một mẫu cấu trúc cho phép chúng ta tách lớp ứng dụng / nghiệp vụ khỏi lớp bền vững (thường là cơ sở dữ liệu quan hệ, nhưng nó có thể là bất kỳ cơ chế bền vững nào khác) bằng cách sử dụng một API trừu tượng .

Chức năng của API này là ẩn khỏi ứng dụng tất cả những phức tạp liên quan đến việc thực hiện các hoạt động CRUD trong cơ chế lưu trữ cơ bản. Điều này cho phép cả hai lớp phát triển riêng biệt mà không cần biết gì về nhau.

Trong hướng dẫn này, chúng ta sẽ đi sâu vào triển khai của mẫu và chúng ta sẽ tìm hiểu cách sử dụng nó để trừu tượng hóa các cuộc gọi đến trình quản lý thực thể JPA.

2. Thực hiện đơn giản

Để hiểu cách hoạt động của mẫu DAO, hãy tạo một ví dụ cơ bản.

Giả sử rằng chúng tôi muốn phát triển một ứng dụng quản lý người dùng. Để giữ cho mô hình miền của ứng dụng hoàn toàn bất khả tri về cơ sở dữ liệu, chúng tôi sẽ tạo một lớp DAO đơn giản sẽ đảm nhận việc giữ cho các thành phần này được tách biệt gọn gàng với nhau .

2.1. Lớp miền

Vì ứng dụng của chúng tôi sẽ hoạt động với người dùng, chúng tôi chỉ cần xác định một lớp để triển khai mô hình miền của nó:

public class User { private String name; private String email; // constructors / standard setters / getters }

Lớp Người dùng chỉ là một vùng chứa đơn giản cho dữ liệu người dùng, vì vậy nó không triển khai bất kỳ hành vi nào khác đáng để nhấn mạnh.

Tất nhiên, lựa chọn thiết kế phù hợp nhất mà chúng ta cần thực hiện ở đây là làm thế nào để giữ cho ứng dụng sử dụng lớp này bị cô lập khỏi bất kỳ cơ chế bền bỉ nào có thể được triển khai vào một thời điểm nào đó.

Chà, đó chính xác là vấn đề mà mẫu DAO cố gắng giải quyết.

2.2. API DAO

Hãy xác định một lớp DAO cơ bản, để chúng ta có thể thấy cách nó có thể giữ cho mô hình miền được tách biệt hoàn toàn khỏi lớp bền vững.

Đây là API DAO:

public interface Dao { Optional get(long id); List getAll(); void save(T t); void update(T t, String[] params); void delete(T t); }

Từ xem mắt của một con chim, thì rõ ràng để thấy rằng Dao giao diện định nghĩa một API trừu tượng mà hoạt động Thực hiện CRUD trên các đối tượng của loại T .

Do mức độ trừu tượng cao mà giao diện cung cấp, thật dễ dàng để tạo một triển khai cụ thể, chi tiết hoạt động với các đối tượng Người dùng .

2.3. Các UserDao Lớp

Hãy xác định một triển khai dành riêng cho người dùng của giao diện Dao :

public class UserDao implements Dao { private List users = new ArrayList(); public UserDao() { users.add(new User("John", "[email protected]")); users.add(new User("Susan", "[email protected]")); } @Override public Optional get(long id) { return Optional.ofNullable(users.get((int) id)); } @Override public List getAll() { return users; } @Override public void save(User user) { users.add(user); } @Override public void update(User user, String[] params) { user.setName(Objects.requireNonNull( params[0], "Name cannot be null")); user.setEmail(Objects.requireNonNull( params[1], "Email cannot be null")); users.add(user); } @Override public void delete(User user) { users.remove(user); } }

Lớp UserDao triển khai tất cả các chức năng cần thiết để tìm nạp, cập nhật và xóa các đối tượng Người dùng .

Vì mục đích đơn giản, Danh sách người dùng hoạt động giống như một cơ sở dữ liệu trong bộ nhớ, được điền vào một vài đối tượng Người dùng trong hàm tạo .

Tất nhiên, thật dễ dàng để cấu trúc lại các phương thức khác, vì vậy, chẳng hạn như chúng có thể hoạt động với cơ sở dữ liệu quan hệ.

Trong khi cả hai lớp UserUserDao cùng tồn tại độc lập trong cùng một ứng dụng, chúng ta vẫn cần xem cách sau này có thể được sử dụng như thế nào để giữ lớp bền vững ẩn khỏi logic ứng dụng:

public class UserApplication { private static Dao userDao; public static void main(String[] args) { userDao = new UserDao(); User user1 = getUser(0); System.out.println(user1); userDao.update(user1, new String[]{"Jake", "[email protected]"}); User user2 = getUser(1); userDao.delete(user2); userDao.save(new User("Julie", "[email protected]")); userDao.getAll().forEach(user -> System.out.println(user.getName())); } private static User getUser(long id) { Optional user = userDao.get(id); return user.orElseGet( () -> new User("non-existing user", "no-email")); } }

Ví dụ được tạo ra, nhưng tóm lại, nó cho thấy các động lực đằng sau mô hình DAO. Trong trường hợp này, phương thức chính chỉ sử dụng một cá thể UserDao để thực hiện các hoạt động CRUD trên một vài đối tượng Người dùng .

Khía cạnh phù hợp nhất của quá trình này là cách UserDao ẩn khỏi ứng dụng tất cả các chi tiết cấp thấp về cách các đối tượng được duy trì, cập nhật và xóa .

3. Sử dụng mẫu với JPA

Các nhà phát triển có xu hướng chung nghĩ rằng việc phát hành JPA đã hạ cấp chức năng của mẫu DAO xuống 0, vì mẫu này chỉ trở thành một lớp trừu tượng và phức tạp khác được thực hiện trên lớp do người quản lý thực thể của JPA cung cấp.

Không nghi ngờ gì nữa, trong một số trường hợp, điều này là đúng. Mặc dù vậy , đôi khi chúng tôi chỉ muốn hiển thị cho ứng dụng của mình một vài phương pháp dành riêng cho miền cụ thể của API của trình quản lý thực thể. Trong những trường hợp như vậy, mô hình DAO có vị trí của nó.

3.1. Các JpaUserDao Lớp

Như đã nói, hãy tạo một triển khai mới của giao diện Dao , để chúng ta có thể thấy nó có thể đóng gói chức năng mà trình quản lý thực thể của JPA cung cấp như thế nào:

public class JpaUserDao implements Dao { private EntityManager entityManager; // standard constructors @Override public Optional get(long id) { return Optional.ofNullable(entityManager.find(User.class, id)); } @Override public List getAll() { Query query = entityManager.createQuery("SELECT e FROM User e"); return query.getResultList(); } @Override public void save(User user) { executeInsideTransaction(entityManager -> entityManager.persist(user)); } @Override public void update(User user, String[] params) { user.setName(Objects.requireNonNull(params[0], "Name cannot be null")); user.setEmail(Objects.requireNonNull(params[1], "Email cannot be null")); executeInsideTransaction(entityManager -> entityManager.merge(user)); } @Override public void delete(User user) { executeInsideTransaction(entityManager -> entityManager.remove(user)); } private void executeInsideTransaction(Consumer action) { EntityTransaction tx = entityManager.getTransaction(); try { tx.begin(); action.accept(entityManager); tx.commit(); } catch (RuntimeException e) { tx.rollback(); throw e; } } }

Lớp JpaUserDao có khả năng làm việc với bất kỳ cơ sở dữ liệu quan hệ nào được hỗ trợ bởi việc triển khai JPA.

Hơn nữa, nếu chúng ta xem xét kỹ lớp này, chúng ta sẽ nhận ra cách sử dụng Composition và Dependency Injection cho phép chúng ta chỉ gọi các phương thức trình quản lý thực thể mà ứng dụng của chúng ta yêu cầu.

Nói một cách đơn giản, chúng tôi có một API được điều chỉnh theo miền cụ thể, thay vì toàn bộ API của người quản lý tổ chức.

3.2. Sắp xếp các tài Lớp

Trong trường hợp này, chúng tôi sẽ sử dụng Hibernate làm triển khai mặc định của JPA, do đó chúng tôi sẽ cấu trúc lại lớp Người dùng cho phù hợp:

@Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String name; private String email; // standard constructors / setters / getters }

3.3. Khởi động trình quản lý thực thể JPA theo lập trình

Assuming that we already have a working instance of MySQL running either locally or remotely and a database table “users” populated with some user records, we need to get a JPA entity manager, so we can use the JpaUserDao class for performing CRUD operations in the database.

In most cases, we accomplish this via the typical “persistence.xml” file, which is the standard approach.

In this case, we'll take an “xml-less” approach and get the entity manager with plain Java through Hibernate's handy EntityManagerFactoryBuilderImpl class.

For a detailed explanation on how to bootstrap a JPA implementation with Java, please check this article.

3.4. The UserApplication Class

Finally, let's refactor the initial UserApplication class, so it can work with a JpaUserDao instance and execute CRUD operations on the User entities:

public class UserApplication { private static Dao jpaUserDao; // standard constructors public static void main(String[] args) { User user1 = getUser(1); System.out.println(user1); updateUser(user1, new String[]{"Jake", "[email protected]"}); saveUser(new User("Monica", "[email protected]")); deleteUser(getUser(2)); getAllUsers().forEach(user -> System.out.println(user.getName())); } public static User getUser(long id) { Optional user = jpaUserDao.get(id); return user.orElseGet( () -> new User("non-existing user", "no-email")); } public static List getAllUsers() { return jpaUserDao.getAll(); } public static void updateUser(User user, String[] params) { jpaUserDao.update(user, params); } public static void saveUser(User user) { jpaUserDao.save(user); } public static void deleteUser(User user) { jpaUserDao.delete(user); } }

Even when the example is pretty limited indeed, it remains useful for demonstrating how to integrate the DAO pattern's functionality with the one that the entity manager provides.

In most applications, there's a DI framework, which is responsible for injecting a JpaUserDao instance into the UserApplication class. For simplicity's sake, we've omitted the details of this process.

Các điểm phù hợp nhất với stress đây là cách các JpaUserDao lớp giúp giữ UserApplication lớp hoàn toàn thuyết bất khả tri về cách thức lớp kiên trì hoạt động Thực hiện CRUD .

Ngoài ra, chúng tôi có thể hoán đổi MySQL cho bất kỳ RDBMS nào khác (và thậm chí đối với cơ sở dữ liệu phẳng) hơn nữa, và ứng dụng của chúng tôi vẫn sẽ tiếp tục hoạt động như mong đợi, nhờ vào mức độ trừu tượng được cung cấp bởi giao diện Dao và trình quản lý thực thể. .

4. Kết luận

Trong bài viết này, chúng tôi đã đi sâu vào các khái niệm chính của mẫu DAO, cách triển khai nó trong Java và cách sử dụng nó trên trình quản lý thực thể của JPA.

Như thường lệ, tất cả các mẫu mã hiển thị trong bài viết này đều có sẵn trên GitHub.