개발/[Spring] 블로그 만들기

[코드로 배우는 스프링 웹 프로젝트] 4강. MyBatis와 스프링 연동

ee2ee2 2021. 11. 30. 17:29
728x90
반응형

→해당 프로젝트는 코드로 배우는 스프링 웹 프로젝트(개정판)을 기반으로 진행됩니다.


MyBatis?

: 간략히 말하면, SQL Mapping 프레임워크로 분류되는데, 개발자들은 JDBC 코드의 복잡한 작업을 피하는 용도로 주로 사용한다. MyBatis는 기존의 SQL을 그대로 활용할 수 있는 장점이 있고, 대체로 쉬운 편이어서 JDBC의 대안으로 많이 사용된다.

JDBC 프로그렘 MyBatis

-. 직접 Connection을 맺고 마지막에 Close
-. PreparedStatement 직접 생성 처리
-. PreparedStatement의 Setter 등에 대한 모든 작업을 개발자가 처리
-. SELECT의 경우 직접 ResultSet 처리

-. 자동으로 Connection close() 가능
-. MyBatis 내부적으로 PreparedStatement 처리
-. #{prop}와 같이 속성을 지정하면 내부적으로 자동 처리
-. 리턴 타입을 지정하는 경우 자동으로 객체 생성 및 ResultSet 처리

mybatis-spring이라는 라이브러리를 통해 쉽게 연동작업 처리가 가능

 


MyBatis 관련 라이브러리 추가하기

 

pom.xml 추가

<!-- MyBatis 관련 라이브러리 추가 시작-->
		<!-- mybatis/mybatis-spring : Mybatis와 spring 연동용 라이브러리 -->
		<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>
		
		<!-- spring-jdbc/spring-tx :스프링에서 데이터베이스 처리와 트랜잭션 처리 -->
		<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 관련 라이브러리 추가 끝 -->

 

SQLSesssionFactory

: MyBatis에서 핵심적인 객체는 SQLSessionSQLSessionFactory이다.

SQLSessionFactory란, 내부적으로 SQLSession이라는 것을 만들어 내는 존재인데, 개발에서는 SQLSession을 통해서 Connection을 생성하거나 원하는 SQL을 전달하고 결과는 리턴받는 구조로 작성하게 된다.

root-context.xml 추가

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

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

 

Java 설정을 이용하는 경우 : RootConfig.java 추가

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

DataSourceTests.java : Test 코드로 확인해보기

  : SqlSessionFactoryBean을 이용해서 SqlSession을 사용 해보는 테스트

@RunWith(SpringJUnit4ClassRunner.class)
@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 conn = session.getConnection();) {
			log.info(session);
			log.info(conn);
		} catch (Exception e) {
			fail(e.getMessage());
		}
	}
    
    
    .....
    생략
    }

결과확인

 


스프링과의 연동처리

: SQL을 어떻게 처리할 것인지를 별도의 설정을 분리하고, 자동으로 처리되는 방식을 이용해본다. 이를 위해서는 MyBatis의 Mapper라는 존재를 작성해줘야 한다.

Mapper란?

: SQL과 그에 대한 처리를 지정하는 역할을 한다. MyBatis-Spring을 이용하는 경우에는 Mapper를 XML과 인터페이스+어노테이션의 형태로 작성 가능하다.


Mapper 인터페이스 작성하기

: org.zerock.mapper라는 패키지 생성 후, TimeMapper라는 인터페이스를 작성한다.

TimeMapper.java

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에 추가적으로 설정을 해준다.

 

root-context.xml 일부

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

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

 

위 내용을 Java 설정을 이용하는 경우

RootConfig.java 일부

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() {
    
    ...
    생략

Test 코드로 확인해보기

TimaMapperTests.java

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());
	}

}

결과 확인

: 인터페이스만 만들어 주었는데 내부적으로 적당한 클래스가 만들어진 것을 확인할 수 있다. 스프링이 인터페이스를 이용해서 객체를 생성한다는 사실에 초점을 맞추자.

 


XML 매퍼와 같이 쓰기

: SQL이 길어지거나 복잡한 결루에는 어노테이션보다 XML을 이용하는 방식이 더 편리하다.

TimaMapper.xml 생성하기

: 매퍼에 대한 자세한 사항은 이 곳에서 확인 가능하다.

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

<!-- 자세한 정보 참고 URL : http://www.mybatis.org/mybatis-3/ko/sqlmap-xml.html -->
<!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 매퍼를 이용할 때 신경써야 하는 부분은 태그의 Namespace 속성값이다. MyBatis는 Mapper 인터페이스와 XML을 인터페이스 이름과 namespace 속성값을 가지고 판단한다.

 

TimaMapper.java

: Mapper 인터페이스와 XML을 같이 이용해보기 위해 기존 TimeMapper 인터페이스에 getTime2 메소드 추가

package org.zerock.mapper;

import org.apache.ibatis.annotations.Select;

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

Test 코드로 확인해보기

TimaMapperTests.java

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

결과 확인

: getTime()과 동일한 결과 출력

 

 


log4jdbc-log4j2 설정하기

: 실제 적용되는  SQL 로그를 제대로 보기 위해서는 log4jdbc-log4j2 라이브러리를 사용해야 한다. 이를 위해선 아래 3가지 단계를 적용해야한다.

1) pom.xml 추가

 		<!-- SQL의 로그를 제대로 보기 위한 라이브러리 사용 -->
 		<!-- 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>

 

2) 로그 설정 파일 추가

log4jdbc.log4j2.properties

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

 

3) JDBC의 연결 정보 수정

root-context.xml

	<!-- HikariCP의 bean 설정 -->
	<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
<!-- 		<property name="driverClassName" value="oracle.jdbc.OracleDriver"></property>
		<property name="jdbcUrl" value="jdbc:oracle:thin:@localhost:1522:xe"></property> -->
		
		<!-- log4jdbc를 이용하기 위해서는 JDBC드라이버와 URL 정보 수정이 필요함. -->
		<property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"></property>
		<property name="jdbcUrl" value="jdbc:log4jdbc:oracle:thin:@localhost:1522:xe"></property>
		
		<property name="username" value="book_ex"></property>
		<property name="password" value="book_ex"></property>
	</bean>

 

위 내용을 Java 설정을 이용하는 경우

RootConfig.java 일부

	@Bean
	public DataSource dataSource() {
		HikariConfig hikariConfig =  new HikariConfig();
		//hikariConfig.setDriverClassName("oracle.jdbc.driver.OracleDriver");
		//hikariConfig.setJdbcUrl("jdbc:oracle:thin:@localhost:1522:xe");
		
		/* log4jdbc를 이용하기 위해서는 JDBC드라이버와 URL 정보 수정이 필요함. */
		hikariConfig.setDriverClassName("net.sf.log4jdbc.sql.jdbcapi.DriverSpy");
		hikariConfig.setJdbcUrl("jdbc:log4jdbc:oracle:thin:@localhost:1522:xe");
		
		hikariConfig.setUsername("book_ex");
		hikariConfig.setPassword("book_ex");
		
		HikariDataSource dataSource = new HikariDataSource(hikariConfig);
		return dataSource;
	}

 

TimaMapperTests.java 테스트 코드 재실행 결과

 


 

로그의 레벨 설정

: 테스트 코드 실행시 상당히 많은 양의 로그(기본 info레벨) 가 나오기 때문에 로그 레벨을 높여 로그의 양을 줄이도록 해보자.

log4j.xml 일부

: 로그 레벨에 대한 자세한 설명은 이 곳을 참고하면 된다.

	<logger name="jdbc.audit">
		<level value="warn" />
	</logger>
	
	<logger name="jdbc.resultset">
		<level value="warn" />
	</logger>
	
	<logger name="jdbc.connection">
		<level value="warn" />
	</logger>

	<!-- Root Logger -->
	<root>
		<priority value="info" />
		<appender-ref ref="console" />
	</root>

 

결과 확인

 

다음 시간에는 스프링 MVC 설정으로 찾아오겠다~