รูปแบบ DAO ใน Java

1. ภาพรวม

เข้าถึงข้อมูลวัตถุ (DAO) รูปแบบที่เป็นรูปแบบโครงสร้างที่ช่วยให้เราแยกชั้นสมัคร / ธุรกิจจากชั้นวิริยะ (มักจะเป็นฐานข้อมูลเชิงสัมพันธ์ แต่มันอาจจะเป็นกลไกการติดตาอื่น ๆ ) โดยใช้ API

การทำงานของ API นี้คือการซ่อนความซับซ้อนทั้งหมดที่เกี่ยวข้องกับการดำเนินการ CRUD ในกลไกการจัดเก็บข้อมูลพื้นฐานจากแอปพลิเคชัน สิ่งนี้ทำให้ทั้งสองเลเยอร์สามารถพัฒนาแยกกันโดยไม่รู้อะไรเกี่ยวกับกันและกัน

ในบทช่วยสอนนี้เราจะเจาะลึกเกี่ยวกับการนำรูปแบบไปใช้งานและเราจะได้เรียนรู้วิธีใช้สำหรับการโทรไปยังผู้จัดการนิติบุคคล JPA

2. การใช้งานที่เรียบง่าย

เพื่อทำความเข้าใจว่ารูปแบบ DAO ทำงานอย่างไรให้สร้างตัวอย่างพื้นฐาน

สมมติว่าเราต้องการพัฒนาแอปพลิเคชันที่จัดการผู้ใช้ เพื่อให้รูปแบบโดเมนของโปรแกรมอย่างสมบูรณ์ไม่เชื่อเรื่องพระเจ้าเกี่ยวกับฐานข้อมูลที่เราจะสร้างการเรียนง่าย DAO ที่จะดูแลของการรักษาองค์ประกอบเหล่านี้หลุดพ้นอย่างประณีตจากกันและกัน

2.1. คลาสโดเมน

เนื่องจากแอปพลิเคชันของเราจะทำงานร่วมกับผู้ใช้เราจึงต้องกำหนดคลาสเดียวสำหรับการใช้งานโมเดลโดเมน:

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

ผู้ใช้ระดับเป็นเพียงภาชนะธรรมดาสำหรับข้อมูลของผู้ใช้จึงไม่ได้ดำเนินการใด ๆ มูลค่าพฤติกรรมอื่น ๆ ที่เน้นหนัก

แน่นอนว่าตัวเลือกการออกแบบที่เกี่ยวข้องมากที่สุดที่เราต้องทำคือทำอย่างไรให้แอปพลิเคชันที่ใช้คลาสนี้แยกออกจากกลไกการคงอยู่ที่สามารถนำไปใช้ได้ในบางจุด

นั่นเป็นปัญหาที่รูปแบบ DAO พยายามแก้ไข

2.2. DAO API

มากำหนดเลเยอร์ DAO พื้นฐานกันเพื่อที่เราจะได้เห็นว่ามันจะทำให้โมเดลโดเมนแยกออกจากเลเยอร์การคงอยู่ได้อย่างไร

นี่คือ DAO API:

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

จากมุมมองตานกก็เป็นที่ชัดเจนที่จะเห็นว่าดาวอินเตอร์เฟซที่กำหนด API นามธรรมว่าการดำเนินการดำเนินการ CRUD บนวัตถุชนิดT

เนื่องจากอินเทอร์เฟซที่มีอยู่ในระดับสูงจึงเป็นเรื่องง่ายที่จะสร้างการใช้งานที่เป็นรูปธรรมและละเอียดซึ่งทำงานกับวัตถุของผู้ใช้

2.3. UserDaoชั้น

มากำหนดการใช้งานอินเทอร์เฟซ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); } }

UserDaoการดำเนินการเรียนของทุกฟังก์ชั่นที่จำเป็นสำหรับการเรียกการปรับปรุงและการลบผู้ใช้วัตถุ

เพื่อประโยชน์ของความเรียบง่ายที่ผู้ใช้รายการการกระทำเช่นฐานข้อมูลในหน่วยความจำซึ่งเป็นประชากรที่มีคู่ของผู้ใช้วัตถุในตัวสร้าง

แน่นอนว่ามันง่ายที่จะ refactor วิธีการอื่น ๆ ดังนั้นจึงสามารถทำงานร่วมกับฐานข้อมูลเชิงสัมพันธ์ได้

แม้ว่าคลาสUserและUserDao จะอยู่ร่วมกันอย่างอิสระภายในแอปพลิเคชันเดียวกัน แต่เรายังคงต้องดูว่าสามารถใช้ส่วนหลังเพื่อซ่อนเลเยอร์การคงอยู่จากตรรกะแอปพลิเคชันได้อย่างไร:

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")); } }

ตัวอย่างนี้ถูกสร้างขึ้น แต่แสดงให้เห็นโดยสรุปแรงจูงใจที่อยู่เบื้องหลังรูปแบบ DAO ในกรณีนี้วิธีการหลักเพียงแค่ใช้อินสแตนซ์UserDaoเพื่อดำเนินการ CRUD กับอ็อบเจ็กต์Userบางตัว

แง่มุมที่เกี่ยวข้องมากที่สุดของกระบวนการนี้คือวิธีที่UserDaoซ่อนรายละเอียดระดับต่ำทั้งหมดจากแอปพลิเคชันเกี่ยวกับวิธีการคงอยู่อัปเดตและลบวัตถุ

3. การใช้รูปแบบด้วย JPA

มีแนวโน้มโดยทั่วไปในหมู่นักพัฒนาที่คิดว่าการเปิดตัว JPA ลดระดับฟังก์ชันการทำงานของรูปแบบ DAO เป็นศูนย์เนื่องจากรูปแบบดังกล่าวกลายเป็นเพียงชั้นของนามธรรมและความซับซ้อนอีกชั้นหนึ่งที่นำมาใช้โดยผู้จัดการนิติบุคคลของ JPA

ไม่ต้องสงสัยเลยว่าในบางสถานการณ์นี่เป็นเรื่องจริง ถึงกระนั้นบางครั้งเราก็ต้องการให้แอปพลิเคชันของเราแสดงวิธีเฉพาะโดเมนเพียงไม่กี่วิธีของ API ของผู้จัดการเอนทิตี ในกรณีเช่นนี้รูปแบบ DAO มีที่มา

3.1. JpaUserDaoชั้น

จากที่กล่าวมาเรามาสร้างการใช้งานอินเทอร์เฟซDaoใหม่เพื่อให้เราสามารถดูว่าสามารถสรุปฟังก์ชันการทำงานที่ผู้จัดการเอนทิตีของ JPA จัดเตรียมไว้ให้ได้อย่างไร:

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; } } }

JpaUserDaoระดับความสามารถในการทำงานร่วมกับฐานข้อมูลเชิงสัมพันธ์ใด ๆ ที่ได้รับการสนับสนุนโดยการดำเนินการ JPA

นอกจากนี้หากเราดูชั้นเรียนอย่างใกล้ชิดเราจะทราบว่าการใช้ Composition และ Dependency Injection ช่วยให้เราสามารถเรียกเฉพาะวิธีการจัดการเอนทิตีที่แอปพลิเคชันของเราต้องการเท่านั้น

พูดง่ายๆก็คือเรามี API ที่ปรับแต่งเฉพาะโดเมนแทนที่จะเป็น API ของผู้จัดการเอนทิตีทั้งหมด

3.2. การปรับโครงสร้างคลาสผู้ใช้ใหม่

ในกรณีนี้เราจะใช้ Hibernate เป็นการใช้งานเริ่มต้นของ JPA ดังนั้นเราจะสร้างคลาสUser ใหม่ตามลำดับ:

@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. การบูต JPA Entity Manager โดยใช้โปรแกรม

สมมติว่าเรามีอินสแตนซ์การทำงานของ MySQL ที่ทำงานในพื้นที่หรือจากระยะไกลอยู่แล้วและตารางฐานข้อมูล"ผู้ใช้"ที่มีบันทึกผู้ใช้บางส่วนเราจำเป็นต้องได้รับตัวจัดการเอนทิตี JPA เพื่อให้เราสามารถใช้คลาสJpaUserDaoเพื่อดำเนินการ CRUD ใน ฐานข้อมูล.

ในกรณีส่วนใหญ่เราทำได้ผ่านไฟล์"persistence.xml"ทั่วไปซึ่งเป็นแนวทางมาตรฐาน

ในกรณีนี้เราจะใช้แนวทาง"xml-less"และรับตัวจัดการเอนทิตีด้วย Java ธรรมดาผ่านคลาสEntityManagerFactoryBuilderImpl ที่มีประโยชน์ของ Hibernate

สำหรับคำอธิบายโดยละเอียดเกี่ยวกับวิธีบูตการใช้งาน JPA กับ Java โปรดอ่านบทความนี้

3.4. UserApplicationชั้น

สุดท้ายเราจะรีแฟคเตอร์คลาสUserApplicationเริ่มต้นเพื่อให้สามารถทำงานกับอินสแตนซ์JpaUserDaoและดำเนินการ CRUD ในเอนทิตีผู้ใช้ :

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); } }

แม้ว่าตัวอย่างจะค่อนข้าง จำกัด แต่ก็ยังมีประโยชน์สำหรับการสาธิตวิธีการรวมฟังก์ชันการทำงานของรูปแบบ DAO กับรูปแบบที่ผู้จัดการเอนทิตีมีให้

ในแอปพลิเคชันส่วนใหญ่จะมีเฟรมเวิร์ก DI ซึ่งมีหน้าที่ในการฉีดอินสแตนซ์JpaUserDaoลงในคลาสUserApplication เพื่อความเรียบง่ายเราจึงละเว้นรายละเอียดของกระบวนการนี้

จุดที่เกี่ยวข้องมากที่สุดกับความเครียดนี่คือวิธีJpaUserDaoระดับจะช่วยให้UserApplicationระดับสมบูรณ์ไม่เชื่อเรื่องพระเจ้าเกี่ยวกับวิธีการดำเนินงานชั้นคงดำเนิน CRUD

นอกจากนี้เรายังสามารถสลับ MySQL สำหรับ RDBMS อื่น ๆ (และแม้กระทั่งสำหรับฐานข้อมูลแบบแบน) ที่อยู่ไกลออกไปและถึงกระนั้นแอปพลิเคชันของเราก็ยังคงทำงานได้ตามที่คาดไว้เนื่องจากระดับของสิ่งที่เป็นนามธรรมที่มีให้โดยอินเทอร์เฟซDaoและผู้จัดการเอนทิตี .

4. สรุป

ในบทความนี้เราได้เจาะลึกถึงแนวคิดหลักของรูปแบบ DAO วิธีการนำไปใช้ใน Java และวิธีใช้งานบนตัวจัดการเอนทิตีของ JPA

ตามปกติตัวอย่างโค้ดทั้งหมดที่แสดงในบทความนี้มีอยู่ใน GitHub