코드로 배우는 스프링 웹 프로잭트 Part1
Chapter04 Mybatis와 스프링 연동
이번장에서는 스프링 프레임워크와 Mybatis를 연동해서 좀 더 빠르게 SQL을 처리할 수 있는 구조를 만들어보도록 하곘습니다.
4.1 MyBatis
MyBatis는 흔히 SQL 매핑(mapping)프레임워크로 분류 되는데, 개발자들은 JDBC 코드의 복잡하고 지루한 작업을 피하는 용도로 많이 사용 합니다.
전통적인 JDBC 프로그래밍의 구조와 비교해보면 MyBatis의 장점을 파악할 수 있습니다.
MyBatis는 기존의 SQL을 그대로 활용할 수 있다는 장점이 있고, 진입장벽이 낮은편이여서 JDBC의 대안으로 많이 사용 합니다.
스프링 프레임워크의 특징 중 하나는 다른 프레임워크들과의 연동을 쉽게 할 수 있는 추가적인 라이브러리들이 많다는 것입니다.
MyBatis 역시 mybatis-spring 이라는 라이브러리를 통해 쉽게 연동을 할 수 있습니다.
4.1.1 MyBatis 관련 라이브러리 추가
Mybatis와 mybatis-spring을 이용하기 위해서는 pom.xml 파일에 추가적인 라이브러리들을 설정해야 합니다.
- spring-jdbc/spring-tx : 스프링에서 데이터베이스 처리와 트랜잭션 처리
- mybatis/mybatis-spring : Mybatis와 스프링 연동용 라이브러리
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<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>
4.1.2 SQLSessionFactory
Mybatis에서 가장 핵심적인 객체는 SQLSession이라는 존재와 SQLSessionFactory입니다.
SQLSessionFactory의 이름에서 보듯 내부적으로 SQLSession이라는 것을 만들어 내는 존재인데, 개발에서는 SQLSession을 통해서 Connection을 생성하거나 원하는 SQL을 전달하고, 결과를 리턴받는 구조로 작성하게 됩니다.
root-context.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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- Root Context: defines shared resources visible to all other web components -->
<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
<property name="driverClassName"
value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"></property>
<property name="jdbcUrl"
value="jdbc:log4jdbc:oracle:thin:@localhost:1521:orcl"></property>
<property name="username" value="c##book_ex"></property>
<property name="password" value="book_ex"></property>
</bean>
<!-- 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 base-package="org.zerock.sample">
</context:component-scan>
</beans>
스프링에 SqlSessionFactory를 등록하는 작업은 SqlSessionFactoryBean을 이용합니다.
패키지명을 보면 MyBatis의 패키지가 아니라 스프링과 연동 작업을 처리하는 mybatis-spring 라이브러리의 클래스임을 알 수 있습니다.
4.2 스프링과의 연동 처리
SQLSessionFactory를 이용해서 코드를 작성해도 직접 Connection을 얻어서 JDBC코딩이 가능하지만 좀 더 편하게 작업하기 위해서는 SQL을 어떻게 처리할 것인지를 별도의 설정을 분리해주고, 자동으로 처리되는 방식을 이용하는 것이 좋습니다.
이를 위해서는 MyBatis의 Mapper라는 존재를 작성해 줘야 합니다.
Mapper는 쉽게 말해서 SQL과 그에 대한 처리를 지정하는 역할을 합니다.
MyBatis-Spring을 잉요하는 경우에는 mapper를 XML과 인터페이스 + 어노테이션의 형태로 생성할 수 있습니다.
4.2.1 Mapper 인터페이스
Mapper를 작성하는 작업은 XML을 이용할 수도 있지만, 이번 예제에서는 최소한의 코드를 작성하는 Mapper 인터페이스를 사용해 보겠습니다.
우선 org.zerock.mapper라는 패키지를 만들고 TimeMapper라는 인터페이스를 추가 합니다.
TimeMapper인터페이스에는 MyBatis의 어노테이션을 이용해서 SQL을 메서드에 추가 합니다
package org.zerock.mapper;
import org.apache.ibatis.annotations.Select;
public interface TimeMapper {
@Select("SELECT sysdate FROM dual")
public String getTime();
}
Mapper 설정
Mapper를 작성해 주었다면 MyBatis가 동작할 때 Mapper를 인식할 수 있도록 root-context.xml에 추가적인 설정이 필요합니다.
가장 간단한 방식은 <mybatis:scan>이라는 태그를 이용하는 것입니다.
root-context.xml 파일을 열고, 아래쪽의 Namespaces 항목에서 mybatis-spring탭을 선택 합니다.
<mybatis-spring:scan base-package="org.zerock.mapper"/>
<mybatis-spring:scan> 태그의 base-packgae 속성은 지정된 패키지의 모든 MyBatis 관련 어노테이션을 찾아서 처리합니다.
Mapper를 설정하는 작업은 각각의 XML이나 Mapper인터페이스를 설정할 수도 있지만, 매번 너무 번잡하기 때문에 예제는 자동으로 org.zerock.mapper 패키지를 인식하는 방식으로 작성 하였습니다.
4.2.2 Mapper 테스트
MyBatis-Spring은 Mapper인터페이스를 이용해서 실제 SQL 처리가 되는 클래스를 자동으로 생성 합니다.
따라서 개발자들은 인터페이스와 SQL만을 작성한느 방식으로도 모든 JDBC 처리를 끝낼 수 있습니다.
작성한 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 = { org.zerock.config.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());
}
}
TimeMapperTests 클래스는 TimeMapper가 정상적으로 사용이 가능한 지를 알아 보기 위한 테스트 코드 입니다.
위의 코드가 정상적으로 동작 한다면 스프링 내부에는 TimeMapper타입으로 만들어진 스프링 객체(bean)가 존재한다는 뜻이 됩니다.
위 코드에서 timeMapper.getClass().getName()은 실제 동작하는 클래스의 이름을 확인해주는데 실행 결과를 보면 개발시 인터페이스만 만들어 주었는데 내부적으로 적당한 클래스가 만들어진것을 확인할 수 있습니다.
(자세한 내용은 스프링 AOP에서 설명 하겠습니다.)
여기서는 스프링이 인터페이스를 이용해서 객체를 생성한다는 사실에 주목합니다.
테스트를 실행하면 아래와 같은 로그들이 기록 되는 것을 볼 수 있습니다.
4.2.3 XML 매퍼와 같이 쓰기
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 폴더를 생성합니다.
(폴더를 만들때는 반드시 하나씩 생성 합니다.)
XML 파일에는 MyBatis의 XML 매퍼에서 이용하는 태그에 대한 설정이 필요합니다.
잉에 대한 자세한 정보는 http://www.mybatis.org/mybatis-3/ko/sqlmap-xml.html 을 통해 확인 할 수 있습니다.
Mapper 인터페이스와 XMl을 같이 이용해보기 위해 기존의 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();
}
TimePapper 인터페이스를 보면 getTime2()가 추가된 것을 볼 수 있는데 특이하게도 @Select와 같이 Mybatis의 어노테이션이 존재하지 않고 SQL 역시 존재하지 않는것 을 볼 수 있습니다.
실제 SQL은 XML을 통해 처리할 것이므로, 생성한 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>
XML 매퍼를 이용할 때 신경 써야 하는 부분은 <mapper> 태그의 namesapce 속성값 입니다.
MyBatis 는 Mapper 인터페이스와 XML을 인터페이스의 이름과 namespace 속성값을 가지고 판단 합니다.
위와 같이 org.zerock.mapper.TimeMapper 인터페이스가 존재하고, XMl의 <mapper namespace="org.zerock.mapper.TimeMapper">와 같이 동일한 이름이 존재하면 이를 병합해서 처리합니다.
따라서 위의 경우 메서드 선언은 인터페이스에 존재하고, SQL에 대한 처리는 XML을 이용하는 방식이라고 할 수 있습니다.
<select> 태그의 id 속성값은 메서드의 이름과 동일하게 맞춰야 합니다.
<select> 태그의 경우 resultType 속성을 가지는데 이 값은 인터페이스에 선언된 메서드의 리턴 타입과 동일하게 작성 합니다.
최종 확인을 위해 TimeMapperTests 클래스를 이용해서 테스트 작업을 진행 해주겠습니다.
getTime2의 테스트 결과는 getTime()과 동일 합니다.
4.3 log4jdbc-log4j2 설정
Mybatis는 내부적으로 JDBC의 PreparedStatement를 이용해서 SQL을 처리합니다.
따라서 SQL에 전달되는 파라미터는 JDBC에서와 같이 "?" 로 치환되어 처리됩니다.
그래서 어떤 파라미터가 전달 되는지 확인 하기가 어려운데 log4jdbc-log4j2 라이브러리를 이용하면 확인 할 수 있습니다.
라이브러리 다운을 위해 pom.xml에 아래 내용을 추가 해줍니다.
<dependency>
<groupId>org.bgee.log4jdbc-log4j2</groupId>
<artifactId>log4jdbc-log4j2-jdbc4</artifactId>
<version>1.16</version>
</dependency>
라이브러리를 추가한 후에는 로그파일 설정을 추가하는 작업, JDBC의 연결정보를 수정 해야 합니다.
log4jdbc.log4j2.properties를 추가 해야 하는데 이미 앞서 했기때문에 넘어가도록 합니다.
log4jdbc를 이용하는 경우는 JDBC드라이버와 URL 정보를 수정해야 합니다.
root-context.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:context="http://www.springframework.org/schema/context"
xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- Root Context: defines shared resources visible to all other web components -->
<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
<property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"></property>
<property name="jdbcUrl" value="jdbc:log4jdbc:oracle:thin:@localhost:1521:orcl"></property>
<property name="username" value="c##book_ex"></property>
<property name="password" value="book_ex"></property>
</bean>
<!-- 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>
<mybatis-spring:scan base-package="org.zerock.mapper"/>
<context:component-scan base-package="org.zerock.sample">
</context:component-scan>
</beans>
dataSource() 메서드에서 변경되는 부분은 JDBC 드라이버 클래스를 "net.sf.log4jdbc.sql.jdbcapi.DriverSpy"로 수정하는 작업과 JDBC 연결 URL 부분에서 중간에 log4jdbc 문자열이 추가되는 부분입니다.
이 두 설정이 제대로 되어 있지 않으면 데이터베이스의 로그가 정상적으로 기록되지 않습니다.
설정을 변경한 후 테스트 코드를 실행 해보면 이전과 달리 JDBC와 관련된 로그들이 출력 되는 것을 볼 수 있습니다.
4.3.1 로그 레벨의 설정
테스트 코드를 실행하면 상당히 많은 양의 로그가 출력되기 때문에 처음 개발할댸는 좋지만 시간이 지나면서 불편하다고 느낄 수 있습니다.
이런 상황에서 로그 레벨을 이용해서 수정해 줄 수 있습니다.
테스트 코드가 실행될 때의 로그와 관련된 설정은 src/test/resource/log4j.xml을 이용 합니다.
만일 log4jdbc에서 출력되는 로그를 조절하고 싶다면 추가적은 <logger>를 지정해서 처리합니다.
<logger name="jdbc.audit">
<level value="warn" />
</logger>
<logger name="jdbc.resultset">
<level value="warn" />
</logger>
<logger name="jdbc.connection">
<level value="warn" />
</logger>
기본 설정의 로그는 info 레벨이기 때문에 warn과 같이 좀 더 높은 레벨의 로그만 기록 하게 되면 테스트 코드를 실행할 때 이전에 비해 로그의 양이 줄어드는 것을 확인 할 수 있습니다.
로그 레벨에 대한 자세한 설명은 구글에 "log4jdbc 로그레벨"을 검색해서 참고 하시기 바랍니다.
'개발관련 > 코드로 배우는 스프링 웹 프로젝트(개정판)' 카테고리의 다른 글
Part2 - 스프링 MVC Controller Chapter06 (0) | 2023.02.20 |
---|---|
Part2 - 스프링 MVC의 기본 구조 Chapter05 (1) | 2023.02.19 |
Part1 - 스프링 개발 환경 구축 Chapter03 (0) | 2022.12.26 |
Part1 - 스프링 개발 환경 구축 Chapter02 (0) | 2022.12.23 |
Part1 - 스프링 개발 환경 구축 Chapter01 (2) | 2022.12.16 |