이번 포스팅에선 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
)
'Spring' 카테고리의 다른 글
[Spring + Kotlin] Jackson 역직렬화할 때 프로퍼티명 2번째 대문자가 소문자로 변하는 문제 해결 방법 (0) | 2024.11.07 |
---|---|
[Spring] Illegal mix of collations (utf8mb4_general_ci,IMPLICIT) and (utf8mb4_0900_ai_ci,IMPLICIT) for operation '=', 'UNION' (1) | 2024.03.30 |
[Spring] 재시도할 때 사용하는 @Retryable, @Recover 사용법 (1) | 2024.02.26 |
[Spring] Kotest 병렬 테스트 설정 방법 (23) | 2023.12.31 |
[Spring] Gradle Test events were not received 해결 방법 (21) | 2023.12.28 |
댓글