Generic DAO

เป็นความพยายามในการใช้ Java Generic เพื่อสร้าง Class DAO เพียงตัวเดียวแต่สามารถจัดการข้อมูลได้หลาย Table ไม่ว่าจะเป็นงาน Insert Update Delete Select แบบระบุ key หรือ Select ทั้ง table ซึ่งงานเหล่านี้เป็นงานพื้นฐานที่ต้องมีอยู่แล้ว หากต้องการเขียน Select ที่มีเงื่อนไขซับซ้อนมากขึ้น ก็เพียงแค่ทำการสืบทอด Generic DAO แล้วเพิ่ม method สำหรับ Select ที่ว่านั้น

จากที่ผมรวบรวมข้อมูลมา สามารถแบ่งสายของคนที่เล่น Generic ได้สองสาย

  • สาย Abstract Class เอาไว้สร้าง DAO แบบหนึ่ง Class ต่อหนึ่ง Table ตามปกติ
  • สาย Concrete Class (Java class ธรรมดา) คลาสเดียวแล้วสามารถใช้ได้ทุก Table

สาย Concrete Class

  1. สร้าง interface ขึ้นมาหนึ่งตัว
    package com.magicalcyber.blog.genericdao.dao;
    
    import java.io.Serializable;
    
    /**
     * User: MagicalCyber
     * Date: 10/22/13
     * Time: 2:16 PM
     */
    public interface GenericDao<T, PK extends Serializable> {
        T create(T entity) throws Exception;
        T findById(PK id) throws Exception;
        T update(T entity) throws Exception;
        void delete(PK id) throws Exception;
    }
    
  2. สร้าง Implement Class โดยเลือกเอาเทคโนโลยีที่ต้องการ ในที่นี้ผมใช้ JPA
    package com.magicalcyber.blog.genericdao.dao.jpa;
    
    import com.magicalcyber.blog.genericdao.dao.GenericDao;
    
    import javax.persistence.EntityManager;
    import javax.persistence.EntityManagerFactory;
    import javax.persistence.Persistence;
    import java.io.Serializable;
    
    /**
     * User: MagicalCyber
     * Date: 10/22/13
     * Time: 2:20 PM
     */
    public class JpaImplGenericDao<T, PK extends Serializable> implements GenericDao<T, PK> {
    
        private EntityManager em;
        private Class<T> type;
    
        public JpaImplGenericDao(Class<T> type) {
            EntityManagerFactory emf = Persistence.createEntityManagerFactory("generic-unit");
            em = emf.createEntityManager();
            this.type = type;
        }
    
        @Override
        public T create(T entity) throws Exception {
            em.getTransaction().begin();
            em.persist(entity);
            em.flush();
            em.getTransaction().commit();
            return entity;
        }
    
        @Override
        public T findById(PK id) throws Exception {
            return em.find(type, id);
        }
    
        @Override
        public T update(T entity) throws Exception {
            em.getTransaction().begin();
            T updatedEntity = em.merge(entity);
            em.flush();
            em.getTransaction().commit();
            return updatedEntity;
        }
    
        @Override
        public void delete(PK id) throws Exception {
            em.getTransaction().begin();
            T entity = em.find(type, id);
            em.remove(entity);
            em.flush();
            em.getTransaction().commit();
        }
    }
    
  3. ผมสร้าง Entity ขึ้นมาหนึ่งตัวเพื่อทำการทดสอบดังนี้
    package com.magicalcyber.blog.genericdao.entity;
    
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.validation.constraints.NotNull;
    
    /**
     * User: MagicalCyber
     * Date: 10/22/13
     * Time: 2:33 PM
     */
    @Entity
    public class Student {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        @NotNull
        private String name;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    
  4. ผมทดสอบโดยสร้าง Test Case ดังนี้ ผ่านฉลุย
    package com.magicalcyber.blog.genericdao.dao.jpa;
    
    import com.magicalcyber.blog.genericdao.dao.GenericDao;
    import com.magicalcyber.blog.genericdao.entity.Student;
    import org.junit.Test;
    
    import static org.junit.Assert.*;
    
    /**
     * User: MagicalCyber
     * Date: 10/22/13
     * Time: 2:35 PM
     */
    public class JpaImplGenericDaoTest {
        @Test
        public void testDao() throws Exception {
            String studentName1 = "MagicalCyber";
            String studentName2 = "Student2";
    
            GenericDao<Student, Long> dao = new JpaImplGenericDao<Student, Long>(Student.class);
    
            // Test create student, must have id
            Student student = new Student();
            student.setName(studentName1);
            Student tempStudent1 = dao.create(student);
            assertNotNull(tempStudent1.getId());
    
            // Test update new name
            tempStudent1.setName(studentName2);
            Student tempStudent2 = dao.update(tempStudent1);
            assertNotEquals(studentName1, tempStudent2.getName());
    
            // Test Delete
            dao.delete(tempStudent2.getId());
            Student tempStudent3 = dao.findById(tempStudent2.getId());
            assertNull(tempStudent3);
        }
    }
    

สาย Abstract Class

  1. สร้าง Abstract ขึ้นมาหนึ่งตัว ซึ่งหน้าตาเหมือน class ของก่อนหน้านี้แทบจะทุกประการ ยกเว้นใน constructure เราจะต้องหา type เอง ซึ่งก่อนหน้านี้เราส่งมา

    package com.magicalcyber.blog.genericdao.dao.jpa;
    
    import com.magicalcyber.blog.genericdao.dao.GenericDao;
    
    import javax.persistence.EntityManager;
    import javax.persistence.EntityManagerFactory;
    import javax.persistence.Persistence;
    import java.io.Serializable;
    import java.lang.reflect.ParameterizedType;
    import java.lang.reflect.Type;
    
    /**
     * User: MagicalCyber
     * Date: 10/22/13
     * Time: 2:20 PM
     */
    public abstract class JpaImplGenericDao2<T, PK extends Serializable> implements GenericDao<T, PK> {
    
        private EntityManager em;
        private Class<T> type;
    
        public JpaImplGenericDao2() {
            EntityManagerFactory emf = Persistence.createEntityManagerFactory("generic-unit");
            em = emf.createEntityManager();
    
            Type dataType = getClass().getGenericSuperclass();
            if (dataType instanceof ParameterizedType) {
                ParameterizedType paramType = (ParameterizedType) dataType;
                type = (Class<T>) paramType.getActualTypeArguments()[0];
            } else if (dataType instanceof Class) {
                type = (Class<T>) dataType;
            }
        }
    
        @Override
        public T create(T entity) throws Exception {
            em.getTransaction().begin();
            em.persist(entity);
            em.flush();
            em.getTransaction().commit();
            return entity;
        }
    
        @Override
        public T findById(PK id) throws Exception {
            return em.find(type, id);
        }
    
        @Override
        public T update(T entity) throws Exception {
            em.getTransaction().begin();
            T updatedEntity = em.merge(entity);
            em.flush();
            em.getTransaction().commit();
            return updatedEntity;
        }
    
        @Override
        public void delete(PK id) throws Exception {
            em.getTransaction().begin();
            T entity = em.find(type, id);
            em.remove(entity);
            em.flush();
            em.getTransaction().commit();
        }
    }
    
    
  2. สร้าง JpaStudentDAO เพื่อ extends abstract class โดยข้างในไม่ต้องทำอะไรเลย
    package com.magicalcyber.blog.genericdao.dao.jpa;
    
    import com.magicalcyber.blog.genericdao.entity.Student;
    
    /**
     * User: MagicalCyber
     * Date: 11/6/13
     * Time: 11:28 PM
     */
    public class JpaStudentDao extends JpaImplGenericDao2<Student, Long>  {
    
    }
    
    
  3. ทดสอบโดยเพิ่ม Unit Test อีก method ก็ผ่านฉลุย
    package com.magicalcyber.blog.genericdao.dao.jpa;
    
    import com.magicalcyber.blog.genericdao.dao.GenericDao;
    import com.magicalcyber.blog.genericdao.entity.Student;
    import org.junit.Test;
    
    import static org.junit.Assert.*;
    
    /**
     * User: MagicalCyber
     * Date: 10/22/13
     * Time: 2:35 PM
     */
    public class JpaImplGenericDaoTest {
        @Test
        public void testDao() throws Exception {
            String studentName1 = "MagicalCyber";
            String studentName2 = "Student2";
    
            GenericDao<Student, Long> dao = new JpaImplGenericDao<Student, Long>(Student.class);
            // Test create student, must have id
            Student student = new Student();
            student.setName(studentName1);
            Student tempStudent1 = dao.create(student);
            assertNotNull(tempStudent1.getId());
    
            // Test update new name
            tempStudent1.setName(studentName2);
            Student tempStudent2 = dao.update(tempStudent1);
            assertNotEquals(studentName1, tempStudent2.getName());
    
            // Test Delete
            dao.delete(tempStudent2.getId());
            Student tempStudent3 = dao.findById(tempStudent2.getId());
            assertNull(tempStudent3);
        }
    
        @Test
        public void testAbstractDao() throws Exception {
            String studentName1 = "MagicalCyber";
            String studentName2 = "Student2";
    
            JpaStudentDao dao = new JpaStudentDao();
            // Test create student, must have id
            Student student = new Student();
            student.setName(studentName1);
            Student tempStudent1 = dao.create(student);
            assertNotNull(tempStudent1.getId());
    
            // Test update new name
            tempStudent1.setName(studentName2);
            Student tempStudent2 = dao.update(tempStudent1);
            assertNotEquals(studentName1, tempStudent2.getName());
    
            // Test Delete
            dao.delete(tempStudent2.getId());
            Student tempStudent3 = dao.findById(tempStudent2.getId());
            assertNull(tempStudent3);
        }
    }
    

สรุป

โดยส่วนตัวผมชอบวิธีแรกมากกว่า เพราะว่าเราไม่ต้องสร้าง DAO เพิ่ม หากจะเขียน query ที่ซับซ้อนค่อยสร้างใหม่ แล้ว extends ของเดิมมา ส่วนสาย Abstract จะการมองว่ามันเป็น function พื้นฐานที่ทุก DAO ต้องมี เลยทำเป็นคลาสกลางไว้ให้คนอื่นสืบทอด ชอบอันใหนก็จัดเลยครับ

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s