Keep going

MyBatis와 스프링 연동 본문

Records/Spring Framework

MyBatis와 스프링 연동

코딩천재홍 2021. 2. 3. 23:19

MyBatis란?

MyBatis는 'SQL 매핑 프레임워크'로 분류되는데 JDBC 코드의 복잡하고 지루한 작업을 피하는 용도로 많이 사용

전통적인 JDBC 프로그램 MyBatis
- 직접 Connection을 맺고 마지막에 close()
- PreparedStatement 직접 생성 및 처리
- PreparedStatement의 setXXX() 등에 대한 모든 작업을 개발자가 처리
- SELECT의 경우 직접 ResultSet 처리
- 자동으로 Connection close() 가능
- MyBatis 내부적으로 PreparedStatement 처리
- #{prop}와 같이 속성을 지정하면 내부적으로 자동 처리
- 리턴 타입을 지정하는 경우 자동으로 객체 생성 및 ResultSet 처리

 

◆ MyBatis 관련 라이브러리 추가

# porm.xml의 추가되는 라이브러리

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

 

 

# 빈 설정

MyBatis에서 가장 핵심적인 객체는 SQLSession이라는 존재와 SQLSessionFactory 이다.

SqlSessionFactory 는 내부적으로 SQLSession이라는 것을 만들어 내고,

개발에서는 SQLSession을 통해서 Connection을 생성하거나 원하는 SQL을 전달하고, 결과를 리턴 받는 구조로 작성하게 된다.

 

-1. root-context.xml을 이용한 설정

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

스프링에 SqlSessionFactory를 등록하는 작업은 SqlSessionFactoryBean을 이용한다.

 

-2. Java (Root-config) 를 이용한 설정

@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
    sqlSessionFactory.setDataSource(dataSource());
    return (SqlSessionFactory) sqlSessionFactory.getObject();
}
cs

 

 

# 테스트 코드 실행

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)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
//@ContextConfiguration(classes = {RootConfig.class}) ← java 설정을 사용하는 경우
 
@Log4j
public class DataSourceTests {
 
    @Setter(onMethod_ = { @Autowired })
    private DataSource dataSource;
 
    @Setter(onMethod_ = { @Autowired })
    private SqlSessionFactory sqlSessionFactory;
 
    @Test
    public void testConnection() {
        try (Connection con = dataSource.getConnection()) {
 
            log.info(con);
 
        } catch (Exception e) {
            fail(e.getMessage());
        }
    }
 
    @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());
        }
    }
}
 
cs

testMyBatis()는 설정된 SqlSessionFactory 인터페이스 타입의 SqlSessionFactoryBean 을 이용해서 생성하고,

이를 이용해서 Connection까지를 테스트한다.

 

 

 

 

◆ 스프링과의 연동 처리

SQLSessionFactory를 이용해서 코드를 작성해도 직접 connection을 얻어서 JDBC 코딩이 가능하지만, 

Mapper를 작성해주면 SQL을 어떻게 처리할 것인지를 별도의 설정을 분리해주고, 자동으로 처리된다.

Mapper 는 쉽게 말해서 SQL과 그에 대한 처리를 지정하는 역할을 합니다. 

MyBatis-Spring을 이용하는 경우 Mapper를 XML과 인터페이스 + 어노테이션의 형태로 작성할 수 있다.

 

① Mapper 인터페이스

-1. root-context.xml 설정하는 경우

 

TimeMapper 인터페이스
package org.zerock.mapper;
 
import org.apache.ibatis.annotations.Select;
 
public interface TimeMapper {
 
    @Select ("SELECT sysdate FROM dual")
    public String getTime();
    
}
 
cs

 

root-context.xml 에 추가할 내용
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"
 
<mybatis-spring:scan base-package="org.zerock.mapper"/>
cs

 

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
     https://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="oracle.jdbc.driver.OracleDriver"></property>
        <property name="jdbcUrl"
            value="jdbc:oracle:thin:@localhost:1521:XE"></property>
        <property name="username" value="book_ex"></property>
        <property name="password" value="1111"></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>
 
 
cs

<mybatis-spring:scan> 태그의 base-package 속성은 지정된 패키지의 모든 MyBatis 관련 어노테이션을 찾아서 처리한다.

Mapper를 설정하는 작업은 각각의 XML이나 Mapper 인터페이스를 설정할 수도 있지만, 매번 너무 번잡하기 때문에 예제는 자동으로 org.zero.mapper 패키지를 인식하는 방식으로 작성하는 것이 가장 편리하다.

 

 

-2 Java설정을 이용하는 경우

 
RootConfig 클래스
package org.zerock.config;
import javax.sql.DataSource;
 
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
 
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
 
@Configuration
@ComponentScan(basePackages = {"org.zerock.sample"})
@MapperScan(basePackages = {"org.zerock.mapper"})
public class RootConfig {
    
    @Bean
    public DataSource dataSource() {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setDriverClassName("oracle.jdbc.driver.OracleDriver");
        hikariConfig.setJdbcUrl("jdbc:oracle:thin:@localhost:1521:XE");
        hikariConfig.setUsername("book_ex");
        hikariConfig.setPassword("book_ex");
    
        HikariDataSource dataSource = new HikariDataSource(hikariConfig);
        
        return dataSource;
    }
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(dataSource());
        return (SqlSessionFactory) sqlSessionFactory.getObject();
    }
    
}
 
cs

JAVA 설정을 이용하는 경우에는 mybatis-spring에서 사용하는 @MapperScan을 이용해서 처리한다.

 

 

# 테스트 코드 실행

MyBatis-Spring은 Mapper 인터페이스를 이용해서 실제 SQL 처리가 되는 클래스를 자동으로 생성한다.

따라서 개발자들은 인터페이스와 SQL만을 작성하는 방식으로도 모든 JDBC 처리를 끝낼 수 있다.

테스트 코드
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")
//@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());
    }
 
}
 
cs

 

TimeMapperTests 클래스는 지정한 패키지 (org.zerock.mapper) 안에 있는 TimeMapper 클래스가 정상적으로 사용이 가능한지를 알아보기 위한 테스트 코드이다.

위의 코드가 정상적으로 동작한다면 스프링 내부에는 TimeMapper 타입으로 만들어진 Bean 이 존재한다는 뜻이다.

 

실행 결과 일부

위의 코드에서 timeMapper.getClass().getName()은 실제 동작하는 클래스의 이름을 확인해 주는데 실행 결과를 보면 

인터페이스만 만들어 주었는데 내부적으로 적당한 클래스가 만들어진 것을 확인할 수 있다.

 

 

②  XML 매퍼와 같이 쓰기

MyBatis를 이용해서 SQL을 처리할 때 어노테이션을 이용하는 방식이 압도적으로 편리하기는 하나,

SQL이 복잡하거나 길어지는 경우에는 어노테이션 보다는 XML을 이용하는 방식을 더 선호하게 된다.

MyBatis-Spring의 경우 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(); //새로운 메서드 생성
    
}
cs

위의 코드를 보면 getTime2()가 추가되었는데 특이하게도 @Select와 같이 MyBatis의 어노테이션이 존재하지 않고 SQL 역시 존재하지 않다. 실제 SQL은 XML을 이용해서 처리해보겠다.

 

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>
cs

 

XML 매퍼를 이용할 때 신경 써야 하는 부분은 <mapper> 태그의 namespace 속성값이다.

MyBatis는 Mapper 인터페이스와 XML  →  인터페이스 이름과 namespace 속성값을 가지고 판단한다.

위와 같이 org.zerock.mapper.TimeMapper 인터페이스가 존재하고 

XML의 <mapper namespace="org.zerock.mapper.TimeMapper"> 와 같이 동일한 이름이라면 병합해서 처리한다.

 

메서드 선언은 인터페이스에 존재하고 SQL에 대한 처리는 XML을 이용하는 방식이라고 볼 수 있다.

<select> 태그의 id 속성의 값메서드의 이름과 동일하게 맞춰야 한다.

resultType 속성은 인터페이스에 선언된 메서드의 리턴 타입과 동일하게 작성해야한다.

 

 

# 테스트 코드 실행

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")
//@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());
    }
    
    @Test
    public void testGetTime2() {
        log.info("getTime2");
        log.info(timeMapper.getTime2());
    }
 
}
 
cs

실행 결과

getTime2() 테스트 코드의 결과는 getTime()과 동일하다.

 

 

◆ log4jdbc-log4j2 설정

Mybatis는 내부적으로 JDBC의 PreparedStatement를 이용해서 SQL을 처리한다.

SQL에 전달되는 파라미터는 JDBC에서와 같이 '?' 로 치환되어서 처리 된다.

복잡한 SQL의 경우 '?'로 나오는 값이 확인하기가 쉽지 않고 실행된 SQL 내용을 정확히 확인하기 어렵다.

이러한 문제를 해결위해 PreparedStatement에 사용된 '?' 가 어떤 값으로 처리되었는지 확인하는 기능 (log4jdbc-log4j2 라이브러리) 을 추가할 것이다.

 

#porm.xml 설정

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

라이브러리를 추가한 후에는 

1) 로그 설정 파일을 추가하는 작업과

2) JDBC의 연결 정보를 수정해야 한다.

 

로그 설정 파일 경로

파일 설정 코드
log4jdbc.spylogelegator.name=net.sf.log4jdbc.log.log.slf4j.Slf4jSpyLogDelegator
cs

 

 

# JDBC드라이버와 URL 정보 수정

 

-1. root-context.xml

<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:XE"></property> // 수정한 
    <property name="username" value="book_ex"></property>
    <property name="password" value="1111"></property>
</bean>
cs

 

 

-2. Root-config 설정

@Bean
    public DataSource dataSource() {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setDriverClassName("net.sf.log4jdbc.sql.jdbcapi.DriverSpy"); // 변경한 부분
        hikariConfig.setJdbcUrl("jdbc:log4jdbc:oracle:thin:@localhost:1521:XE"); // 변경한 부분
        hikariConfig.setUsername("book_ex");
        hikariConfig.setPassword("book_ex");
    
        HikariDataSource dataSource = new HikariDataSource(hikariConfig);
        
        return dataSource;
    }
cs

 

dataSource() 메서드에서 변경되는 부분

- JDBC 드라이버 클래스 'net.sf.log4jdbc.sql.jdbcapi.Driverspy'로 수정하는 작업

- URL 부분에서 중간에 'log4jdbc' 문자열이 추가되는 부분

 

 

# 테스트 코드 실행

테스트 코드를 실행해보면 이전과 달리 JDBC와 관련된 로그들이 출력되는 것을 볼 수 있다.

 

 


출처 : 코드로 배우는 스프링 웹 프로젝트 [구멍가게 코딩단]

Comments