SPRING
2020.10.21 / 10:34

Part1. 개발환경 구축 - 스프링과 MySql + Mybatis 연동

추석돌이
추천 수 16

이미 mysql 설치와 기본적인 설정은 완료되었고 스프링과 연동만 시키면 된다. 특히 커넥션 풀을 이용해서 여러명의 사용자를 동시에 처리해야 하기 때문에 아예 스프링에 커넥션 풀을 등록해서 사용하는 것이 좋을 것이다. java에서는 DataSource 라는 인터페이스를 통해서 커넥션 풀을 사용한다. 이걸 이용하면 매번 데이터베이스와 연결하는 것이 아니라 미리 연결을 맺어주고 변환하는 구조를 이용하기 때문에 성능향상에 도움이 된다.

혹시 mysql 설치방법이 모른다면 다음 링크참조

https://all-record.tistory.com/93

커넥션 풀은 여러 종류가 있고 spring-jdbc 라이브러리를 이용하는 방식도 있지만, 예제는 최근 유행하는 히카리CP를 이용한다.

히카리 성능에 대해서는 다음 링크 참조

https://jeong-pro.tistory.com/162

책 개정판이 조금 아쉬운 점이 오라클 디비로만 설정하기 때문에 코딩단 카페에서 저자가 배포한 mysql 설정 파일을 봐야만 제대로된 연동이 가능하다.

1. 라이브러리 추가와 DataSource 설정

pom.xml을 수정해서 HikariCP를 추가한다.

<!-- https://mvnrepository.com/artifact/com.zaxxer/HikariCP --> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>3.3.1</version> </dependency>

이제 root-context.xml 에 직접 <bean> 태그를 정의해서 작성하는데 개정판에는 해당 내용이 없으므로 다음 코드를 참고하자.

mybatis까지 설정완료된 코드이다

<?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:mybatis-spring="http://mybatis.org/schema/mybatis-spring" xmlns:context="http://www.springframework.org/schema/context" 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="com.mysql.cj.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/book_ex?useSSL=false&amp;serverTimezone=Asia/Seoul"></property> <property name="username" value="zerock"></property> <property name="password" value="zerock"></property> </bean> <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close"> <constructor-arg ref="hikariConfig" /> </bean> <context:component-scan base-package="org.zerock.service"> </context:component-scan> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"></property> <property name="typeAliasesPackage" value="org.zerock.domain"></property> </bean> <mybatis-spring:scan base-package="org.zerock.mapper"/> </beans>

빈을 이렇게 정의한 후에는 항상 테스트를 해보자. 'src/text/java'에 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) @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()); } } }

이 테스트는 스프링에 빈으로 등록된 DataSource를 이용해서 Connection을 제대로 처리할 수 있는지를 확인하는 용도이다 testConnection()을 실?低떨? 내부적으로 히카리CP가 시작되고, 종료되는 로그를 볼 수 있다.

INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... INFO : com.zaxxer.hikari.pool.PoolBase - HikariPool-1 - Driver does not support get/set network timeout for connections. (net.sf.log4jdbc.sql.jdbcapi.ConnectionSpy.getNetworkTimeout()I) INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. INFO : org.zerock.persistence.DataSourceTests - org.apache.ibatis.session.defaults.DefaultSqlSession@5884a914 INFO : org.zerock.persistence.DataSourceTests - HikariProxyConnection@2032169857 wrapping net.sf.log4jdbc.sql.jdbcapi.ConnectionSpy@491b9b8 INFO : org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@6f1fba17: startup date [Wed Jul 24 21:26:02 KST 2019]; root of context hierarchy INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.

Mybatis 연동

MyBatis는 흔히 SQL매핑 프레임워크로 분류되는데, 개발자들은 JDBC코드의 복잡하고 지루한 작업을 피하는 용도로 사용한다. 장점으로는 다음과 같다.

- 자동으로 Connection close() rksmd

- Mybatis내부적으로 PerparedStatement처리

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

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

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

1. Mybatis 관련 라이브러리 추가

Mybatis 와 mybatis-spring을 사용하기 위해서 pom.xml 파일에 추가적인 라이브러리들을 설정해야 한다.

다음은 pom.xml에 추가되는 라이브러리 코드이다.

<!-- 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-jdbc</artifactId> <version>${org.springframework-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${org.springframework-version}</version> </dependency>

2. SQLSessionFactory

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

root-context.xml 에 sqlSessionFactory 빈을 추가해야 하는데 위에 이미 해당 코드가 포함된 root-context.xml 소스를 올려놨다.

3. 스프링과의 연동

3.1 Mapper 인터페이스

Mapper를 작성하는 작업은 xml을 이용할 수도 있지만, 이번 예제는 최소한의 코드를 작성하는 Mapper인터페이스를 사용해보겠다.

TimeMapper 라는 인터페이스를 추가한다. Mapper 에 대한 설명은 따로 포스팅해야겠다.

TimeMapper 인터페이스는 MyBatis의 어노테이션을 이용하여 SQL을 메서드에 추가한다.

package org.zerock.mapper; import org.apache.ibatis.annotations.Select; public interface TimeMapper { @Select("SELECT sysdate() From dual") public String getTime(); public String getTime2(); }

작성했다면 Mybatis가 동작할 때 Mapper를 인식할 수 있도록, root-context,xml 에 추가설정이 필요하다. 간단한 방식은 <mybatis:scan>이라는 태그를 이용하는 것이다.

다음은 root-context.xml의 일부이다.

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"></property> <property name="typeAliasesPackage" value="org.zerock.domain"></property> </bean> <mybatis-spring:scan base-package="org.zerock.mapper"/>

base-package 속성은 지정된 패키지의 모든 MyBatis관련 어노테이션을 찾아서 처리한다. Mapper를 설정하는 작업은 각각의 xml 이나 mapper 인터페이스를 설정할 수도 있지만, 너무 번잡하기 때문에 자동으로 패키지를 인식하는 방식으로 처리하는 방법이 가장 편리하다.

3.2 Mapper 테스트

Mybatis-spring 은 Mapper 인터페이스를 이용해서 실제 sql 처리가 되는 클래스를 자동으로 생성한다. 따라서 인터페이스와 sql 만으로 작성하는 방식으로도 모든 jdbc처리를 끝낼 수 있다.

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

우선 스프링이 인터페이스를 이용해서 객체를 생성한다는 사실에 주목하자.

3.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 폴더 내 다음처럼 mapper 폴더를 작성한다.

한번에 한 폴더씩 생성

xml 파일에는 Mybatis의 XML 매퍼에서 사용하는 태그에 대한 설정이 필요하다.

Mapper 인터페이스와 xml을 같이 이용해 보기 위해서 기존의 TimeMapper 인터페이스에 추가적인 메서드를 선언한다.

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

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>

<select> 태그의 id 속성값은 메서드의 이름과 동일하게 맞춰야 한다. <select> 태그의 경우 resultType 속성을 가지는데 이 값은 인터페이스에 선언된 메서드의 리턴 타입과 동일하게 작성한다.

4. log4jdbc-log4j2 설정

Mybatis는 내부적으로 JDBC의 PreparedStatement 를 이용해서 SQL을 처리한다. 따라서 파라미터는 jdbc에서 처럼 ? 으로 치환되어서 처리된다. 복잡한 SQL 의 경우 ? 의 값이 제대로 나왔는지 확인이 어렵고 실행된 SQL의 내용을 정확히 확인하기 어렵다. 이런문제를 확인하려고 log4jdbc-log4j2라이브러리가 필요한 것이다.

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

pom.xml에 위와같이 라이브러리를 추가한다. 추가한 후에는 (1) 로그 설정파일을 추가하는 작업 (2) JDBC연결 정보를 수정해야 한다. src/main/resources 밑에 log4jdbc.log4j2.properties 파일을 추가한다.

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

log4jdbc를 이용하는 경우 JDBC 드라이버와 URL 정보를 수정해야 한다. root-context.xml의 일부를 수정하자. 다시말하지만 root-context.xml은 bean을 설정하는 곳이다.

<bean id ="hikariConfig" class="com.zaxxer.hikari.HikariConfig"> <!-- <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/book_ex?useSSL=false&amp;serverTimezone=Asia/Seoul"></property> --> <property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"></property> <property name="jdbcUrl" value="jdbc:log4jdbc:mysql://127.0.0.1:3306/book_ex?useSSL=false&amp;serverTimezone=Asia/Seoul"></property> <property name="username" value="root"></property> <property name="password" value="thdgkals12!"></property> </bean>

이렇게 수정하면 되는데 dataSource() 메서드에서 변경되는 부분은 JDBC드라이버의 클래스를 'net.sf.log4jdbc.sql.jdbcapo.DriverSpy로 변경하는 작업과 JDBC 연결 URL 부분에서 중간에 log4jdbc 문자열이 추가되는 부분이다. 이 두 설정이 되어있지 않으면 로그가 정상적으로 기록되지 않는다.