[MyBatis] 스프링부트 마이바티스 연동

dondonee·2024년 3월 29일
0
post-thumbnail

의존성 추가

스프링 프레임워크에서 마이바티스를 사용하기 위해 필요한 것은 크게 4가지이다 :

  1. JDBC
  2. 데이터베이스
  3. DataSource
  4. MyBatis

이번에는 스프링 부트에서 H2 데이터베이스에 마이바티스를 사용해보기로 했다. 이 경우 필요한 의존성은 다음과 같다 (Gradle) :

implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.2'
  1. JDBC : spring-boot-starter-jdbc 추가
  2. 데이터베이스 : com.h2database:h2 추가
  3. DataSource : 스트링부트에 내장된 HikariCP 사용
  4. MyBatis : mybatis-spring-boot-starter:3.0.2 추가

호환 버전

마이바티스는 스프링부트에서 버전관리를 하지 않기 때문에 의존성을 추가할 때 뒤에 버전 정보를 붙여주어야 한다. 나는 스프링부트 3.1 버전이기 때문에 마이바티스는 3.0 버전을 사용했다. (호환 버전은 🔗 마이바티스 매뉴얼 참고)



설정 - DataSource

마이바티스는 커넥션 풀을 사용한다. 따라서 DataSource를 설정해주어야 한다.

  • DataSource : 애플리케이션이 커넥션 풀링을 통해 데이터베이스와 연결을 확보할 때 사용하는 인터페이스 (이전 포스팅 참고 : 🔗 DataSource 개념 및 설정)

스프링 레거시

레거시 프로젝트의 경우 DB 연결 정보와 커넥션 풀 설정을 담당하는 HikariConfig 빈과, HikariConfig를 참고하여 커넥션 풀을 만드는 HikariDataSource 빈을 직접 등록해주어야 했다. (이전 포스팅 참고 : 🔗 MyBatis 연결)


스프링 부트

스프링 부트에서는 application.yml만 작성하면 자동으로 필요한 스프링 빈을 등록해준다.

spring:
  datasource:
    url: jdbc:h2:tcp://localhost/~/testdb;MODE=MySQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE;IGNORECASE=TRUE
    username: sa
    password: sa

참고로 스프링부트는 JDBC 드라이버를 JDBC URL을 통해 자동 인식하며, 가장 우선적으로 HikariCP를 탐색하기 때문에 hikari 프로퍼티는 생략해도 된다. (이전 포스팅 참고 : 🔗 H2 데이터베이스)



설정 - MyBatis

스프링 레거시

	<!--   기존 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"/>

레거시 프로젝트의 경우 root-context.xml에 네임스페이스, SqlSessionFactoryBean, 스캔 경로를 설정해주어야 한다.


스트링부트 Auto-configuration

스트링부트의 MyBatis-Spring-Boot-Starter는 다음과 같은 작업을 자동화해준다 :

  • 존재하는 DataSource 조회
  • SqlSessionFactoryBeanDataSource를 전달하고 SqlSessionFactory 인스턴스를 생성 및 등록
  • SqlSessionFactory에서 가져온 SqlSessionTemplate 인스턴스를 생성 및 등록
  • 매퍼를 자동 스캔하고, SqlSessionTemplate에 연결하고, 매퍼를 빈에 주입할 수 있도록 스프링 컨텍스트에 등록한다.

보충설명

SqlSessionTemplate은 트랜잭션 관리, 세션의 생명주기 관리를 담당하며 마이바티스 예외를 스프링의 DataAccessException로 변환해주는 스프링 마이바티스 연동 모듈의 핵심이다.

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
  <constructor-arg index="0" ref="sqlSessionFactory" />
</bean>

SqlSessionTemplate의 구현인 sqlSession 인스턴스는 sqlSessionFactory에 의해 생성된다.


SqlSessionFactoryBean은 스프링 연동모듈에서 SqlSessionFactoryBuilder 대신 사용되는 것으로, DataSource를 통해 SqlSessionFactory를 생성한다.

@Configuration
public class MyBatisConfig {
  @Bean
  public SqlSessionFactory sqlSessionFactory() {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(dataSource());
    return factoryBean.getObject();
  }
}
  • 스프링이 빈으로 등록하는 것은 SqlSessionFactoryBean에서 getObject()로 얻은 SqlSessionFactory이다.
  • SqlSessionFactoryBeanDataSource를 참조한다.

스트링부트 설정

mybatis:
  type-aliases-package: com.example.board.domain
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true

스트링부트의 자동설정 덕분에 개발자는 application.yml에 간단한 설정만 하면 된다.

  • type-aliases-package : 도메인 객체가 있는 패키지를 지정해주면 매퍼에서 타입 정보를 명시할 때 패키지 이름을 생략할 수 있다(prefix).
  • mapper-locations : 매퍼 XML 파일의 위치
  • map-underscore-to-camel-case : 관례적으로 RDB에서는 스네이크 케이스를 사용하고 자바는 카멜 케이스를 사용하기 때문에 이러한 불일치를 보완해주는 설정이다. (예를 들면 컬럼과 필드를 매핑할 때 user_name과 userName을 동일한 것으로 간주한다.)

더 많은 설정은 다음 사이트에서 참고한다. (🔗 MyBatis 매뉴얼)


매퍼 작성 및 등록

마이바티스 연동 테스트를 위해 매퍼 간단하게 매퍼 XML 없이 매퍼 인터페이스를 작성해보았다.

@Mapper
public interface PostMapper {

    @Insert("INSERT INTO post (post_title, post_content) VALUES (#{postTitle}, #{postContent})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    void save(Post post);

    @Select("SELECT * FROM post WHERE id = #{id}")
    Post findById(Long id);
}

save(Post post)post를 저장한 뒤 데이터베이스에 의해 auto-increment된 id 값을 가져와야 하기 때문에 @Option을 사용했다.

  • 참고) 매퍼 인터페이스에서 INSERT, UPDATE, DELETE 쿼리를 수행하는 메서드의 리턴 타입은 void 또는 int이어야 한다. int로 반환하는 경우 쿼리로 인해 영향받은 로우 수를 반환한다. 단, 일반적인 인터페이스의 경우와 달리 매퍼 인터페이스를 구현한 클래스가 메서드를 오버라이딩하는 경우 리턴 타입을 변경할 수 있다. (🔗 관련 포스팅 : Mapper method has an unsupported return type 오류)

@Configuration
@RequiredArgsConstructor
public class MyBatisConfig {

    private final PostMapper postMapper;

    @Bean
    public PostRepository postRepository() {
        return new MyBatisPostRepository(postMapper);
    }
}

추후에 리포지토리를 JPA로 변경할 예정이기 때문에 매퍼를 바로 사용하지 않고 리포지토리 인터페이스를 만든 뒤 그 구현체에서 매퍼를 주입하여 사용했다.


@Repository
@RequiredArgsConstructor
public class MyBatisPostRepository implements PostRepository {

    private final PostMapper postMapper;

    @Override
    public Post save(Post post) {
        postMapper.save(post);
        return post;
    }

    @Override
    public Post findById(Long id) {
        return postMapper.findById(id);
    }
}

마이바티스를 사용하는 리포지토리 인터페이스의 구현 클래스이다.

save()의 리턴 타입을 void에서 Post로 오버라이딩했다. 따라서 save()는 로직을 처리한 뒤 파라미터로 전달되었던 post 객체를 그대로 반환하는데, postid 필드는 쿼리 실행 후 데이터베이스에서 auto-increment된 id 값이 새롭게 바인딩된다. 매퍼에서 save()@Options(useGeneratedKeys = true, keyProperty = "id")를 사용했기 때문이다.


마이바티스 연동 테스트

@Slf4j
@Transactional
@SpringBootTest
public class MyBatisPostRepositoryTest {

    @Autowired
    PostRepository postRepository;

    @Test
    void save() {
        //given
        Post post = new Post("title 1", "Lorem Ipsum");

        //when
        Post savePost = postRepository.save(post);\

        //then
        Post findPost = postRepository.findById(savePost.getId());
        assertThat(findPost).isEqualTo(savePost);
    }
}



🔗 Reference

0개의 댓글