본문 바로가기
Spring

[Spring] Spring Boot + Kotlin + MyBatis 프로젝트 설정 방법

by 노력남자 2024. 3. 26.
반응형

이번 포스팅에선 Spring Boot + Kotlin 프로젝트에서 MyBatis를 설정하는 방법에 대해 알아보겠다.

 

어렵지 않지만 생각보다 깔끔하게 정리된 곳이 없어서 꽤 애를 먹었다.

 

MyBatis 사용법을 다루진 않을 거다. 자세한 건 공식 홈페이지를 참고바란다.

 

1. mybatis-spring-boot-starter 의존성 추가

 

spring boot 버전에 따른 mybatis-spring-boot-starter는 MyBatis 공식 사이트에서 확인 가능하다.

 

 

나는 2..7.x를 사용하고 있어서 2.3.x 버전을 사용했다.

 

// Gradle
implementation("org.mybatis.spring.boot:mybatis-spring-boot-starter:2.3.1")

// Maven
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>2.3.1</version>
</dependency>

 

혹시나 IntelliJ에서 프로젝트를 처음 만든다면 아래와 같이 검색해서 처음부터 추가해놓자.

 

 

MybatisAutoConfiguration.java에서 간편 설정을 해주고 있으니 심심하면 들어가서 어떤 걸 해주는지 확인해보자.

tmi: mybatis-spring-boot-starter는 spring 진영에서 개발한 게 아니다.

 

2. DataSource 설정

 

- application.yml에 설정한 dataSource 값으로 설정

 

mybatis-spring-boot-starter는 application.yml에 설정해놓은 DataSource를 주입받아 DB 연결 정보를 자동으로 셋팅해준다.

 

application.yml에 설정하는 방법은 아래 포스팅에 가서 확인하자.

 

MySQL - https://effortguy.tistory.com/278

H2 - https://effortguy.tistory.com/270

 

- 별도 dataSource 설정

 

만약, application.yml에 설정한 DB 연결 정보를 사용하지 않고 별도로 사용하고 싶다면 아래와 같이 sqlSessionFactory를 설정해주자.

 

import org.apache.ibatis.session.SqlSessionFactory
import org.mybatis.spring.SqlSessionFactoryBean
import org.springframework.boot.jdbc.DataSourceBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.io.support.PathMatchingResourcePatternResolver

@Configuration
class MyBatisConfig {

    @Bean
    fun sqlSessionFactory(): SqlSessionFactory {
        return SqlSessionFactoryBean().apply {
            val dataSource = DataSourceBuilder
                .create()
                .url("hostname")
                .username("계정명")
                .password("비밀번호")
                .driverClassName("com.mysql.cj.jdbc.Driver")
                .build()
            setDataSource(dataSource)
        }.`object`!!
    }
}

 

이 방법을 쓰는 경우에 유의할 사항은 MybatisAutoConfiguration.java에서 sqlSessionFactory가 있는 경우 별도 생성하지 않기 때문에 앞으로 application.yml에 설정하는 MyBatis 옵션들을 위 파일에 설정해줘야 한다.

 

아니면, 별도 properties를 만들어서 주입받아서 써야할듯..

 

 

3. 맵핑 옵션 설정 (옵션)

 

db 컬럼이 snake_case이고, 리턴 클래스의 property가 camelCase인 경우 아래 옵션을 추가해주자.

 

// application.yml
mybatis:
  configuration:
    map-underscore-to-camel-case: true

// 별도 sqlSessionFactory 구성 시 - 위 코드에 추가
val configuration = org.apache.ibatis.session.Configuration()
            configuration.isMapUnderscoreToCamelCase = true
            setConfiguration(configuration)

 

4. Mapper 위치 설정

 

MyBatis는 Mapper를 사용해서 쿼리를 실행한다.

 

쿼리가 들어있는 xml은 resources아래에 두는 게 일반적이다.

 

 

아래와 같이 설정하면 해당 경로에 있는 xml 파일에서 쿼리를 읽어온다.

 

// application.yml
mybatis:
  mapper-locations: classpath*:mybatis/*.xml
  
// 별도 sqlSessionFactory 구성 시 - 위 코드에 추가
setMapperLocations(*PathMatchingResourcePatternResolver().getResources("classpath*:mybatis/*.xml"))

 

5. Mapper 생성

 

Mapper는 인터페이스, XML 2개가 한 쌍이다.

 

Mapper 인터페이스만 사용할 수도 있다. 그럴 일은 별로 없겠지만.

 

- Mapper 인터페이스

 

@Mapper를 붙인 인터페이스는 mapper sacn을 통해 빈으로 등록된다. 알아서 해준다.

 

인터페이스 안에 메소드를 정의하는 방법은 아래와 같다.

 

메소드명: 내가 원하는 쿼리명 (findByIdAndName)

파라미터: 쿼리에 사용할 값 (id, name)

리턴 클래스: 쿼리 결과를 담을 클래스 (Customer)

 

예)

 

package com.example.spring_kotlin_mybatis

import org.apache.ibatis.annotations.Mapper

@Mapper
interface CustomerMapper {
    fun findByIdAndName(id: Long, name: String): Customer
}

 

- Mapper 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="Mapper 인터페이스의 패키지 + 클래스명">

    <select id="Mapper 인터페이스에 정의한 메소드명" resultType="리턴 클래스의 패키지 + 클래스 이름">
    	... 쿼리 ...
    </select>
</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.example.spring_kotlin_mybatis.CustomerMapper">

    <select id="findByIdAndName" resultType="com.example.spring_kotlin_mybatis.Customer">
        SELECT *
        FROM customer
        <include refid="findByIdAndNameWhere" />
    </select>

    <sql id="findByIdAndNameWhere">
        <where>
            <if test="id != null">
                AND id = #{id}
            </if>
            <if test="reservationNo != null">
                AND name = #{name}
            </if>
        </where>
    </sql>
</mapper>

 

6. 로거 추가

 

위처럼 설정하고 호출하면 로그가 안 보인다.

 

대표적인 방법으로 log4jdbc를 사용한다.

 

- 의존성 추가

 

// Gradle
implementation("org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16")

 

- log4jdbc.log4j2.properties 추가

 

resources에 추가하면 된다.

 

log4jdbc.auto.load.popular.drivers=false
log4jdbc.drivers=com.mysql.cj.jdbc.Driver
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
log4jdbc.dump.sql.maxlinelength=0

 

- datasource url 변경

 

jdbc:mysql -> jdbc:log4jdbc:mysql 

 

jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=Asia/Seoul

-> jdbc:log4jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=Asia/Seoul

 

테스해보니 url에 log4jdbc를 붙였다고 뭔가 다른 곳에 영향이 가진 않았다.

 

- application.yml 설정 추가

 

아래 옵션들은 전부 기본값이 INFO

 

아래 옵션 중 1개만 INFO로 변경, 나머지는 OFF

 

전부 INFO로 하면 난리남

 

logging:
  level:
    jdbc:
      audit: OFF # 어떤 절차로 쿼리가 실행됐는지, ? 파라미터에 직접 매핑 x
      resultset: OFF # 절차, 쿼리, 이쁘지 않게 결과 출력
      resultsettable: OFF  # 이상하게 작동 안 됨
      sqlonly: OFF     # SQL
      sqltiming: INFO    # SQL + 소요시간
      connection : OFF  # 커넥션 정보

 

중요한 사항은 log4jdbc는 쿼리의 파라미터가 ?로 찍히지 않고 다 맵핑까지 해주는 로거라 운영에는 사용하지 않는 게 좋다고 한다. 성능이 엄청 떨어지기 때문이다.

 

?에 맵핑을 안 해주는 로거가 따로 있는 거 같은데 정확히 방법을 잘 모르겠다. 알게되면 추가하겠다.

 

주의사항

 

resultType에 사용한 객체는 기본 값을 가지고 있어야 한다.

 

아래와 같이 생긴 Customer를 select 해오는데

 

class Customer(
    val id: Long,
    val customerName: String
)

 

생성자 필드 순서랑 다르게 customer_name, id 순으로 select를 하게 되면

 

<?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.example.spring_kotlin_mybatis.CustomerMapper">
    
    <select id="findAll" resultType="com.example.spring_kotlin_mybatis.Customer">
        SELECT customer_name, id
        FROM customer
    </select>
</mapper>

 

customerName은 id에

 

id는 customerName에 

 

연결된다.

 

이런 문제가 발생한 이유는?

 

코틀린은 no-arg 생성자가 없다.

 

no-arg 생성자가 필요하다. 설정하러 가보자.

 

1. 쿼리 결과를 담을 클래스에 붙일 MyBatisResult 어노테이션 생성

 

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class MyBatisResult

 

2. build.gradle에 plugin.jpa 추가

 

plugins {
    ...
    kotlin("plugin.jpa") version "1.9.22"
    ...
}

 

3. build.gradle에 1에서 선언한 어노테이션이 붙어있는 클래스면 no-arg 생성자가 자동으로 생기게 설정

 

2를 추가해줘야 noArg를 사용할 수 있다.

 

noArg{
    annotation("com.bunjang.order.global.config.mybatis.MyBatisResult")
}

 

4. resultType에 사용할 클래스에 MyBatisResult 어노테이션 추가

 

@MyBatisResult
class Customer(
    val id: Long,
    val customerName: String
)
반응형

댓글