[Spring] Spring Legacy Project 만들기 (2) - MyBatis 연결

dondonee·2024년 1월 6일
2
post-thumbnail
post-custom-banner

박매일 님의 스프 1탄을 들으면서 정리하고 있다. 이전에는 스프링 레거시 프로젝트를 만들고 Spring MVC까지 적용해 보았다.

이번에는 프로젝트에 DB 및 MyBatis 연결을 해 보았다.



1) DB 생성

나는 Docker MySQL을 사용할 것이기 때문에 프로젝트 디렉토리에 docker-compose.yml 파일을 만들어주었다.


도커 컨테이너 생성

version: '3'

services:
  db:
    image: mysql:8.0
    container_name: untitled
    restart: always
    ports:
      - 3306:3306
    environment:
      MYSQL_ROOT_PASSWORD: "root1234"
      MYSQL_DATABASE: "untitled"
      MYSQL_USER: "ex"
      MYSQL_PASSWORD: "ex"
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
    volumes:
      - ./database/datadir/:/var/lib/mysql
      - ./database/init/:/docker_entrypoint-initdb.d/
    

설정 파일을 생성하고 터미널에서 docker-compose up을 통해 빌드하면 컨테이너가 생성되고 실행된다.


인텔리제이 DB 연동

인텔리제이에서 DB 연동을 해 준다. 연동을 해두면 직접 쿼리를 실행할 수 있고 스키마나 데이터도 인텔리제이에서 편하게 확인할 수 있다.

  • User, Password는 루트가 아닌 유저의 정보를 입력해야 한다.
  • 도커 설정 파일에서 생성한 DatabaseTest Connection을 해야 완전히 생성된다. 테스트 커넥션이 성공적으로 이루어지면 Database를 선택해주고 OK를 눌러 연결을 마친다.

스키마 생성

entity 패키지에 board.sql 파일을 생성하고 스키마를 구성하고 임시 데이터를 삽입해준다. SQL 파일을 Run하면 설정 창이 뜨는데 타겟 데이터베이스를 선택해주고 실행하면 쿼리가 실행된다.


2) 애플리케이션 DB 연동

자바 애플리케이션에 DB를 연동하기 위해서는 JDBC API가 필요하다. 또한 사용하는 DB에 맞는 JDBC 드라이버도 추가해야 한다. 마이바티스는 커넥션 풀을 사용하기 때문에 데이터소스로 HikariCP를 사용하겠다.


의존성 추가 - pom.xml

		<!--   DB 관련   -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>
        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
            <version>3.4.1</version>
        </dependency>

설정 - root-context.xml

톰캣이 실행되면 톰캣은 web.xml을 읽고 스프링 컨테이너를 생성한다. 이 때 ContextLoaderListener도 생성되는데 이 객체는 root-context.xml를 읽고 객체를 생성한다.

    <!-- Root Context: defines shared resources visible to all other web components -->
    <!-- API(HikariCP) -->
    <!-- bean : 객체를 생성하는 태그 -->
    <bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/untitled?serverTimezone=UTC"/>
        <property name="username" value="ex"/>
        <property name="password" value="ex"/>
    </bean>

    <!-- HikariDataSource(Connection POOL을 만드는 역할을 한다) -->
    <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
        <constructor-arg ref="hikariConfig"/>
    </bean>
  • hikariConfig: 포트 번호, 데이터베이스 이름, 유저 이름과 패스워드를 잘 기재한다.
  • dataSource: hikariConfig 설정 정보를 참조하여 커넥션 풀을 생성한다.
    • <constructor-arg ref="hikariConfig"/>
    • destroy-method="close": 빈이 다 사용되고 destroy가 호출될 때 close 메서드로 커넥션을 종료하게끔 설정한다. 개발자가 커넥션을 종료하는 것이 아니라 자동으로 끊어지게 된다.

마이바티스는 dataSource에 의해 생성된 커넥션 풀에서 커넥션을 가져다 사용한다.


3) Mapper 생성

Mapper 인터페이스

DB에서 Board에 맞는 데이터를 꺼내오기 위해 BoardMapper 인터페이스를 만들어준다. 마이바티스가 매퍼를 인식하기 위해 @Mapper 애노테이션을 달아주어야 한다.

  • 아직은 마이바티스를 추가하지 않았기 때문에 @Mapper를 인식하지 못한다.

Mapper XML

<?xml version="1.0" encoding="UTF-8"?>
<!-- xml 파일명은 인터페이스와 같아야 함 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="org.example.mapper.BoardMapper"> <!-- 스프링이 이것을 참고하여 경로를 찾아감 -->

    <!--  BoardMapper 인터페이스의 getList 메서드와 매핑   -->
    <select id="getLists" resultType="org.example.entity.Board">
        select * from myboard order by idx desc
    </select>


</mapper>

매퍼 인터페이스만으로도 마이바티스를 사용할 수 있다. getList 메서드에 @Select("select * from myboard order by idx desc")를 달아주면 된다. 그러나 매퍼가 복잡해지면 XML 파일로 분리하는 것이 좋다.

  • <mapper namespace> : XML 파일이 매퍼를 찾을 수 있도록 경로를 적어준다.
  • <select> :
    • id: 인터페이스의 메서드명과 일치해야 한다.
    • resultType: 결과값을 바인딩 할 객체의 경로를 적어준다.

4) MyBatis 연동

스프링에서 마이바티스를 사용하기 위해 필요한 API는 총 다섯 개이다:

  1. JDBC Driver API
  2. Spring-jdbc API
  3. HikariCP API
  4. MyBatis API
  5. Mabatis-spring API

1~3은 앞에서 추가했으므로 MyBatis, Mabatis-spring만 추가하면 된다.


의존성 추가 - 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>

설정 - root-context.xml

	<!--   기존 namespace 아래 정보 추가 -->
	<beans xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring">
    <beans xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">
      
    <!-- BoardMapper interface의 구현클래스 생성
    SqlSessionFactoryBean(SQL을 실행하는 API) -->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!-- Mapper interface들을 메모리에 올리는 방법(scan) -->
    <mybatis-spring:scan base-package="org.example.mapper"/>

<mybatis-spring:scan>

마이바티스가 매퍼를 인식하기 위해서 매퍼를 빈으로 등록해야 한다. 하나하나 직접 등록하는 것은 번거로우므로 스캔을 통해 베이스 패키지(base-package="kr.board.mapper") 내의 매퍼들을 일괄 등록하도록 한다.

SqlSessionFactoryBean

BoardMapper는 인터페이스이기 때문에 사용하려면 구현체가 필요하다. 마이바티스는 내부적으로 SqlSessionFactoryBean라는 구현체를 만들어준다.

SqlSessionFactoryBean은 인터페이스 메서드의 내부를 모두 구현해준다. DB에서 쿼리를 실행하고 결과를 지정 타입에 맞는 타입으로 반환하는 것까지 마이바티스에서 해결해주는 것이다.

이 구현체는 개발자에게 보이지는 않지만 자동 생성되지 않고 꼭 필요한 객체이기 때문에 <bean>으로 등록해주어야 한다.

  • SqlSessionFactoryBean은 커넥션 풀을 통해 쿼리를 실행하므로 HikariDataSource를 참조해야 한다. 객체 명칭(id)인 dataSource로 가져와준다.

5) 테스트

컨트롤러

@Controller
public class BoardController {

    @Autowired
    private BoardMapper boardMapper;

    @RequestMapping("/boardList.do")
    public String boardList(Model model) {
        List<Board> list = boardMapper.getLists();
        model.addAttribute("list", list);
        return "boardList"; // /WEB-INF/views/boardList.jsp -> forward
    }
}
  • boardMapper 프로퍼티를 추가해주고 스프링이 자동 주입을 하도록 @Autowired를 걸어준다.
  • boardList 에서는 매퍼 인터페이스에 정의된 대로 boardMapper.getLists()를 호출하여 DB에서 모든 리스트를 가져온다.
  • 결과값을 모델에 list라는 이름으로 담아 bordList라는 뷰에 전달한다.

실행

브라우저에서 /boardList.do를 실행해 보면 위와 같이 데이터가 잘 가져와진 것을 확인할 수 있다 !



+) 로깅 설정

테스트 도중에 오류가 발생했는데 로깅을 설정하지 않아 디버깅이 어려웠다. 이전 포스트에서 로깅 의존성을 추가만 하고 설정을 하지 않았기 때문에 보이지 않았던 것이다.

src/main/resourceslog4j.xml을 만들고 다음과 같이 설정해준다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd">
<log4j:configuration>

    <!-- Appenders -->
    <appender name="console" class="org.apache.log4j.ConsoleAppender">
        <param name="Target" value="System.out" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%-5p: %c - %m%n" />
        </layout>
    </appender>

    <!-- Application Loggers -->
    <logger name="org.example.controller">
        <level value="info" />
    </logger>

    <!-- 3rdparty Loggers -->
    <logger name="org.springframework.core">
        <level value="info" />
    </logger>

    <logger name="org.springframework.beans">
        <level value="info" />
    </logger>

    <logger name="org.springframework.context">
        <level value="info" />
    </logger>

    <logger name="org.springframework.web">
        <level value="info" />
    </logger>

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

</log4j:configuration>

디버깅이 필요한 부분에서 로깅 레벨을 debug로 높이면 아래와 같이 동작을 상세하게 확인할 수 있다.


Caused by: org.xml.sax.SAXParseException; lineNumber: 34; columnNumber: 61; cvc-complex-type.2.4.c: 일치하는 와일드 카드 문자가 엄격하게 적용되지만 'mybatis-spring:scan' 요소에 대한 선언을 찾을 수 없습니다.
	at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:204)
	at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:135)
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:396)
	...

내 오류의 원인은 root-context.xml에서 mybatis-spring를 위한 xsi:schemaLocation이 지정되어있지 않아 XML 파일을 읽어올 수 없던 것이었다. 상단의 root-context.xml 예시에서는 올바르게 수정해 두었다.




🔗 Reference

post-custom-banner

0개의 댓글