Posts Tagged ‘DAO Ibatis’

Spring กับ iBatisDao ภาค2

January 23rd, 2009

ต่อจากตอนที่แล้วเราพบว่าทำไม mothod ใน DAO มันเยอะจังก็อย่าเพิ่งไปสนใจมากครับเพราะเพื่อความยืดหยุ่นเราจะสร้างมันไว้ก่อนนอกจากนี้ยังทะลึ่งมี พวกที่ลงท้าย
ด้วย Expression อีก method ที่ลงท้ายด้วย Expression นั้นจะเป็นพวกสร้าง Dynamic Query ทั้งหลายยกตัวอย่างเช่นผมต้องการสร้าง

SELECT * FROM USERS WHERE USERS.USR_ID LIKE '%username%'

แทนที่จะต้องไปสร้าง

<select> ... </select>

ใหม่เราก็สร้างเป็น dynamic block ก่อนหน้าตาเป็นแบบนี้

  <sql id="Where_Clause" >
    <iterate property="oredCriteria" conjunction="or" prepend="where" removeFirstPrepend="iterate" >
      <isEqual property="oredCriteria[].valid" compareValue="true" >
        (
        <iterate prepend="and" property="oredCriteria[].criteriaWithoutValue" conjunction="and" >
          $oredCriteria[].criteriaWithoutValue[]$
        </iterate>
        <iterate prepend="and" property="oredCriteria[].criteriaWithSingleValue" conjunction="and" >
          $oredCriteria[].criteriaWithSingleValue[].condition$
            #oredCriteria[].criteriaWithSingleValue[].value#
        </iterate>
        <iterate prepend="and" property="oredCriteria[].criteriaWithListValue" conjunction="and" >
          $oredCriteria[].criteriaWithListValue[].condition$
          <iterate property="oredCriteria[].criteriaWithListValue[].values" open="(" close=")" conjunction="," >
            #oredCriteria[].criteriaWithListValue[].values[]#
          </iterate>
        </iterate>
        <iterate prepend="and" property="oredCriteria[].criteriaWithBetweenValue" conjunction="and" >
          $oredCriteria[].criteriaWithBetweenValue[].condition$
          #oredCriteria[].criteriaWithBetweenValue[].values[0]# and
          #oredCriteria[].criteriaWithBetweenValue[].values[1]#
        </iterate>
        )
      </isEqual>
    </iterate>
  </sql>

จากนนั้นเราจะทำการไปสร้าง

<select> ... </select>

แบบ dynamic ขึ้นมาหน้าตาเป็นแบบนี้ครับ

  <select id="Users_selectByExpression" resultMap="UsersResult" parameterClass="com.spring66.tutorial.model.UsersExpression" >
    select USR_ID, USR_NAME, USR_PWD, USR_LEVEL, USR_FIRST_LOGIN, REG_DATE, LOG_DATE
    from users
    <isParameterPresent >
      <include refid="Where_Clause" />
      <isNotNull property="orderByClause" >
        order by $orderByClause$
      </isNotNull>
    </isParameterPresent>
  </select>

เพียงเท่านี้เราก็สามารถเขียน Dynamic SQL ได้จาก Java Code เลยครับยกตัวอย่างเช่น

	UsersExpression usrEx = new UsersExpression();
        	usrEx.createCriteria().andUsrNameLike("%user%").andUsrIdEqualTo("1");

ได้เรื่อยๆครับซึ่งนี่เป็นหนึ่งในหลายๆวิธีที่เราสามารถสร้าง Dynamic SQL ได้โดยจุดนี้ถือเป็นจุดแข็งอีกเรื่องของ iBatis เพราะไม่ต้องปรับ skill มากแค่มีความรู้เรื่อง SQL ก็สามารถ
เดา code ได้เกือบทั้งหมดแล้วครับ โอเคต่อไปหลังจากสร้าง DAO แล้วอาการแดงเถือกจะหายไปต่อไปเราจะลอง เรียก test แล้วครับ และแน่นอนโดนด่าแน่นอนเพราะไม่มี bean ชื่อ
นี้ดังนั้น ให้ไวครับเขียน implementation คลาสของ interface ที่เราเพิ่งสร้างขึ้นมา

package com.spring66.tutorial.dao.ibatis;

import com.spring66.tutorial.dao.UsersDao;
import com.spring66.tutorial.model.Users;
import com.spring66.tutorial.model.UsersExpression;
import java.util.List;
import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;

public class IbatisUsersDao extends SqlMapClientDaoSupport implements UsersDao {
    public IbatisUsersDao() {
        super();
    }
    public void insert(Users record) {
        getSqlMapClientTemplate().insert("Users_insert", record);
    }
    public int updateByPrimaryKey(Users record) {
        int rows = getSqlMapClientTemplate().update("Users_updateByPrimaryKey", record);
        return rows;
    }
    public int updateByPrimaryKeySelective(Users record) {
        int rows = getSqlMapClientTemplate().update("Users_updateByPrimaryKeySelective", record);
        return rows;
    }
    public List selectByExpression(UsersExpression example) {

        List list = getSqlMapClientTemplate().queryForList("Users_selectByExpression", example);
        return list;
    }
    public Users selectByPrimaryKey(String usrId) {
        Users key = new Users();
        key.setUsrId(usrId);
        Users record = (Users) getSqlMapClientTemplate().queryForObject("Users_selectByPrimaryKey", key);
        return record;
    }
    public int deleteByExpression(UsersExpression example) {
        int rows = getSqlMapClientTemplate().delete("Users_deleteByExpression", example);
        return rows;
    }
    // some other methods
    }
}

จะเห็นว่าคลาสนี้ extends มาจาก SqlMapClientDaoSupport แล้วเอ๊ะไอ้คลาสนี้มาจากไหนจริงๆแล้ว Spring เตรียมเครื่องมือที่ช่วยให้เราใช้ iBatis ได้สะดวกมากขึ้นไว้ให้แล้ว
ซึ่งการใช้ Template คลาสพวกนี้จะช่วยให้เราทำงานกับ ORM ต่างๆได้สะดวกมากขึ้นเพราะโดยปกติแล้วการใช้งาน ORM จะต้องมีการเขียน code ที่เป็น overhead เยอะพอสมควร
ดังนั้น สิ่งต่อไปที่เราต้องทำคือ ต้องสร้าง bean ใหม่ขึ้นมาหนึ่งตัวเพื่อใช้ระบุค่า configuration พื้นฐานต่างๆของ iBatis ดังนี้

<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">

	<property name="configLocation" value="classpath:sqlmap-config.xml"/>

	<property name="dataSource" ref="dataSource"/>

</bean>

พรอพเพอตี้ตัวแรก configLocation เป็นที่ที่เราใช้เก็บ sqlmap-config ซึ่งเป็นที่ระบุว่า mapping file ของเราอยู่ที่ใดและมีกี่ไฟล์ที่ถูกใช้งานอยู่

<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">

	<property name="configLocation" value="classpath:sqlmap-config.xml"/>

	<property name="dataSource" ref="dataSource"/>

</bean>

ของเรามีตารางเดียวครับ ดังนั้นเราก็จะระบุลงไปใน sqlmap-cinfig.xml ดังนี้

<sqlMapConfig>

	<sqlMap resource="ibatis/users.xml"/>

</sqlMapConfig>

พรอพเพอตี้ตัวต่อไปคือ dataSource ก็คือเราจะต่อกับระบบฐานข้อมูลด้วย Connection ไหนต่อไปก็ทำการเพิ่ม Dao ของเราเข้าไปในระบบพร้อมทั้งระบุด้วยว่าเราจะขอใช้ sqlMapClient

<bean id="usersDao" class="com.spring66.tutorial.dao.ibatis.IbatisUsersDao">

	<property name="sqlMapClient" ref="sqlMapClient"/>

</bean>

จากตรงนี้เราจะเห็นได้ว่า ทุกครั้งที่เราต้องการเรียกใช้ sqlMapClient เราก็แค่เรียกมัน ไม่ต้องมานั้ง new ให้เมื่อยตุ้มครับ ถึงตรงนี้ทุกอย่างน่าจะครบแล้วก็ run test เลยครับ แน่นอน หล่อ

Spring กับ IbatisDao ภาค 1

January 22nd, 2009

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 มันยาวเกินจบตอนนี้ก่อนนะครับ เพราะ