본문 바로가기

Spring 4/eGov MyBatis

eGovFrame-MyBatis example

전자정부프레임워크와 MyBatis 연동 예제


개요

egovframe.org에서 제공하는 예제를 분석하고 실행해 보고자 합니다

ex-MyBatis.zip

   

MyBatis 연동을 위한 2개 형태 예제 제공


1. EgovAbstractMapper 추상클래스를 상속한 Mapper

   @Repository("empMapper")

   public class EmpMapper extends EgovAbstractMapper { }

  - DI 사용하여 MyBatis가 생성해준 empMapper를 사용함


2. Mapper 를 인터페이스로 선언하고 MyBatis가 구현

  @Mapper("deptMapper")

  public interface DeptMapper { }

  - DI사용하여 MyBatis 가 생성해준 deptMapper를 사용함


예제에 포함되어 있는 설명은 아래와 같습니다

1) DAO 클래스 대신 "Mapper 인터페이스"로 구현하는 방법과

2) EgovAbstractMapper 추상클래스를 상속하여 기존 DAO 클래스를 구현하는 방법으로 두 가지 예제를 제공한다.


[ 표준프레임워크 MyBatis 적용 시 준수사항 ]

1)의 경우, Mapper 인터페이스에 반드시 @Mapper를 선언해야 한다. 

@Mapper 스캔을 위해 egovframework.rte.psl.dataaccess.mapper.MapperConfigurer 클래스를 빈으로 등록하고, basePackage 프로퍼티를 설정한다.

2)의 경우, EgovAbstractMapper를 상속하거나 확장하여 활용해야 한다.


참고

  egovframework.rte.fdl.property.EgovPropertyService;

  egovframework.rte.ptl.mvc.tags.ui.pagination.PaginationInfo;

  egovframework.rte.fdl.property.EgovPropertyService;


  EgovIdGnrService : 자동으로 아이디(숫자)가 증가하는 기능 제공, IDS 테이블을 사용하는 것으로 생각됨

  PaginationInfo : 페이지 네비게이션을 위한 정보



예제에서 사용된 MyBatis 버전 정보

mybatis 3.2.8

mybatis-spring 1.2.2


MyBatis 예제 안에서 사용된 설정파일의 주요내용

WEB-INF/

  └ web.xml ( WEB-INF/ )

       - 일반 스프링 프로젝트와 동일

       - 스프링 설정파일 2개의 xml (contextConfigLocation과 서블릿 컨텍스트 파일)의 경로 지정

       - contextConfigLocation: classpath*:META-INF/spring/context-*.xml

       - 서블릿 컨텍스트 파일 : /WEB-INF/spring/dispatcher-*.xml


WEB-INF/spring/

  └ dispatcher-servlet.xml ( /WEB-INF/spring/ )

       - 일반 스프링 프로젝트와 동일

       - Pagination 기능을 위한 2개의 빈 설정

       - PaginationManager, imageRender


META-INF/spring/

  └ context-common.xml ( META-INF/spring/ )

       - db.properties 경로 지정


  └ context-datasource.xml ( META-INF/spring/ )

       - dataSource 빈 설정 ( ddl.sql, dml.sql 경로 지정 )

       - jdbc dataSource, 아파치 dbcp dataSource 중에서 한 가지를 선택하여 설정


  └ context-idgen.xml ( META-INF/spring/ )

       - 자동증가필드에 값을 삽입할 때 사용될 아이디 생성 빈 등록

       - 사원번호, 부서번호를 새로 삽입할 때 등록된 빈을 코드에서 사용함


  └ context-mybatis.xml ( META-INF/spring/ )

       - sqlSession 빈 설정 (속성으로 dataSource 참조, sql-myabtis-config.xml 경로참조, mappers/*.xml 경로참조)

       - @Mapper("deptMapper")으로 선언된 인터페이스 경로(패키지) 지정 -> DI을 이용하여 코드에서 인스턴스 사용가능


  └ context-properties.xml  ( /WEB-INF/spring/ )

       - propertiesService 빈( EgovPropertyServiceImpl) 등록 (pageUnit, pageSize 정보 설정)


META-INF/sqlmap/mappers/

  └ employee.xml      ( employee 테이블 SQL 문장 )

  └ department.xml    ( department 테이블 SQL 문장 )


META-INF/sqlmap/config/

  └ sql-mybatis-config.xml ( META-INF/sqlmap/config/ )

       - EmpVO, DeptVO, SearchVO 에 대한 각 alias 선언


META-INF/property/

  └ db.properties ( META-INF/property/ )

       - driver, url, username, password 등


META-INF/db/

  └ ddl.sql ( META-INF/db/ ) : 예제에 사용될 테이블을 생성하는 create table ~ 문장

       - IDS, EMPLOYEE, DEPARTMENT 테이블 생성 

       - IDS 테이블은 각 테이블의 다음 id (숫자)가 저장됨, 즉 한 행이 추가될 때마다 다음에 추가될 행의 id(숫자)를 저장해두는 역할을 함


  └ dml.sql ( META-INF/db/ ) : 예제로 사용될 테이블에 샘플 데이터 입력

       - IDS, EMPLOYEE, DEPARTMENT 테이블에 샘플 레코드 입력



코드 분석

Employee CRUD 기능 ( EgovAbstractMapper 를 사용하는 경우 )

@Controller

public class EmpController


public interface EmpService


public class EmpVO


@Repository("empMapper")

public class EmpMapper extends EgovAbstractMapper


@Service("empService")

public class EmpServiceImpl extends EgovAbstractServiceImpl implements EmpService


emp/list.jsp

emp/detail.jsp

emp/form.jsp



Department CRUD 기능 ( Mapper 인터페이스를 사용하는 경우 )

@Controller

public class DeptController


public interface DeptService


public class DeptVO


@Mapper("deptMapper")

public interface DeptMapper


@Service("deptService")

public class DeptServiceImpl extends EgovAbstractServiceImpl implements DeptService


dept/list.jsp

dept/detail.jsp

dept/form.jsp



위의 2가지 기능에 공통으로 사용되는 클래스

EgovImgPaginationRenderer.java

JdbcLoggingExcepHndlr.java

SearchVO.java



이클립스에 새로 생성된 프로젝트를 아래처럼 설정하고 실행 테스트

eGovFrame v 3.5.1 개발자 환경 다운로드

압축해제 후 eclipse 폴더 내에서 이클립스 실행파일 더블클릭하여 이클립스 실행

eGovFrame Web Project 생성(예제코드 없이 생성)


MyBatis 예제의 모든 설정파일을 생성된 프로젝트의 각 대응 위치에 복사하기

MyBatis 예제의 모든 소스코드(*.java, *.jsp)를 생성된 프로젝트의 각 대응 위치에 복사하기


데이터베이스 접속을 위한 드라이버를 pom.xml의 dependencies 요소에 추가하기


MyBatis 예제에 포함된 *.sql 파일을 이용하여 샘플 데이터베이스 테이블을 생성하고 샘플 데이터를 저장한다

 - ddl.sql, dml.sql 2개의 파일에는 테이블 생성/레코드 추가 문장이 저장되어 있다


모든 설정파일(context-*.xml)에 분산되어 있는 빈 설정내용을 모두 dispatcher-servlet.xml 파일에 복사한다

dispatcher-servlet.xml 파일에서 dataSource를 설정하는 부분을 편집하여 현재 데이터베이스에 맞도록 설정한다

dispatcher-servlet.xml 파일에서 sqlSession 빈을 설정하는 부분을 편집하여 필요한 만큼의 맵파일을 등록한다


4개의 리소스파일은 egovframework.sample.cmmn 패키지 안에 복사한다

 - department.xml, employee.xml, message-common_ko.properties, sql-mybatis-config.xml

 - 위의 4개 리소스 파일은 dispatcher-servlet.xml 파일에서 참조하도록 설정한다


department.xml, employee.xml파일에서 오라클 데이터베이스에 맞도록 SQL문장을 편집한다


프로젝트 위에서 마우스 우측 > Run As > Run on server 

웹브라우저 화면에 보여지는 index.jsp 내용에서 2개의 링크 중에 하나를 클릭한다



dispatcher-servlet.xml 파일의 전체내용

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"

xmlns:mvc="http://www.springframework.org/schema/mvc" 

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">


<!--

- The controllers are autodetected POJOs labeled with the @Controller annotation.

-->

<context:component-scan base-package="egovframework">

<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>

</context:component-scan>


<!--아래의 component-scan은 현재 시스템에 맞게 설정한다 -->

<context:component-scan base-package="egovframework.rte.psl.dataaccess.mapper" />

<context:component-scan base-package="egovframework.sample.cmmn" />

<context:component-scan base-package="egovframework.sample.dept.service" />

<context:component-scan base-package="egovframework.sample.dept.service.impl" />

<context:component-scan base-package="egovframework.sample.dept.web" />

<context:component-scan base-package="egovframework.sample.emp.service" />

<context:component-scan base-package="egovframework.sample.emp.service.impl" />

<context:component-scan base-package="egovframework.sample.emp.web" />

    

<!--현재 시스템의 DB에 맞게 아래의 설정은 수정해야 한다-->

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" 

p:driverClassName="oracle.jdbc.OracleDriver

p:url="jdbc:oracle:thin:@localhost:1521:xe

p:username="scott"

p:password="TIGER"/> 

    

    <!-- SqlSession setup for MyBatis Database Layer -->

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean">

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

<property name="configLocation" value="classpath:egovframework/sample/cmmn/sql-mybatis-config.xml" />

<property name="mapperLocations">

<list>

<value>classpath:egovframework/sample/cmmn/department.xml</value>

<value>classpath:egovframework/sample/cmmn/employee.xml</value>

</list>

</property>

</bean>

<!-- MapperConfigurer setup for MyBatis Database Layer with @Mapper("deptMapper") in DeptMapper Interface -->

  <bean class="egovframework.rte.psl.dataaccess.mapper.MapperConfigurer">

<property name="basePackage" value="egovframework.sample.dept.service.impl" />

</bean>  

   <!-- 사원번호 생성 -->

<bean name="egovIdGnrServiceEmp" class="egovframework.rte.fdl.idgnr.impl.EgovTableIdGnrServiceImpl" destroy-method="destroy">

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

<property name="strategy" ref="mixPrefixEmployee" />

<property name="blockSize" value="1" />

<property name="table" value="IDS" />

<property name="tableName" value="EMPLOYEE" />

</bean>

<bean name="mixPrefixEmployee" class="egovframework.rte.fdl.idgnr.impl.strategy.EgovIdGnrStrategyImpl">

<property name="prefix" value="EMPLOYEE-" />

<property name="cipers" value="8" />

<property name="fillChar" value="0" />

</bean>


<!-- 부서번호 생성 -->

<bean name="egovIdGnrServiceDept" class="egovframework.rte.fdl.idgnr.impl.EgovTableIdGnrServiceImpl" destroy-method="destroy">

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

<property name="strategy" ref="mixPrefixDepartmet" />

<property name="blockSize" value="1" />

<property name="table" value="IDS" />

<property name="tableName" value="DEPARTMENT" />

</bean>

<bean name="mixPrefixDepartmet"

class="egovframework.rte.fdl.idgnr.impl.strategy.EgovIdGnrStrategyImpl">

<property name="prefix" value="DEPARTMENT-" />

<property name="cipers" value="8" />

<property name="fillChar" value="0" />

</bean>

    

<!--

        - This bean resolves specific types of exceptions to corresponding logical view names for error views. 

        - The default behaviour of DispatcherServlet is to propagate all exceptions to the servlet container: 

        - this will happen here with all other types of exceptions.

    -->

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">

<property name="defaultErrorView" value="cmmn/bizError" />

<property name="exceptionMappings">

<props>

                <prop key="org.springframework.dao.DataAccessException">/cmmn/dataAccessFailure</prop>

                <prop key="org.springframework.transaction.TransactionException">/cmmn/transactionFailure</prop>

                <prop key="egovframework.rte.fdl.cmmn.exception.EgovBizException">/cmmn/bizError</prop>

</props>

</property>

</bean>


<!--

        - This bean configures the 'prefix' and 'suffix' properties of InternalResourceViewResolver, which resolves logical view names returned by Controllers. 

        - For example, a logical view name of "vets" will be mapped to "/WEB-INF/jsp/vets.jsp".

    -->

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:viewClass="org.springframework.web.servlet.view.JstlView"  

   p:prefix="/WEB-INF/jsp/egovframework/" p:suffix=".jsp" />

<!-- Pagination Tag -->

    <bean id="imageRenderer" class="egovframework.sample.cmmn.EgovImgPaginationRenderer" /> 


    <bean id="paginationManager" class="egovframework.rte.ptl.mvc.tags.ui.pagination.DefaultPaginationManager">

        <property name="rendererType">

            <map>

                <entry key="image" value-ref="imageRenderer"/> 

            </map>

        </property>

    </bean>

<!-- Pagination Tag -->


<!-- Message Source-->    

<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">

<property name="basenames">

<list>

<value>classpath:egovframework/sample/cmmn/message-common</value>

<value>classpath:/egovframework/rte/fdl/idgnr/messages/idgnr</value>

<value>classpath:/egovframework/rte/fdl/property/messages/properties</value>

</list>

</property>

<property name="cacheSeconds">

<value>60</value>

</property>

</bean>


<bean id="leaveaTrace" class="egovframework.rte.fdl.cmmn.trace.LeaveaTrace">

<property name="traceHandlerServices">

<list>

<ref bean="traceHandlerService" />

</list>

</property>

</bean>


<bean id="traceHandlerService" class="egovframework.rte.fdl.cmmn.trace.manager.DefaultTraceHandleManager">

<property name="reqExpMatcher">

<ref bean="antPathMater" />

</property>

<property name="patterns">

<list>

<value>*</value>

</list>

</property>

<property name="handlers">

<list>

<ref bean="defaultTraceHandler" />

</list>

</property>

</bean>

<bean id="antPathMater" class="org.springframework.util.AntPathMatcher" />


<bean id="defaultTraceHandler" class="egovframework.rte.fdl.cmmn.trace.handler.DefaultTraceHandler" />

<!-- context.common.xml -->


<bean name="propertiesService" class="egovframework.rte.fdl.property.impl.EgovPropertyServiceImpl" destroy-method="destroy">

<property name="properties">

       <map>

        <entry key="pageUnit" value="10"/>

        <entry key="pageSize" value="10"/>

 

<!--         <entry key="fileDir" value="C:\\temp\\"/>

        <entry key="filePath" value="C:\\temp\\"/>

 

        <entry key="emailServer" value="smtp.gmail.com"/>

        <entry key="emailAuthId" value="dongdo.lee@gmail.com"/>

        <entry key="emailAuthPwd" value="dongdo04"/>

        <entry key="emailFromAddr" value="egov@egov.gov"/>

        <entry key="emailFromName" value="EgovFramwork"/>

 

        <entry key="myAdmnstmachValue" value="001"/>

        <entry key="cvplOthbcValueOpen" value="001"/>

 

        <entry key="cvplOthbcCode" value="001"/>

        <entry key="userDeptCode" value="002"/>

        <entry key="userRspofcCode" value="003"/>

        <entry key="admnstmachCode" value="004"/>

 

           <entry key="marshallingDir" value="C:/svn_egovframework/SAMPLE/civilappealSample/trunk/egovframework-civilappealsample-web/src/main/webapp/WEB-INF/config/egovframework/"/>

            -->

       </map>

</property>

</bean>

</beans>



eGovFramework에서 MyBatis를 사용하는 2가지 방법

 - DAO를 interface 형식으로 선언하고 MyBatis에서 구현하도록 하는 방법

 - 전자정부프레임워크가 제공하는 EgovAbstractMapper 클래스를 상속하는 방법


참고: 전자정부프레임워크에서는 Service 클래스를 선언할 때 egovframework.rte.fdl.cmmn.EgovAbstractServiceImpl 클래스를 상속하여 작성한다


1. EgovAbstractMapper 클래스를 상속하여 DAO를 작성하는 예


@Repository("empMapper")

public class EmpMapper extends EgovAbstractMapper {


/**

* DB에서 사원목록을 조회한다.

* @param empVO

* @return List

* @throws Exception

*/

public List<EmpVO> retrieveEmpList(EmpVO empVO) throws Exception {


/* employee.xml  파일의 namespace는 '<mapper namespace="EmpMapper">'으로 선언되어 있으므로

* 소스코드에서 employee.xml 파일에 선언된 sql 문장을 참조할 때는 아래의 라인처럼 namespace를 이용하여 

* 'EmpMapper.SQL문장의 아이디'와 같은 방법을 사용하여 접근할 수 있다

 * 아래 라인에서 사용된 selectList()는 EgovAbstractMapper클래스의 멤버 메소드이다

*/

return selectList("EmpMapper.retrieveEmpList", empVO);

}

      ...........

       ............

}



참고: 위의 코드에서 참조하는 employee.xml 의 관련부분 (아래의 sql 문장은 오라클에서 실행되지 않지만 위의 코드와 연결되는 구조를 보여준다)

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE mapper   PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">


<mapper namespace="EmpMapper">


<select id="retrieveEmpList" parameterType="empVO" resultType="empVO">

   <![CDATA[

       SELECT

              EMP.EMPNO,

              EMP.EMPNM,

              EMP.BIRTHDATE,

              EMP.TELEPHONE,

              EMP.ADDRESS

         FROM EMPLOYEE EMP

         WHERE 1=1

   ]]>

   <if test="searchKeyword != null">

       <choose>

           <when test="searchCondition == 1">

               AND EMP.EMPNO LIKE '%'|| #{searchKeyword} ||'%'

           </when>

           <otherwise>

              AND EMP.EMPNM LIKE '%'|| #{searchKeyword} ||'%'

           </otherwise>

       </choose>

</if>

<![CDATA[

ORDER BY EMP.EMPNO DESC

LIMIT #{recordCountPerPage} OFFSET #{firstIndex}

]]>

</select>

       ..........

         .........

</mapper>


위에서 생성한 EmpMapper 클래스를 사용하는 예

@Service("empService")

public class EmpServiceImpl extends EgovAbstractServiceImpl implements EmpService {


@Resource(name = "empMapper")

private EmpMapper empMapper;


@Resource(name = "egovIdGnrServiceEmp")

private EgovIdGnrService egovIdGnrService;


/**

* 사원목록조회 요청을 처리하기 위해 데이터처리를 요청한다.

* @param empVO

* @return List

* @throws Exception

*/

public List<EmpVO> retrieveEmpList(EmpVO empVO) throws Exception {

List<EmpVO> list = empMapper.retrieveEmpList(empVO);

return list;

}



2. DAO를 interface 형식으로 선언하고 MyBatis가 구현하도록 하는 방법

@Mapper("deptMapper")

public interface DeptMapper {


/** 

* DB에서 부서목록을 조회한다.

* @param deptVO

* @return List

* @throws Exception

*/

public List<DeptVO> retrieveDeptList(DeptVO deptVO) throws Exception;


/**

*  DB에서 부서정보를 상세조회한다.

* @param deptVO

* @return DeptVO

* @throws Exception

*/

public DeptVO retrieveDept(DeptVO deptVO) throws Exception;

      .............

        .............

}

위와같이 인터페이스를 선언하여 사용하려는 경우에는 서블릿 설정파일에 아래와 같이 등록해야 한다

<!-- MapperConfigurer setup for MyBatis Database Layer with @Mapper("deptMapper") in DeptMapper Interface -->

 <bean class="egovframework.rte.psl.dataaccess.mapper.MapperConfigurer">

<property name="basePackage" value="egovframework.sample.dept.service.impl" /> <!--@Mapper 인터페이스 패키지 경로-->

 </bean>  



위에 선언한 인터페이스를 사용하는 예

@Service("deptService")

public class DeptServiceImpl extends EgovAbstractServiceImpl implements DeptService {


@Resource(name = "deptMapper")

private DeptMapper deptMapper; // 작성된 인터페이스의 참조


@Resource(name = "egovIdGnrServiceDept")

private EgovIdGnrService egovIdGnrService;


/**

* 부서목록조회 요청을 처리하기 위해 데이터처리를 요청한다.

* @param deptVO

* @return List

* @throws Exception

*/

public List<DeptVO> retrieveDeptList(DeptVO deptVO) throws Exception {

return deptMapper.retrieveDeptList(deptVO);

}

      .........

        ...........

}