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