Spring กับ IbatisDao ภาค 1

January 22nd, 2009 by roofimon Leave a reply »

Spring กับ DAO
หลังจากที่เราจัดการเชื่อมต่อกับฐานข้อมูลแล้วดังนั้นตอนนี้เหมือนเรามี ท่อสำหรับเชื่อมต่อกับตาน้ำบาดาล
แล้วต่อไปเราจะเริ่ม ใส่เครื่องสูบน้ำเข้าไปบนต่อนั้นโดย สิ่งที่จะทำการสูบข้อมูลออกมาจาก
ฐานข้อมูลนั้นสำหรับสปริงเราเรียกว่าชั้นของดาว DAO(Data Access Object) ซึ่งชื่อนี้ก็ไม่ใช่อื่นไกลมัน
คือหนึ่งใน Design Pattern อันโด่งดังนั่นเอง โดยสรุปหน้าที่หลักของ DAO นั้นคือเป็นคนที่มีหน้าที่ในการ
ควบคุม Operation ฑื้นฐานต่างๆที่เราพึงจะกระทำต่อหนึ่งตารางในระบบฐานข้อมูลนั่นคือ
CRUD(Create Read/Retrive Update Delete)
นั่นเองและย้ำเราจะมีหนึ่ง DAO ต่อหนึ่งตารางเท่านั้น
และต่อไปอย่างที่ได้เกริ่นไว้แล้วว่าผมจะใช้ iBatis เข้ามาช่วยในการ map ข้อมูลในตารางออกมาเป็น
Java Object ดังนั้นผมจึงขอ assume ว่าทุกคนมีพื้นเรื่อง iBatis แล้วครับ (เหอๆๆๆๆ เดี่ยวจะกลับมาเล่าอีกที)
แต่ก่อนอื่นเอาคร่าวๆก่อนแล้วกันครับ ในตัวอย่างนี้เรามีตารางชื่อ USERS ที่มี Schema ดังนี้

CREATE TABLE /*!32312 IF NOT EXISTS*/ `users` (
  `USR_ID` tinyint(3) unsigned NOT NULL DEFAULT '0',
  `USR_NAME` varchar(50) NOT NULL DEFAULT '',
  `USR_PWD` varchar(50) DEFAULT NULL,
  `USR_LEVEL` int(10) unsigned DEFAULT NULL,
  `USR_FIRST_LOGIN` date DEFAULT NULL,
  `REG_DATE` date NOT NULL,
  `LOG_DATE` date DEFAULT NULL,
  PRIMARY KEY (`USR_NAME`)
) ENGINE=InnoDB /*!40100 DEFAULT CHARSET=utf8*/;

และเราต้องการที่จะเชื่อมตารางนี้เข้ากับ Doamin class ชื่อ Users ของเราที่มีรายละเอียดตามนี้ครับ

package com.spring66.tutorial.model;
import java.util.Date;
public class Users {
    private String usrId;
    private String usrName;
    private String usrPwd;
    private Integer usrLevel;
    private Date usrFirstLogin;
    private Date regDate;
    private Date logDate;
    ...
    //all get and set methods
}

ซึ่งจริงๆแล้วเราสามารถใช้เครื่องมือที่ชื่อ abator เข้ามาช่วยเราในการสร้างตั้งแต่ Domain Class ไปจนถึง DAO ได้เลย
แต่เพื่อความรู้พื้นฐานผมจะอธิบายเป็นส่วนๆแล้วกันนะครับว่าเป็นอย่างไร การเชื่อมนั้นจริงๆแล้วเราต้องสร้าง configuration file
ขึ้นมาเพือบอกรายละเอียดของตารางและdomain ให้กับ iBatis โดยจะเริ่มจากส่วนแรก

  <resultMap id="UsersResult" class="com.spring66.tutorial.model.Users" >
    <result column="USR_ID" property="usrId" jdbcType="CHAR" />
    <result column="USR_NAME" property="usrName" jdbcType="VARCHAR" />
    <result column="USR_PWD" property="usrPwd" jdbcType="VARCHAR" />
    <result column="USR_LEVEL" property="usrLevel" jdbcType="INTEGER" />
    <result column="USR_FIRST_LOGIN" property="usrFirstLogin" jdbcType="TIMESTAMP" />
    <result column="REG_DATE" property="regDate" jdbcType="TIMESTAMP" />
    <result column="LOG_DATE" property="logDate" jdbcType="TIMESTAMP" />
  </resultMap>

ส่วนนี้จะทำการเชื่อม attribute ต่างๆของเราเข้ากับ field ในฐานข้อมูลต่อไปเรามาดูกันว่าถ้าเราต้องการ SELECT ขึ้นมาจะ
ต้องเขียน เพิ่มอย่างไร

  <select id="Users_selectByPrimaryKey" resultMap="UsersResult" parameterClass="com.spring66.tutorial.model.Users" >
    select USR_ID, USR_NAME, USR_PWD, USR_LEVEL, USR_FIRST_LOGIN, REG_DATE, LOG_DATE
    from users
    where USR_ID = #usrId:CHAR#
  </select>

จะเห็นได้ว่าสิ่งที่เราต้องการทำก็มีแค่เพียงกำหนด SQL Statement ที่ต้องการเข้าไปให้ iBatis และบอกว่าผลที่ได้ให้ map ใส่ object ชื่ออะไรเท่านั้นเองในกรณี INSERT ก็
จะออกมาในลักษณะนี้ครับ

  <insert id="Users_insert" parameterClass="com.spring66.tutorial.model.Users" >
    insert into users (USR_ID, USR_NAME, USR_PWD, USR_LEVEL, USR_FIRST_LOGIN, REG_DATE, LOG_DATE)
    values (#usrId:CHAR#, #usrName:VARCHAR#, #usrPwd:VARCHAR#, #usrLevel:INTEGER#,
      #usrFirstLogin:INTEGER#, #regDate:TIMESTAMP#, #logDate:TIMESTAMP#)
  </insert>

อันนี้จะคิดกลับกันหน่อยคือเราต้องบอกว่าเราจะส่งอะไรให้ iBatis ครับแล้วเราจะ map มันอย่างไร เอาคร่าวๆแค่นี้ก่อนนะครับ ที่เหลือไปอ่านต่อเองครับ
และเพื่อให้เป็นไปตาม concept ของเรา TDD เราจะเริ่มด้วยการเขียน test ก่อนครับ อยากให้
usersDao ทำอะไรได้บ้างก็ร่ายมาเลยครับโดยของผมเป็นแบบนี้ครับ

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.spring66.tutorial.dao;
//import some libs
/**
 *
 * @author TwinP
 */
public class IbatisUserDaoTest extends AbstractDependencyInjectionSpringContextTests {
    protected final Log log = LogFactory.getLog(getClass());
    private UsersDao usersDao;
    @Override
    protected String[] getConfigLocations() {
        setAutowireMode(AUTOWIRE_BY_NAME);
        return new String[]{
                    "classpath:/applicationContext.xml",
                    "classpath*:/applicationContext.xml" // for modular projects
                };
    }
    public void testLoadBean() {
        log.debug("Looking 4 UsersDao First");
        assertNotNull(usersDao);
    }

    public void testCreateUsers() {
        Users u = new Users();
        u.setLogDate(new Date());
        u.setRegDate(new Date());
        u.setUsrFirstLogin(new Date());
        u.setUsrId("1");
        u.setUsrLevel(new Integer(1));
        u.setUsrName("username");
        u.setUsrPwd("password");
        usersDao.insert(u);
    }

    public void testGetUsers() {
        UsersExpression usrEx = new UsersExpression();
        usrEx.createCriteria().andUsrNameEqualTo("username");
        List<Users> usr = usersDao.selectByExpression(usrEx);
        assertEquals(1, usr.size());
    }

    public void testUpdateUsers() {
        UsersExpression usrEx = new UsersExpression();
        usrEx.createCriteria().andUsrNameEqualTo("username");
        //Also can do something like this usrEx.createCriteria().andUsrNameLike("%us%");
        List<Users> usr = usersDao.selectByExpression(usrEx);
        Users user = usr.get(0);
        user.setRegDate(null);
        try {
            usersDao.updateByPrimaryKey(user);
            fail("This operation can not be done because RegDate can not be NULL, there is something WRONG!!!!!!");
        } catch (Exception error) {
            log.debug("OK that's true PK can not be NULL " + error.toString());
        }
    }

    public void testDeleteUsers() {
        usersDao.deleteByPrimaryKey("username");
        UsersExpression usrEx = new UsersExpression();
        usrEx.createCriteria().andUsrNameLike("%user%");
        //Also can do something like this usrEx.createCriteria().andUsrNameLike("%us%");
        List<Users> usr = usersDao.selectByExpression(usrEx);
        assertNull(usr);
    }

 // get set
}

และแน่นอนกล้าเขียนก็กล้า error เพราะมันจะแดงเถือกเลยครับอันดับแรกที่เราจะทำคือ สร้าง UsersDao ครับแต่เป็น interface นะ

package com.spring66.tutorial.dao;
import com.spring66.tutorial.model.Users;
import com.spring66.tutorial.model.UsersExpression;
import java.util.List;
public interface UsersDao {
    void insert(Users record);
    int updateByPrimaryKey(Users record);
    int updateByPrimaryKeySelective(Users record);
    List selectByExpression(UsersExpression expression);
    Users selectByPrimaryKey(String usrId);
    int deleteByExpression(UsersExpression expression);
    int deleteByPrimaryKey(String usrId);
    int countByExpression(UsersExpression exampexpressionle);
    int updateByExpressionSelective(Users record, UsersExpression expression);
    int updateByExpression(Users record, UsersExpression expression);
}

เท่านี้อาการแดงเถือกจะลดลงไปได้บ้าง และเพื่อไม่ให้ entry มันยาวเกินจบตอนนี้ก่อนนะครับ เพราะ

Advertisement

8 comments

  1. ใช้ Spring + iBatis เหมือนกันครับ
    เริ่มต้นก็ใช้ abator gen ให้เพราะช่วงแรกอะไรก็ยังไม่นิ่งสามาถ gen ใหม่ได้ตลอด
    พอพัฒนาไปซักพักเมื่อทุกอย่างนิ่งแล้ว ก็จะหยุดใช้ abator จะ refactor code ที่ abator gen ให้ลดความซ้ำซ้อน และไม่ให้มี พวก Example class ทั้งหลายโดนเรียกใช้จาก layer บน
    ทำแบบนี้แล้ว code จะ clean มาก class ลดลงไปเยอะ และสามารถเลือก optimize code ที่เคยเรียกผ่าน Example class ให้เรียกใช้ sql เฉพาะได้โดยตรง

    ผมจะมี script gen abator config. file จาก metadata ของ database อีกที่ด้วย เพื่อที่จะได้แก้ที่ database ที่เดียวแล้วจะได้ update ทั้งระบบเลย

  2. roofimon says:

    ผมชอบ Abator มากครับช่วยได้เยอะ ลดข้อด้อยของ iBatis ไปได้เยอะมากๆ
    ส่วนผมก็ยังชอบ Example Class นะแต่เข้าไปแก้ชื่อให้เป็น Expression เสมอ

  3. ข้างบนผมเขียนผิดนิดนึง ไม่ได้ลด class แต่เพิ่มขึ้นมาสอง class เอาไว้รวม code ที่ generic ทำให้ DAO แทบจะว่างปล่าว
    ยากเว้นแค่ DAO ของ Entity หลัก ที่มีการ find เยอะ

    code ที่เพิ่มเข้ามาเป็น Generic DAO ที่สามารถ implement ด้วย ORM ตัวอื่นได้ เพราะถ้ามี Example class ใน DAO จะมีปัญหาเวลาทีต้องการเปลี่ยน
    ผมเผื่อไว้กรณีที่ iBatis ไม่ work จริงๆ สำหรับบาง project

    ผมก็ยังใช้ Example class ครับ แต่จะเอามาไว้ หลัง DAO
    โดยที่ไม่ให้ DAO มี interface ที่เกี่ยวกับ Example class ให้ใช้ findBy…, findAll… แทน

    ผมใช้ abator gen. DAO ด้วยมันมี Example class ใน interface signature ด้วย
    แต่ผมก็ยังไช้ DAO นี้ไปตลอด เพราะจะทำให้งานเดินไปเร็วไม่ต้องมัวมาคิดตั้งชื่อ method ใน DAO เพื่อ find ที่เฉพาะเจอจง และ gen. ใหม่ได้เรื่อยๆ
    เมื่อ project นิ่ง ค่อยมา refactor เอา Example class มาไว้หลัง DAO แล้วมาตั้งชื่ออีกที จาก project ที่เคยทำ refactor วันเดียวก็เสร็จ

  4. roofimon says:

    สงสัยหลังจาาก จบมหากาพย์ tutorial นี้ต้องมี มหากาพย์ Refactor ต่อด้วยนั่นเรื่องยาวเลยอาจต้องรบกวนคุณเอ๋มาช่วย จะได้ไหมครับ

  5. ขอเป็นเรื่องวิธีการใช้ iBatis กับ Spring แบบที่ผมทำก็แล้วกันครับ

  6. เริ่มต้นด้วยการสร้าง script gen Ibator ที่ Grails66
    http://www.grails66.com/blog/?p=576

  7. brainstorm says:

    แฮะๆ เห็น code แล้วปวดหัวเลยนะครับ พอดีผมเป็นน้องใหม่พึงหัดใช้สปริงนะครับ เห็นความ advance แล้วก็ อึ้ง ทึ่ง เสียว นิดหน่อย
    โอเคเข้าเรื่อง
    คือผมกำลังลอง config ตัว spring framework อยู่นะครับ ที่นี้ก็ลองผิดลองถูกไปมา ตาม web site บ้าง แล้วก็ในหนังสือบ้าง แต่ด้วยความที่มันเป็น open source มันมีให้เลือกหลากหลายมาก ก็เลยอยากรู้ว่าถ้าจะ config spring framework แบบเบสิกๆ เนี่ยต้องมีอะไรบ้างครับ แล้วมีขั้นตอนยังไงครับ ขอคำแนะนำด้วยนะครับ

    ป.ล.1 ขอบคุณล่วงหน้าสำหรับคำแนะนำครับ
    ป.ล.2 ไม่รู้ว่าโพสผิดที่ผิดทางรึป่าวแฮะ ผิดยังไงก็ขออภัยด้วยนะครับ

  8. roofimon says:

    ตอบไปใน email แล้วครับผม

Leave a Reply

You must be logged in to post a comment.