[Spring] MyBatis와 스프링 연동 (+log4jdbc)

    반응형

     

    이 글은 코드로 배우는 스프링 웹 프로젝트(남가람북스, 구멍가게 코딩단)을 읽고 공부한 내용을 바탕으로 정리한 글입니다.


     

    1. MyBatis

    MyBatis는 SQL 매핑 프레임워크입니다. 개발자들이 JDBC 코드의 복잡하고 지루한 작업을 피하는 용도로 사용합니다.

    전통적이 JDBC 프로그램의 경우, 직접 Connection을 맺고 마지막에 close()를 해주어야 하고, PreparedStatement를 직접 생성하고 처리해주어야 합니다. 하지만, MyBatis의 경우, 자동으로 Connection close()를 해주고, MyBatis 내부적으로 PreparedStatement를 처리해줍니다. 그리고 #{prop}처럼 속성을 지정하면 내부적으로 자동으로 처리합니다.

    아래는 JDBC를 이용해 작성한 코드와 MyBatis를 이용해 작성한 코드를 각각 나타낸 것입니다. (출처: https://hyoni-k.tistory.com/70)

    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.List;
     
    import com.address.web.dao.NoticeDao;
    import com.address.web.entity.Notice;
    import com.address.web.entity.NoticeView;
     
    //@Repository
    public class JdbcNoticeDao implements NoticeDao {
     
       @Override
       public List<NoticeView> getList() throws ClassNotFoundException, SQLException {
          int page = 1;
          List<NoticeView> list = new ArrayList<>();
          int index = 0;
          
          String sql = "SELECT * FROM Notice ORDER BY regdate DESC LIMIT 10 OFFSET ?";    
          String url = "jdbc:mysql://dev.notead.com:0000/address?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=UTC";
     
          Class.forName("com.mysql.cj.jdbc.Driver");
          Connection con = DriverManager.getConnection(url, "address", "123");         
          PreparedStatement st = con.prepareStatement(sql);
          st.setInt(1, (page-1)*10); 
          
          ResultSet rs = st.executeQuery();
                
          while (rs.next()) {
             NoticeView noticeView = new NoticeView();
             noticeView.setId(rs.getInt("ID"));
             noticeView.setTitle(rs.getString("TITLE"));
             noticeView.setWriterId(rs.getString("writerId"));
             noticeView.setRegdate(rs.getDate("REGDATE"));
             noticeView.setHit(rs.getInt("HIT"));
             noticeView.setFiles(rs.getString("FILES"));
             noticeView.setPub(rs.getBoolean("PUB"));
             
             list.add(noticeView);      
          }
     
          rs.close();
          st.close();
          con.close();
          
          return list;
       }
     
       @Override
       public Notice get(int id) {
          // TODO Auto-generated method stub
          return null;
       }
     
       @Override
       public int insert(Notice notice) {
          // TODO Auto-generated method stub
          return 0;
       }
     
       @Override
       public int update(Notice notice) {
          // TODO Auto-generated method stub
          return 0;
       }
     
       @Override
       public int delete(int id) {
          // TODO Auto-generated method stub
          return 0;
       }
     
    }
    import java.sql.SQLException;
    import java.util.List;
     
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Select;
     
    import com.address.web.entity.Notice;
    import com.address.web.entity.NoticeView;
     
    @Mapper     
    public interface NoticeDao {
        
        @Select("SELECT * FROM Notice WHERE ${field} LIKE '%${query}%' ORDER BY regdate DESC LIMIT 10")
        List<NoticeView> getList(int page, String query, String field) throws ClassNotFoundException, SQLException;
        
        @Select("SELECT * FROM Notice WHERE id= #{id}")
        Notice get(int id);
        int insert(Notice notice);
        int update(Notice notice);
        int delete(int id);
    }

    MyBatis가 자동으로 처리해주는 것이 많아 코드가 훨씬 간결해진 것을 확인할 수 있습니다.

    MyBatis도 mybatis-spring 라이브러리를 사용하여 쉽게 연동할 수 있습니다. 전체적인 구조는 아래와 같습니다.

     

    2. MyBatis 관련 라이브러리 추가

    pom.xml에 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>

     

    3. SqlSessionFactory

    MyBatis에서 가장 핵심 객체는 SqlSessionSQLSessionFactory입니다. 스프링에 SqlSessionFactory를 등록하는 작업은 SqlSessionFactoryBean을 이용합니다. 따라서 applicationContext.xml에 다음 코드를 추가해줍니다.

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

    아래 테스트 코드를 추가로 작성해서 돌려보면 SqlSession 객체가 생성되었음을 확인할 수 있습니다.

    ...
    import org.apache.ibatis.session.SqlSession;
    
    ...
    public class DataSourceTests {
    	...
        @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());
            }
        }
    }

     

    4. 스프링과 연동하기

    SQLSessionFactory를 이용해서 SQL 쿼리문을 직접 작성하고 직접 Connection을 얻어서 JDBC 코딩이 가능하지만, 좀 더 편하게 작업하기 위해서는 SQL을 어떻게 처리할 것인지 별도의 설정을 분리해주고, 자동으로 처리되는 방식을 이용하는 것이 좋습니다. 이를 위해 MyBatis의 Mapper를 작성해보겠습니다. Mapper는 SQL과 그에 대한 처리를 매핑해주는 역할입니다. mybatis-spring 라이브러리를 이용하여 XML과 인터페이스 + 어노테이션 형태로 Mapper를 작성할 수 있습니다.

    1. Mapper 인터페이스

    예제에서는 Mapper interface를 작성해보겠습니다. src/main/java/org/your_packages.../mapper 패키지를 만들고, TimeMapper라는 인터페이스를 추가합니다.

    TimeMapper는 다음과 같이 작성합니다.

    import org.apache.ibatis.annotations.Select;
    
    public interface TimeMapper {
    
        @Select("SELECT sysdate() FROM dual;")
        public String getTime();
    }

    Mapper를 작성했다면 MyBatis가 동작할 때 Mapper를 인식할 수 있도록 applicationContext.xml에 다음 코드를 추가해줍니다.

    <mybatis-spring:scan base-package="com.hellomygreenworld.ex00.mapper" />

    테스트 코드를 작성해보겠습니다.

    import lombok.Setter;
    import lombok.extern.log4j.Log4j;
    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.springframework.test.context.web.WebAppConfiguration;
    import com.hellomygreenworld.ex00.mapper.TimeMapper;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @WebAppConfiguration
    @ContextConfiguration("file:src/main/webapp/WEB-INF/spring-config/applicationContext.xml")
    @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(timeMapper.getTime2());
    }

    TimeMapper 인터페이스만 만들어주었는데, 내부적으로 클래스가 만들어지고, getTime() 메서드가 정상적으로 실행되어 시간을 출력해주고 있습니다.

    2. XML mapper와 같이 쓰기

    SQL이 복잡하거나 길어지는 경우에는 어노테이션보다 XML을 이용하는 방식이 더 좋습니다. mybatis-spring은 Mapper 인터페이스와 XML을 동시에 이용할 수 있습니다.

    TimeMapper 인터페이스에는 아래 코드만 추가해줍니다. @Select 어노테이션도 없고, SQL도 없습니다. SQL은 따로 XML을 만들어서 처리해주겠습니다.

    public String getTime2();
    

    TimeMapper.xml을 아래와 같이 작성합니다. XML 파일의 위치는 Mapper 인터페이스가 있는 곳에 작성하거나 src/main/resources에 Mapper 인터페이스와 같은 디렉토리를 생성하여 저장하면 좋습니다.

    <?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="com.hellomygreenworld.ex00.mapper.TimeMapper">
        <select id="getTime2" resultType="String">
            SELECT sysdate() from dual
        </select>
    </mapper>

    MyBatis는 Mapper 인터페이스와 XML을 인터페이스의 이름과 namespace 속성값으로 판단하는데, TimeMapper 인터페이스가 존재하고, 같은 namespace의 XML이 존재하면 이를 병합해서 처리합니다.

    테스트 코드에 다음 코드를 추가하여 테스트도 해봅니다.

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

     

    5. log4jdbc-log4j2 설정

    MyBatis는 내부적으로 JDBC의 Preparedstatement를 이용해서 SQL을 처리하는데, SQL에 전달되는 파라미터가 JDBC에서와 같이 '?'로 치환되어 처리되어 값 확인이 어렵고, SQL의 내용 확인도 어렵습니다. 따라서 SQL 로그를 제대로 확인하기 위해서 log4jdbc-log4j2 라이브러리를 사용할 수 있습니다.

    1. 라이브러리 추가

    우선, pom.xmllog4jdbc-log4j2 라이브러리를 추가해줍니다.

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

    2. 로그 설정 파일 추가

    src/main/resources아래에 log4jdbc.log4j2.properties 파일을 추가합니다.

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

    3. JDBC 연결 정보 수정

    log4jdbc를 이용하기 위해 applicationContext.xml에 작성되어있는 JDBC Driver와 URL 정보를 수정합니다.

    <bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
        <property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy" />
        <property name="jdbcUrl" value="jdbc:log4jdbc:mysql://127.0.0.1:3306/SpringWeb?serverTimezone=Asia/Seoul" />
        <property name="username" value="root" />
        <property name="password" value="P@ssw0rd" />
    </bean>

    수정 후, 기존의 테스트 코드를 실행하면 sql 로그가 보기 좋게 찍힙니다!

     

    반응형

    'Java > Spring' 카테고리의 다른 글

    [Spring MVC] Controller  (0) 2023.01.03
    [Spring MVC] 기본 구조  (0) 2023.01.02
    [Spring] JDBC(MySQL) 연결  (0) 2023.01.02
    [Spring] 의존성 주입(DI)  (1) 2022.12.30
    [Spring MVC] IntelliJ에서 Project 생성하기  (0) 2022.12.29

    댓글