티스토리 뷰

Spring

06. Spring + MyBatis 연동

마시멜로co. 2017. 10. 18. 15:55

06. Spring + MyBatis 연동

이전 글 : http://marshmello.tistory.com/7


저번글에서는 스프링 MVC에 대한 구조와 기본 예제 코드에 대해 작성하였습니다.


MyBatis는 JDBC에서 개발자가 직접 처리하는 PreparedStatement의 '?'에 대한 설정이나 ResultSet을 이용한 처리가 이루어지기 때문에 기존 방식에 비해 생산성이 좋아집니다.


MyBatis를 이용할 때 SQL 문을 사용하는 방식은 크게 다음과같이 나누어 집니다. 


1. XML만을 이용해서 SQL문을 설정, DAO에서는 XML을 찾아서 실행하는 코드를 작성하는 방식

 - 장점 : SQL 문은 별도의 XML로 작성되기 때문에 SQL문의 수정이나 유지보수에 적합 

 - 단점 : 개발 시 코드의 양이 많아지고 복잡성이 증가함


2. 애노테이션과 인터페이스만을 이용해서 SQL문을 설정

- 장점 : 별도의 DAO없이도 개발이 가능하기 때문에 생상성이 크게 증가

- 단점 : SQL 문을 애노테이션으로 작성하므로, 매번 수정이 일어나는 경우 다시 컴파일 


3. 인터페이스와 XML로 작성된 SQL문의 활용

 - 장점 : 간단한 SQL 문은 애노테이션으로 복잡한 SQL 문은 XML로 처리하므로 , 상황에 따라 유연하게 처리

 - 단점 : 개발자에 따라 개발 방식의 차이가 있을 수 있기 때문에, 유지 보수가 중요한 프로젝트의 경우 부적합


국내의 대부분 프로젝트는 XML만을 이용해서 SQL 문을 작성하고, 별도의 DAO를 만드는 방식을 선호합니다. 이 방식의 최대 장점은 SQL 문을 온전히 분리해서 처리하기 때문에 향후에 SQL 문의 변경이 일어날때 대처가 수월합니다.



MyBatis를 XML을 사용해서 작성하는 경우 코딩의 순서는 다음과 같습니다. 

  1. 테이블 생성 및 개발 준비
  2. 테이블 생성 및 기타 데이터베이스 관련 설정
  3. 도메인 객체의 설계 및 클래스 작성
  4. DAO 인터페이스의 작성
  5. 실행해야 하는 기능을 인터페이스로 정의 
  6. XML Mapper의 생성과 SQL문 작성
  7. XML 작성 및 SQL 작성
  8. MyBatis에서 작성된 XML Mapper를 인식하도록 설정
  9. DAO 구현
  10. DAO 인터페이스를 구현한 클래스 작성
  11. 스프링상에서 DAO 등록 및 테스트 


이글에서는 Spring+MyBatis 연동을 위 순서대로 진행해 보도록 하겠습니다. 



6.1 테이블 생성 및 개발 준비

가장 먼저 할 일은 개발에 사용할 테이블을 설계하고 이에 맞는 클래스를 구성하는 일입니다.


이전 글에서 MySQL의 데이터베이스의 설치와 설정은 완료되었으므로, 이번 글에서는 다음과 같은 테이블을 생성합니다. 


 create database marshmello;

 use marshmello;


 create table tbl_member (

  userid varchar(50) not null,

  userpw varchar(50) not null,

  username varchar(50) not null,

  email varchar(100),

  regdate timestamp default now(),

  updatedate timestamp default now(),

  primary key(userid)

 ); 



도메인 객체를 위한 클래스의 객체

개발을 할 때 가장 중요한 용어가 될만한 명사를 흔히 '도메인(domain)'이라고 표현합니다. 예컨대, 쇼핑몰의 경우 '회원, 상품, 배송' 등과 같은 용어가 도메인이라고 할 수 있습니다.


도메인의 경우 1차 도메인 2차 도메인.. 등과 같이 가장 우선하고, 중요한 의미를 가지는 순서에 따라 정해집니다. 어떤 비즈니스 로직에 있어 반드시 필요한 것이 1차 도메인이 되고, 점차 필요한 로직에 따라서 2차 , 3차로 확대됩니다.


도메인이라는 단위는 다른 의미로 여러 물리적인 환경으로 분리가 가능한 단위라고 할 수 있습니다. 예를 들어 회원 DB와 상품 DB를 분리해서 운영 할 수 있듯이 도메인은 하나의 온전한 시스템의 단위가 될 수 있습니다.


개발자에게 도메인 클래스는 일반적으로 특정 테이블과 유사한 속성을 가지는 클래스를 의미합니다. 위에서 설계한 tbl_member 테이블을 기준으로 작성되는 도메인 클래스는 다음과 같이 작성합니다. 


package com.marshmellow.example.VO;

import java.util.Date;

public class MemberVO {
	private String userId;
	private String userPw;
	private String userName;
	private String email;
	private Date regDate;
	private Date updatedDate;

	public String getUserId() {
		return userId;
	}

	public void setUserId(String userId) {
		this.userId = userId;
	}

	public String getUserPw() {
		return userPw;
	}

	public void setUserPw(String userPw) {
		this.userPw = userPw;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public Date getRegDate() {
		return regDate;
	}

	public void setRegDate(Date regDate) {
		this.regDate = regDate;
	}

	public Date getUpdatedDate() {
		return updatedDate;
	}

	public void setUpdatedDate(Date updatedDate) {
		this.updatedDate = updatedDate;
	}

	@Override
	public String toString() {
		return "MemberVO [userId=" + userId + ", userPw=" + userPw + ", userName=" + userName + ", email=" + email
				+ ", regDate=" + regDate + ", updatedDate=" + updatedDate + "]";
	}

}



6.2 DAO 인터페이스 작성

데이터베이스에 테이블의 구성과 도메인 클래스에 구성이 끝났다면 실제로 실행해야하는 작업을 인터페이스로 정의하도록 합니다. DAO로 작성하는 일은 향후에 데이터베으스 관련 기술이 변경되더라도, DAO만을 변경해서 처리할 수 있도록 하기 위해서입니다. 


예제를 위해서 간단한 데이터베이스의 현재 시간을 체크하는 기능과 tbl_member 테이블에 데이터를 추가하는 기능입니다. 


일반적인 경우라면 DAO는 'dao'라는 이름의 패키지를 구성하거나 'persistence'라는 패키지를 작성합니다. 이번 글에서는 DAO를 작성할 때는 DAO라는 패키지를 이용합니다.



package com.marshmellow.example.DAO;

import com.marshmellow.example.VO.MemberVO;

public interface MemberDAO {
	public String getTime();

	public void insertMeber(MemberVO memberVo);
}


6.3 XML Mapper의 작성

DAO 클래스가 작성되었다면 이를 이용하는 SQL 문을 작성해야 합니다.

MyBatis에서는 SQL 문을 저장하는 존재를 Mapper 라는 용어로 표합니다.

Mapper는 XML과 인터페이스를 이용할 수 있는데 XML을 사용하는 경우라면 다음과 같은 순서로 작업을 진행합니다.

① XML로 작성된 Mapper의 위치(저장 경로) 결정

② XML Mapper 파일을 작성하고 필요한 DTD 추가

③ SQL 작성


Mapper 파일의 저장 경로

XML로 작성하는 Mapper 파일의 경우 Java로 작성된 클래스와 경로를 분리해 주는 것이 유지보수에 있어서 필수적입니다. 


Java 파일이 아닌 경우에는 아래의 'src/main/resources' 경로를 이용합니다. Mapper를 설정해두는 폴더를 'mappers'로 생성합니다. 




XML Mapper의 작성

작성한 mappers 폴더에 'member_Mapper.xml' 파일을 하나 생성합니다. 




MyBatis의 경우 작성된 XML Mapper는 상단의 파일에서 사용하는 태그들에 대한 정보가 기록된 DTD를 추가합니다. 

참고 URL : http://www.mybatis.org/mybatis-3/ko/getting-started.html

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


	
	
		insert into tbl_member (userid,userpw,username,email) values
		(#{userId},#{userPw},#{userName},#{email})
	

XML Mapper를 작성할 때는 'namespace'라는 설정에 가장 신경을 많이 써야 합니다.'namespace'라는 속성은 클래스의 패키지와 유사한 용도로 MyBatis내에서 원하는 SQL 문을 찾아서 실행할 때 동작합니다.



myBatis-Spring에서 XML Mapper 인식

MyBatis가 동작하면 조금 전에 작성된 XML Mapper를 인식해야만 정상적인 동작이 가능하므로 root-context.xml을 이용해서 아래와 같이 설정을 변경합니다.

참고 URL : http://www.mybatis.org/spring/ko/factorybean.html


  	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/example" />
		<property name="username" value="root" />
		<property name="password" value="1234" />
	

	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="mapperLocations" value="classpath:mappers/**/*Mapper.xml" />
	</bean>

변화된 설정의 핵심은 mapperLocations라는 속성을 추가하고 작성된 mappers 폴더 내에 어떤 폴더이건 관계없이 파일의 이름이 Mapper.xml로 끝나면 자동으로 인식하도록 설정하는 것입니다. 



6.4 DAO 인터페이스의 구현

DAO 인터페이스와 Mapper의 작성이 완료됐다면 실제 이를 구현하는 구현 클래스를 작성해야만 합니다. MyBatis에서 DAO를 이용하는 경우는 SqlSessionTemplate 라는 것을 이용해서 DAO를 구현하므로 우선적으로 

SqlSessionTemplate 을 설정하는 작업부터 시작합니다.


SqlSessionTemplate의 설정

DAO의 작업에서 가장 번거로운 작업은 데이터베이스와 연결 한 후 작업이 완료되면 연결을 close()하는 작업입니다. 다행스럽게도 mybatis-spring 라이브러리에는 이것을 처리할 수 있는 SqlSessionTemplate이라는 클래스를 제공하므로 이를 이용하면 개발자들이 직접 연결을 맺고 종료하는 작업을 줄일수 있습니다.


mybatis-spring에서 제공하는 SqlSessionTemplate은 MyBatis의 SqlSession 인터페이스를 구현한 클래스로 기본적인 트랜잭션의 관리나 쓰레드 처리의 안정성 등을 보장해 주고 데이터베이스의 연결과 종료를 책임집니다.


SqlSessionTemplate은 SqlSessionFactory를 생성자로 주입해서 설정합니다.


참고 URL : http://www.mybatis.org/spring/ko/sqlsession.html



		

파일 첨부 : root-context.xml



구현 클래스 작성하기 

MemberDAO 인터페이스를 구현하는 클래스는 앞에서 설정된 SqlSessionTemplate를 주입받아서 사용하도록 구성합니다. 

package com.marshmellow.example.DAO;

import javax.inject.Inject;

import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Repository;

import com.marshmellow.example.VO.MemberVO;

@Repository
public class MemberDAOImpl implements MemberDAO {
	@Inject
	private SqlSession sqlSession;

	private static final String namespace = "com.marshmellow.example.memberMapper";

	@Override
	public String getTime() {
		return sqlSession.selectOne(namespace + ".getTime");
	}

	@Override
	public void insertMeber(MemberVO memberVo) {
		sqlSession.insert(namespace + ".insertMember", memberVo);
	}

}


위 코드에서 가장 중요한 애노테이션은 클래스의 선언부에 사용된 @Repository 애노테이션 입니다. @Repository는 DAO를 스프링에 인식시키기 위해서 주로 사용합니다. 


MyBatis의 SqlSession에는 SQL문을 처리할 수 있는 다음과 같은 기능들이 존재합니다. 

참고 URL : http://www.mybatis.org/mybatis-3/ko/java-api.html


<T> T selectOne(String statement)
<E> List<E> selectList(String statement)
<K,V> Map<K,V> selectMap(String statement, String mapKey)
int insert(String statement)
int update(String statement)
int delete(String statement)

메소드의 이름에서 보듯이 insert, update, delete 등의 SQL 문의 처리와 selectOne, selectList와 같이 select 형태를 지원합니다. 


6.5 스프링에 빈으로 등록하기 

MemberDAOImpl이 @Repository 애노테이션이 설정되더라도 스프링에 해당 패키지를 스캔하지 않으면 제대로 스프링의 빈으로 등록되지 못합니다. 

root-context.xml을 이용해서 아래와 같이 설정합니다. 



설정된 패키지의 MemberDAOImpl에 스프링의 빈으로 등록되었다는 아이콘이 보여지는지 반드시 확인해야 합니다.



6.6 테스트 코드의 작성

최종적으로 작성된 코드는 테스트 과정을 거쳐서 설정돠 동작 여부를 확인합니다. 테스트 코드가 있는 폴더에 아래의 코드를 작성합니다. 


root-context.xml 설정 변경

	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/marshmello?autoReconnect=true&useSSL=false" />
		<property name="username" value="root" />
		<property name="password" value="1234" />
	</bean>



package com.marshmellow.example;

import javax.inject.Inject;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.marshmellow.example.DAO.MemberDAO;
import com.marshmellow.example.VO.MemberVO;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring/*.xml" })
public class MemberDAOTest {
	@Inject
	private MemberDAO dao;

	@Test
	public void testTime() throws Exception {
		System.out.println("현재 시간 : " + dao.getTime());
	}

	@Test
	public void testInsertMember() throws Exception {
		MemberVO vo = new MemberVO();
		vo.setUserId("marshmello");
		vo.setUserPw("marshmello");
		vo.setUserName("마시멜로");
		vo.setEmail("marshmello@tistory.com");
		dao.insertMeber(vo);
	}

}


파일 첨부 : MemberDAOTest.java


결과 


MemberDAOTest에서는 MemberDAO를 주입하고 , testTime() 메소드를 사용해서 MemberDAO의 설정이 아무런 문제가 없는지 확인합니다. 실행 결과는 데이터베이스의 시간이 출력됩니다. 



6.7 MyBatis의 로그 log4jdbc-log4j2

MyBatis를 사용해서 개발하다 보면 가끔 잘못된 SQL이나 잘못된 속성의 이름으로 인해서 예외가 발생하는 경우가 종종 있습니다. 이런 경우를 대비해서 MyBatis로그를 보다 자세히 조사할 수 있도록 로그를 설정해 주는 것이 좋습니다. 


자세한 로그를 보기 위해서는 우선 log4jdbc-log4j2 라이브러리가 필요합니다.

pom.xml에 다음 라이브러리를 추가합니다.



<!-- log4jdbc-log4j2-jdbc4 -->
		<dependency>
			<groupId>org.bgee.log4jdbc-log4j2
			<artifactId>log4jdbc-log4j2-jdbc4
			<version>1.16
		</dependency>


라이브러리가 추가된 후에는 root-context.xml 파일을 열어 데이터베이스와 연결되는 드라이버 클래스와 연결 URL을 아래와 같이 수정해야합니다. 


<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://localhost:3306/marshmello?autoReconnect=true&useSSL=false" />
		<property name="username" value="root" />
		<property name="password" value="1234" />
	</bean>


파일 첨부 : root-context.xml

과거에 JDBC 드라이버의 클래스가 'com.mysql.jdbc.Driver'였지만 이를 'net.sf.log4jdbc.DriverSpy'로 변경해야 하고 연결 URL에는 중간에 'log4jdbc'라는 단어가 들어가야합니다.


log4jdbc-log4j가 제대로 동작하려면 별도의 로그 관련 설정 파일이 필요합니다.

'src/main/resources' 폴더에 'log4jdbc.log4j2.properties' 파일과 'logback.xml' 파일을 추가해야합니다.



log4jdbc.log4j2.properties 파일 

log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator

파일 첨부 : log4jdbc.log4j2.properties


logback.xml 파일 

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

<configuration>

    <include resource="org/springframework/boot/logging/logback/base.xml"/>

    <!-- log4jdbc-log4j2 -->

 <logger name="jdbc.sqlonly"        level="DEBUG"/>

 <logger name="jdbc.sqltiming"      level="INFO"/>

 <logger name="jdbc.audit"          level="WARN"/>

 <logger name="jdbc.resultset"      level="ERROR"/>

 <logger name="jdbc.resultsettable" level="ERROR"/>

 <logger name="jdbc.connection"     level="INFO"/>

</configuration>

파일 첨부 : logback.xml




위와 같이 설정의 변경과 파일의 추가 이후에 이전의 테스트 코드를 다시 한번 실행해 보면 아래와 같이 상세한 로그가 출력 되는 것을 확인 할 수 있습니다. 



6.8 MyBatis의 #{ } 문법

현재 시간에 대힌 테스트가 완료되었다면 새로운 사용자의 등록과 조회에 대한 처리를 진행합니다.


XML Mapper를 사용하는 경우 가장 먼저 작업하는 내용은 DAO 인터페이스입니다. 


package com.marshmellow.example.DAO;

import com.marshmellow.example.VO.MemberVO;

public interface MemberDAO {
	public String getTime();

	public void insertMeber(MemberVO memberVo);
	
	public MemberVO readMember (String userId) throws Exception;
	
	public MemberVO readWithPW(String userId,String userPw) throws Exception;
}


작성된 인터페이스의 메소드 이름을 기초로 XML Mapper를 작성합니다.



	


MyBatis의 경우 기본적으로 PreparedStatement를 이용해서 처리됩니다. 개발자가 PreparedStatement에 들어가는 파라미터를 사용 할 때는 '#{ }' 기호를 이용해서 처리합니다.


'#{ }'는 다음과 같은 규칙으로 적용됩니다.

  • 파라미터가 여러 속성을 가진 객체인 경우 '#{num}'은 getNum() 혹은 setNum()을 의미합니다.
  • 파라미터가 하나이고, 기본자료이나 문자열인 경우 값이 그대로 전달됩니다.
  • 파라미터가 Map 타입인 경우 '#{num}'은 Map 객체의 키 값이 'num'인 값을 찾습니다. 


Mapper 인터페이스를 구현한 클래스는 아래와 같이 작성합니다. 


package com.marshmellow.example.DAO;

import java.util.HashMap;
import java.util.Map;

import javax.inject.Inject;

import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Repository;

import com.marshmellow.example.VO.MemberVO;

@Repository
public class MemberDAOImpl implements MemberDAO {
	@Inject
	private SqlSession sqlSession;

	private static final String namespace = "com.marshmellow.example.memberMapper";

	@Override
	public String getTime() {
		return sqlSession.selectOne(namespace + ".getTime");
	}

	@Override
	public void insertMeber(MemberVO memberVo) {
		sqlSession.insert(namespace + ".insertMember", memberVo);
	}

	@Override
	public MemberVO readMember(String userId) throws Exception {
		return (MemberVO)sqlSession.selectOne(namespace+".selectMember", userId);
	}

	@Override
	public MemberVO readWithPW(String userId, String userPw) throws Exception {
		Map<string, object> paramMap = new HashMap<string, object>();
		paramMap.put("userId", userId);
		paramMap.put("userPw", userPw);
		
		return sqlSession.selectOne(namespace+".readWithPw", paramMap);
	}

}

readWithPW()의 경우 파라미터가 2개 이상 전달되는 경우이므로, Map 타입의 객체를 구성해서 파라미터로 사용합니다.


스프링의 경우 하나의 설정이 잘못된 경우에도 모든 동작이 실행되지 않기 때문에 각 단계마다 어떤 결과가 나와야 하는지 명확히 알고 진행해야 합니다.



수고하셨습니다! 다음은 간단한 프로젝트를 만들어 보도록 하겠습니다. 


파일첨부 : example.zip






댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크