MyBatis-Spring

hyekyeong Song·2020년 3월 14일
0

Mybatis

마이바티스는 객체와 SQL사이의 Mapping을 도와주고, SQL쿼리문을 xml파일로 분리하여 관리한다. DB에 접근할 때, 자바코드에서 xml태그의 id를 호출하기만 하면 되기 때문에 개발이 용이하다. SQL 수정시 굳이 프로젝트를 다시 컴파일하지 않아도 되기 때문에 매우!! 편리하다.

SqlSession_마이바티스 스프링 연동 모듈

마이바티스는 SqlSessionFactory를 사용해 SqlSession을 생성한다. 이로서 세션을 한번 생성했을 때 매핑구문을 실행하거나 커밋, 롤백을 수행할 수 있다.
그런데 마이바티스 스프링 연동모듈을 사용하면 SqlSessionFactory를 직접 사용할 필요 없이 쓰레드에 안전한 SqlSession 개체를 스프링 빈에 주입해 사용할 수 있다.


현재 진행중인 프로젝트를 통해 Spring에서 마이바티스를 사용하는 방법을 정리하자.
참고로 이와 관련된 전체 코드는 Github의 Hola 레파지토리에 저장되어있으니 구경하세용😊

Prerequisites

현재 프로젝트 환경은 다음과 같다

  • jdk1.8 (Open jdk - Zulu)
  • Spring Framework (4.3.18.RELEASE)
  • JSP
  • Lombok
  • MVC Model
  • MySQL DB
  • Maven

1. Mybatis Spring 모듈 추가

1) pom.xml 파일에 Mybatis-spring 모듈을 추가한다.

        <!--Mybatis library-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.1</version>
        </dependency>
        <!--Mybatis Spring library-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.0</version>
        </dependency>

2) mybatis-spring 모듈

: 스프링의 DataSource 및 트랜잭션 관리 기능을 Mybatis와 연동할 때, 이에 필요한 기능을 제공한다. 핵심 클래스는 SqlSessionFactoryBeanSqlSessionTemplate이다.

2. SqlSessionFactory 설정

mybatis-spring 모듈이 제공하는 SqlSessionFactoryBean을 이용해서 mybatis의 SqlSessionFactory를 생성한다.

1) applicationContext.xml 파일에 추가

    <!--Mybatis 연결 설정-->
    <!-- SqlSessionFactory 설정 : dataSource를 참조, mybatis-config.xml 경로 설정-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:/mybatis-config.xml"/>
        <!--mapper 경로-->
        <property name="mapperLocations" value="classpath:com.hyegyeong.hola.mappers/**/*Mapper.xml" />
    </bean>
  • dataSource 프로퍼티 : DB연결을 구할 때 사용할 DataSource를 설정한다
  • mapperLocations : 매핑 쿼리를 담고 있는 파일의 목록을 지정한다.
    나의 프로젝트에서는 main>resources에 com.hyegyeong.hola.mappers라는 디렉터리를 만들어 해당 디렉터리에 매핑 쿼리를 담은 파일들을 위치시켰으므로 value를 위와 같이 설정했다.

2) DataSource 설정

참고로 나는 dataSource 설정을 applicationContext.xml에 아래와 같이 입력해놨다.

    <!--DataSource 설정-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"/>
        <property name="url" value="jdbc:log4jdbc:mysql://x.x.x.x:x(DB연결 IP를 입력)/X_DB(가져올 데이터베이스입력)?allowPublicKeyRetrieval=true&amp;useSSL=false&amp;serverTimezone=UTC"/>
        <property name="username" value="xxx(DB사용자)"/>
        <property name="password" value="xxx(DB사용자패스워드)"/>
    </bean>

3. SqlSessionTemplate

SqlSessionTemplate 클래스는 MyBatis의 SqlSession 기능과 스프링의 DB 지원 기능을 연동해준다. SqlSessionTemplate이 내부적으로 사용하는 SqlSession 프록시 객체가 스프링을 연동해주기 때문에 SqlSessionTemplate을 사용해 DAO를 구현하면 된다.

applicationContext.xml에 추가

    <!-- Mybatis에서 DAO를 이용하는 경우-->
    <!-- DB 연결 + 종료작업처리 -->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate" destroy-method="clearCache">
        <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory" />
    </bean>

4. Mybatis JUnit테스트

Mybatis 설정이 올바르게 되었는지 테스트 코드로 검증한다.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"file:web/WEB-INF/applicationContext.xml"})
public class MybatisTest {

    @Inject
    private SqlSessionFactory sqlSessionFactory;

    @Test
    public void testFactory() {
        System.out.println(sqlSessionFactory);
    }

    @Test
    public void testSession() throws Exception {
        try(SqlSession session = sqlSessionFactory.openSession()) {
            System.out.println(session);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • @ContextConfiguration
    : 컨테이너 생성시 사용할 스프링 설정 파일을 지정한다
  • SqlSessionFactory객체를 주입하여 세션이 올바르게 형성되는지 확인한다

5. 간단한 CRUD 예제

1) DTO

CRUD에 사용될 데이터를 담을 DTO 객체이다.
Lombok의 Data 어노테이션으로 Getter, Setter를 한번에 설정한다.

@Data
public class DiaryDTO {
    private int diaryId;   //게시물번호
    private String title;   //제목
    private String content; //본문
    private int memberId;    //작성자(회원번호)
    private String moodCode;    //MoodCode
    private int heart; //하트수
    private String opnFlag; //공유여부
    private Date regDate;   //작성날짜
    private Date updDate;   //수정날짜
    private String delFlag;  //삭제여부
}

2) DAO

@Component
public class MyDiaryDAO {

    @Inject
    private SqlSession sqlSession;

    private static final String NAMESPACE = "com.hyegyeong.hola.mappers.diaryMapper";

    /**
     * 새로운 다이어리 추가
     * @param diaryDTO 추가하려는 내용을 담고있는 다이어리 객체
     * @return insert에 의해 영향을 받은 레코드 수
     */
    public int insertDiary(DiaryDTO diaryDTO) {
        return sqlSession.insert(NAMESPACE + ".insertDiary", diaryDTO);
    }

    /**
     * 특정 회원이 작성한 모든 다이어리 객체를 가져온다
     * @param memberId 회원 아이
     * @return 특정 회원이 작성한 모든 다이어리 객체
     */
    public List<DiaryDTO> selectDiaryList(int memberId) {
        List<DiaryDTO> diaryList;
        diaryList = sqlSession.selectList(NAMESPACE + ".selectDiaryList", memberId);
        return diaryList;
    }

    /**
     * 특정 다이어리 하나를 읽어온다
     * @param diaryId 읽으려는 다이어리의 아이디
     * @return 읽어온 다이어리 객체
     */
    public DiaryDTO selectDiary(int diaryId) throws BusinessException {
        DiaryDTO diaryDTO = sqlSession.selectOne(NAMESPACE + ".selectDiary", diaryId);
        return diaryDTO;
    }

    /**
     * 다이어리 내용을 수정한다
     * @param diaryDTO 수정하려는 다이어리의 아이디
     * @return update영향을 받은 레코드 수
     */
    public int updateDiary(DiaryDTO diaryDTO) {
        return sqlSession.update(NAMESPACE + ".updateDiary", diaryDTO);
    }

    /**
     * 다이어리를 삭제한다
     * @param diaryId 삭제하려는 다이어리 이이디
     * @return delete영향을 받은 레코드 수
     */
    public int deleteDiary(int diaryId) {
        return sqlSession.delete(NAMESPACE + ".deleteDiary", diaryId);
    }
    
}
  • Component 어노테이션으로 클래스를 Bean으로 등록한다
  • SqlSession을 주입하여 마이바티스의 세션을 사용해 CRUD를 수행한다.
  • 실행시킬 SQL구문을 저장한 mapper xml파일 위치를 NAMESPACE에 저장하고 각 메소드에서 ".사용할 xml태그의 id"를 파라미터로 지정하여 DB에 접근한다.
  • 마이바티스의 insert, update, delete는 영향받은 레코드의 수를 리턴한다.

3) Mapper.xml 과 resultMap

(1) resultMap

  • DB 필드와 데이터를 담을 객체(ex.DTO)의 변수명이 다를 경우 사용한다. 이때, 반드시 모든 필드명을 적어주어야함에 유의하자.
  • SQL구문에 별칭(AS)을 쓰지 않아 훨씬 보기 좋은 코드를 작성할 수 있다.
  • resultMap 엘리먼트 : 결과 매핑에 사용하는 최상위 엘리먼트로 id속성과 매핑 클래스를 정의하는 type 속성을 가진다
  • id 엘리먼트 : 기본키
  • result 엘리먼트 : 기본키 이외의 모든 컬럼
  • column : DB 테이블의 필드명
  • property : 객체의 변수명
  • 마이바티스에서 association, collection 엘리먼트로 1:1매핑, 1:N매핑을 처리할 수 있는데 이는 추후 정리할 예정이다.

(2) diaryMapper.xml

<?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="com.hyegyeong.hola.mappers.diaryMapper">

    <resultMap id="diaryResult" type="com.hyegyeong.hola.dto.DiaryDTO">
        <id column="DIARY_ID" property="diaryId"/>
        <result column="TITLE" property="title"/>
        <result column="CONTENT" property="content"/>
        <result column="MEMBER_ID" property="memberId"/>
        <result column="MOOD_CODE" property="moodCode"/>
        <result column="HEART" property="heart"/>
        <result column="OPN_FLAG" property="opnFlag"/>
        <result column="REG_DATE" property="regDate"/>
        <result column="UPD_DATE" property="updDate"/>
        <result column="DEL_FLAG" property="delFlag"/>
    </resultMap>


    <insert id="insertDiary" useGeneratedKeys="true" keyProperty="diaryId">
    INSERT INTO DIARYS_MST (
                TITLE
                ,CONTENT
                ,MEMBER_ID
                ,MOOD_CODE
                ,OPN_FLAG
                )
         VALUES (
                #{title}
                ,#{content}
                ,#{memberId}
                ,#{moodCode}
                ,#{opnFlag}
         )
    </insert>

    <select id="selectDiaryList" resultMap="diaryResult">
    SELECT *
      FROM DIARYS_MST
     WHERE MEMBER_ID = #{memberId}
       AND DEL_FLAG = 'N'
    </select>

    <select id="selectDiary" resultMap="diaryResult">
    SELECT *
      FROM DIARYS_MST
     WHERE DIARY_ID = #{diaryId}
       AND DEL_FLAG = 'N'
    </select>

    <update id="updateDiary">
    UPDATE DIARYS_MST
       SET TITLE = #{title}
           ,CONTENT = #{content}
           ,MOOD_CODE = #{moodCode}
           ,OPN_FLAG = #{opnFlag}
     WHERE DIARY_ID = #{diaryId}
       AND DEL_FLAG = 'N'
    </update>

    <delete id="deleteDiary">
    UPDATE DIARYS_MST
       SET DEL_FLAG = 'Y'
     WHERE DIARY_ID = #{diaryId}
    </delete>

</mapper>

(3) DAO에 대한 Junit TestCode 작성과 확인

@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"file:web/WEB-INF/applicationContext.xml"})
public class MyDiaryDaoTest {

    @Inject
    private MyDiaryDAO myDiaryDAO;

    /**
     * 회원이 새로운 여행 다이어리를 작성해 저장한다
     * (원래 파라미터로 들어온 다이어리 객체에서 회원 번호를 알아내서 저장하나, 회원 번호를 임의로 지정해주었음)
     * @throws Exception
     */
    @Test
    public void testInsertDiary() throws Exception {
        DiaryDTO diaryDTO = new DiaryDTO();
        diaryDTO.setTitle("testTitle3");
        diaryDTO.setContent("test Content3");
        diaryDTO.setMemberId(2);
        diaryDTO.setMoodCode("NO");
        diaryDTO.setOpnFlag("Y");

        int resultNums = myDiaryDAO.insertDiary(diaryDTO);
        log.info("success insert Diary : " + resultNums);
    }

    /**
     * 특정 회원이 작성한 다이어리를 모두 읽어온다
     * @throws BusinessException
     */
    @Test
    public void testSelectDiaryList() throws Exception {
        myDiaryDAO.selectDiaryList(1);
    }

    /**
     * 작성한 다이어리들 중 하나의 다이어리를 읽어온다.
     * @throws Exception
     */
    @Test
    public void testSelectDiary() throws Exception {
        myDiaryDAO.selectDiary(1);  //읽으려는 다이어리의 ID
    }

    /**
     * diaryId에 해당하는 다이어리를 수정한다
     * @throws Exception
     */
    @Test
    public void testUpdateDiary() throws Exception {
        DiaryDTO diaryDTO = new DiaryDTO();
        diaryDTO.setDiaryId(1);
        diaryDTO.setTitle("updated Title3");
        diaryDTO.setContent("updated Content1");
        diaryDTO.setMoodCode("NA");
        diaryDTO.setOpnFlag("Y");

        int resultNums = myDiaryDAO.updateDiary(diaryDTO);
        log.info("success update Diary : " + resultNums);
    }

    @Test
    public void testDeleteDiary() throws Exception {
        int resultNums = myDiaryDAO.deleteDiary(1); //1번 다이어리를 삭제(삭제여부를 Y로 업데)
        log.info("success delete Diary : " + resultNums);
    }

}
  • DAO객체를 주입해 사용한다.
  • @ContextConfiguration 애노테이션으로 컨테이너 생성시 사용할 스프링 설정 파일을 지정한다
profile
안녕하세요😀😀

1개의 댓글