Sangwon Coding

4. MyBatis와 스프링 연동 본문

Spring/코드로 배우는 스프링 웹 프로젝트

4. MyBatis와 스프링 연동

SW1 2019. 11. 5. 22:42

안녕하세요! 이번 포스팅에서는 스프링 프레임워크과 MyBatis를 연동해서 좀 더 빠르게 SQL을 처리할 수 있는 구조를 만들어 보겠습니다. 마찬가지로 테스트 작업을 진행해서 일르 확인해보겠습니다.

 

MyBatis는 흔히 'SQL 매핑(mapping) 프레임워크'로 분류되는데, 개발자들은 JDBC 코드의 복잡하고 지루한 작업을 피하는 용도로 많이 사용합니다. 전통적인 JDBC 프로그래밍의 구조와 비교해 보면 MyBatis의 장점을 파악할 수 있습니다.

 

전통적인 JDBC프로그램

MyBatis

직접Connection을 맺고 마지막에close()

PreparedStatement직접 생성 및 처리

PreparedStatement의 setxxx() 등에 대한 보든 작업을 개발자가 처리

SELECT의 경우 직접ResultSet처리 

자동으로 Connection close()가능

MyBatis 내부적으로 PreparedStatement처리 

#{prop}와 같이 속성을 지정하면 내부적으로 자동처리 

리턴 타입을 지정하는 경우 자동으로 객체 생성 및 ResultSet 처리 

 

MyBatis는 기존의 SQL을 그대로 활용할 수 있다는 장점이 있고, 진입장벽이 낮은 편이어서 JDBC 대안으로 많이 사용합니다. 스프링 프레임워크의 특징 중 하나는 다른 프레임워크들을 배척하는 대신에 다른 프레임워크들과의 연동을 쉽게 하는 추가적인 라이브러리들이 많다는 것입니다. MyBatis 역시 mybatis-spring이라는 라이브러리를 통해 쉽게 연동작업을 처리할 수 있습니다.

 

먼저 MyBatis 관련 라이브러리를 추가하기 위해 pom.xml 파일에 추가적인 라이브러리들을 설정해보겠습니다.

 

  • spring-jdbc/spring-tx : 스프링에서 데이터베이스 처리와 트랜젝션 처리 (해당 라이브러리들을 MyBatis와 무관하게 보이지만 추가하지 않을 경우 에러가 발생하니 주의)
  • mybatis/mybatis-springMyBatis와 스프링 연동용 라이브러리

 

		<!--https://mvnrepository.com/artifact/org.mybatis/mybatis -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.4.6</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis-spring</artifactId>
			<version>1.3.2</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>

 

 

 

MyBatis에서 가장 핵심적인 객체는 SQLSession이라는 존재와 SQLSessionFactory입니다. SQLSessionFactory의 이름에서 보듯이 내부적으로 SQLSession이라는 것을 만들어 내는 존재인데, 개발에서는 SQLSession을 통해서 Connection을 생성하거나 원하는 SQL을 전달하고, 결과를 리턴 받는 구조로 작성하게 굅니다.

 

root-context.xml에서는 아래와 같은 형태로 작성합니다.

 

	<!--HikariCP Configuration -->
	<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource"
		destroy-method="close">
		<constructor-arg ref="hikariConfig" />
	</bean>
	<bean id="sqlSessionFactory"
		class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
    
    <!--context:component-scan ..... -->

 

스프링에 SqlSessionFactory를 등록하는 작업은 SqlSessionFactoryBean을 이용합니다. 패키지명을 보면 MyBatis의 패키지가 아니라 스프링과 연동 작업을 처리하는 mybatis-spring 라이브러리의 클래스임을 알 수 있습니다.

 

SqlSessionFactoryBean을 이용해서 SqlSession을 사용해 보는 테스트는 기존의 DataSourceTests 클래스에 추가해서 확인합니다.

 

package org.zerock.persistence;
 
import static org.junit.Assert.fail;
 
import java.sql.Connection;
 
import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
import lombok.Setter;
import lombok.extern.log4j.Log4j;
 
@RunWith(SpringJUnit4ClassRunner.class)
//xml설정을 이용하는 경우
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
//JAVA설정을 사용하는 경우 @ContextConfiguration(classes= {RootConfig.class})
@Log4j
public class DataSourceTests {
 
    @Setter(onMethod_ = {@Autowired})
    private DataSource dataSource;
    
    @Setter(onMethod_ = {@Autowired})
    private SqlSessionFactory sqlsessionFactory;
    
    @Test
    public void testMyBatis() {
        try (SqlSession session = sqlsessionFactory.openSession();
        		Connection con = session.getConnection();
        		){
            log.info(session);
            log.info(con);
            
        } catch (Exception e) {
            fail(e.getMessage());
            
        }
    }
    
}
 

 

testMyBatis()는 설정된 SqlSessionFactory 인터페이스 타입의 SqlSessionFactoryBean을 이용해서 생성하고, 이를 이용해서 Connection까지를 테스트합니다. 테스트 코드가 정상적으로 실행된다면 아래와 유사한 로그가 출력되는 것을 볼 수 있습니다.

 

 

Mapper인터페이스를 만들어보기 위해 우선 'scr/main/java''org.zerock.mapper'라는 패키지를 만들고, TimeMapper라는 인터페이스를 추가합니다. 소스 코드는 아래와 같습니다.

 

package org.zerock.mapper;

import org.apache.ibatis.annotations.Select;

public interface TimeMapper {
	
	@Select("SELECT sysdate FROM dual")
	public String getTime();
	
}

 

Mapper를 작성해 주었다면 MyBatis가 동작할 때 Mapper를 인식할 수 있도록 root-context.xml에 추가적인 설명이 필요합니다. 가장 간단한 방식은 <mybatis:scan>이라는 태그를 이용하는 것입니다.

root-context.xml 파일을 열고, 아래쪽의 'Namespaces' 항목에서 'mybatis-spring'탭을 선택합니다.

 

 

	<bean id="sqlSessionFactory"
		class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	<mybatis-spring:scan
		base-package="org.zerock.mapper" /> <!--추가-->

	<context:component-scan
		base-package="org.zerock.sample"></context:component-scan>

 

<mybatis-spring:scan> 태그의 base-package 속성은 지정된 패키지의 모든 MyBatis 관련 어노테이션을 찾아서 처리합니다. Mapper를 설정하는 작업은 각가의 XML이나 Mapper 인터페이스를 설정할 수도 있지만, 매번 너무 번잡하기 때문에 예제는 자동으로 org.zerock.mapper 패키지를 인식하는 방식으로 작성하는 것이 가장 편리합니다.

 

이제 작성한 TimeMapper를 테스트하는 코드를 작성할텐데 'src/test/java''org.zerock.persistence'패키지 안에 TimeMapperTests라는 클래스를 생성해서 처리합니다. 소스 코드는 아래와 같습니다.

 

package org.zerock.persistence;
 
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.zerock.mapper.TimeMapper;
 
import lombok.Setter;
import lombok.extern.log4j.Log4j;
 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
//JAVA설정을 사용하는 경우
//@ContextConfiguration(classes= {RootConfig.class})
@Log4j
public class TimeMapperTests {
 
    @Setter(onMethod_=@Autowired)
    private TimeMapper timeMapper;
    
    @Test
    public void testGetTime() {
        log.info(timeMapper.getClass().getName());
        log.info(timeMapper.getTime());
    
    }

}

 

위의 코드가 정상적으로 동작한다면 스프링 내부에는 TimeMapper 타입으로 만들어진  스프링 객체(빈)가 존재한다는 뜻이 됩니다. 테스트를 실행하면 아래와 같은 로그들이 기록되는 것을 볼 수 있습니다.

 

 

MyBatis를 이용해서 SQL을 처리할 때 어노테이션을 이용하는 방식이 편리하기는 하지만,SQL이 복잡하거나 길어지는 경우에는 어노테이션 보다 XML을 이용하는 방식을 더 선호하게 됩니다. 다행히도 MyBatis-Spring의 겨우 MApper 인터페이스와 XML을 동시에 이용할 수 있습니다.

XML을 작성해서 사용할 때에는 XML 파일의 위치와 XML파일에 지정하는 namespace속성이 중요한데, XML파일 위치의 경우 Mapper 인터페이스가 있는 곳에 같이 작성해야하거나 밑에 사진처럼 src/main/resources 구조에 XML을 저장할 폴더를 생성할 수 있습니다. XML파일을 만들때 규칙은 따로 없지만 Mapper 인터페이스와 같은 이름을 사용하는것이 가독성에 좋습니다.

'src/main/resources'org폴더와 하위에 zerock 폴더, mapper 폴더를 작성하고 그 안에 TimeMapper.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="org.zerock.mapper.TimeMapper">

	<select id="getTime2" resultType="string">
	SELECT sysdate FROM dual
	</select>
</mapper>

 

 

테스트하기위해 TimeMapper 인터페이스에 추가적인 메서드를 선언합니다.

 

package org.zerock.mapper;

import org.apache.ibatis.annotations.Select;

public interface TimeMapper {
	
	@Select("SELECT sysdate FROM dual")
	public String getTime();
	
	public String getTime2();
	
}

 

TimeMapperTests 클래스도 아래 코드를 추가합니다.

 

    @Test
    public void testGetTime2() {
        log.info("getTime2");
        log.info(timeMapper.getTime2());
    
    }

 

getTime2() 결과와 getTime() 결과는 동일합니다.

 

MyBatis는 내부적으로 JDBC의 PreparedStatement를 잉요해서 SQL을 처리함.따라서 SQL에 전달되는 파라미터는 JDBC에서 와 같이 '?'로 치환되어서 처리됩니다. 복잡한 SQL의 경우 '?'로 나오는 값이 제대로 되었는지 확인하기가 쉽지 않고 실행된 SQL의 내용을 정확히 확인하기는 어렵습니다. 이런 문제를 해결하기 위해서 SQL을 변환해서 PreparedStatement에 사용된 '?'가 어떤 값으로 처리되었는지 확인하는 기능을 추가하도록 합니다. SQL 로그를 제대로 보기 위해 log4jdbc-log4j2 라이브러리를 사용합니다.

 

pom.xml에 라이브러리를 설정합니다.

 

		<!-- https://mvnrepository.com/artifact/org.bgee.log4jdbc-log4j2/log4jdbc-log4j2-jdbc4 -->
        <dependency>
            <groupId>org.bgee.log4jdbc-log4j2</groupId>
            <artifactId>log4jdbc-log4j2-jdbc4</artifactId>
            <version>1.16</version>
        </dependency>

 

라이브러리를 추가한 후에는 로그 설정 파일을 추가하는 작업과 JDBC의 연결 정보를 수정해야합니다.

우선 'src/main/resources'내에 log4jdbc.log4j2.properties 파일을 추가합니다.

 

 

안에 내용은  log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator 라고만 써줍니다.

 

실행해보면 이전과는 달리 JDBC와 관련된 로그들이 출력되는 것을 볼 수 있습니다.

 

 

이상으로 포스팅을 마치고 다음 포스팅은 스프링 MVC의 기본 구조에 대해 학습하겠습니다!

Comments