스프링과 마이바티스(MyBatis)

반응형

MyBatis는 Java 기반의 SQL 매퍼 프레임워크로, SQL 쿼리를 자바 코드와 매핑하여 데이터베이스 작업을 더 쉽게 처리할 수 있도록 도와줍니다.

간단히 말하면, 데이터베이스와의 상호작용을 간소화하고 더 유연하게 관리할 수 있도록 해주는 도구입니다.

 

관련된 좀 더 자세한 설명은 아래의 글을 참고해 주세요.

https://wellsbabo.tistory.com/31

 

JPA, JDBC

JDBC (Java Database Connectivity)자바에서 데이터베이스와 직접 상호작용하기 위한 저수준 API입니다. 여러 종류의 데이터베이스가 존재하는 상황에서, 데이터베이스가 교체되더라도 코드를 바꾸지

wellsbabo.tistory.com

MyBatis는 SQL 중심으로 개발자가 직접 SQL을 작성하고, Hibernate(JPA)는 객체 중심으로 SQL을 자동으로 생성해 줍니다.

 

이번 글에서는 스프링에서 마이바티스트를 사용해서 DB와 연결하는 작업을 알아보겠습니다.

 

연결 기본

연결 자체는 매우 간단합니다.

`application.properties` 또는 `application.yaml`에 연결할 DB 정보를 설정하고, `@Mapper` 어노테이션을 사용한 인터페이스에서 직접 SQL을 작성하면 DB와 연결이 됩니다.

 

간단한 예시를 작성해 보았습니다.

 

우선 `application.properties`입니다.

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver  #JDBC 드라이버 클래스 이름 지정
spring.datasource.url=jdbc:mysql://localhost:3306/mydb  #연결한 데이터베이스의 URL 설정
spring.datasource.username=root  #데이터베이스 접속 계정
spring.datasource.password=yourpassword  #데이터베이스 접속 비밀번호
mybatis.configuration.map-underscore-to-camel-case=true #DB 컬럼 이름과 자바 객체의 필드 이름 간 매핑 규칙 설정

 

`mybatis.configuration.map-underscore-to-camel-case=true` 같은 경우는 조금 더 자세히 설명하자면 데이터베이스의 컬럼 이름이 스네이크 케이스(`snake_case`) 일 경우, 자바 객체의 필드 이름을 카멜 케이스로(`camelCase`)로 자동 변환돼서 매핑됩니다. `user_id` > `userId`

 

이렇게 DB 관련 정보를 설정 파일에 입력한 후에 `Mapper` 인터페이스를 작성합니다.

이때 `@Mapper` 어노테이션을 붙여주어야 합니다.

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface UserMapper {

    @Select("SELECT * FROM users WHERE id = #{id}")
    User findUserById(Long id);

}

 

그리고 서비스 레이어에서 이렇게 가져와서 사용하면 됩니다.

`UserService` 메서드를 호출하면 MyBatis가 인터페이스와 SQL을 기반으로 DB 작업을 수행합니다.

import org.springframework.stereotype.Service;

@Service
public class UserService {

    private final UserMapper userMapper;

    public UserService(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    public User getUserById(Long id) {
        return userMapper.findUserById(id);
    }
}

 

스프링 부트에서 `mybatis-spring-boot-starter`를 의존성에 추가하면, 스프링이 자동으로 MyBatis 설정을 처리합니다.

`@Mapper`가 붙은 인터페이스는 스프링에서 빈으로 등록되고, SQL은 어노테이션(`@Select`, `@Insert` 등)으로 작성하거나, XML 매퍼 파일을 통해 분리해서 사용할 수 있습니다.


위에 설명한 내용과 같이 간단하게 MyBatis를 통한 DB 작업을 수행할 수 있습니다.

하지만 조금 더 세부적인 설정을 위해서는 `DBConfig` 파일을 만들어서 사용하게 됩니다.

 

DBConfig 파일

`DBConfig` 파일은 스프링 부트와 MyBatis를 사용하여 여러 데이터소스를 구성하고 관리하기 위한 설정 클래스입니다.

 

기본적으로는 아래의 역할들을 하기 위해 사용되는데요.

  1. 다중 데이터 소스 지원: 프로젝트에서 여러 데이터베이스를 사용할 경우, 각 데이터소스 별로 설정을 관리
  2. MyBatis 연동: 각 데이터소스에 대한 `MyBatis`의 설정(`SqlSessionFactory`, `SqlSessionTemplate` 등)을 제공
  3. 트랜잭션 관리: 데이터 소스별 트랜잭션 관리를 설정
  4. Mapper 연결: 특정 데이터소스와 관련된 MyBatis 매퍼를 스캔

`DBConfig` 파일이 없으면 이런 역할을 해주는 객체들이 자동으로 생성되고 MyBatis를 통해 바로 사용할 수 있습니다.

하지만 자동 생성 및 설정은 단일 데이터소스 환경에서만 가능합니다.

 

다중 데이터소스 환경에서는 데이터 소스별로 독립적인 설정(`SqlSessionFactory`, `SqlSessionTemplate`, `트랜잭션 매니저` 등)이 필요합니다.

그리고 고급 설정(예: 동적 SQL 처리, 특정 MyBatis 설정 등)을 적용해야 할 경우에도 `DBConfig` 파일이 필요합니다.

 

결론적으로, `DBConfig`는 단일 데이터소스에서 기본 설정이 충분할 경우 불필요하지만, 다중 데이터소스나 커스텀 설정이 필요한 경우 꼭 있어야 합니다.

 

이제 세부적인 `DBConfig` 파일 작성에 대해서 알아보겠습니다.

package com.example.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = {"com.example.mapper.dbname"}, sqlSessionFactoryRef = "dbNameSqlSessionFactory")
public class DBNameConfig {

    // 1. DataSource Bean 생성
    @Bean(name = "dbNameDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.dbname")
    public DataSource dbNameDataSource() {
        return DataSourceBuilder.create().build();
    }

    // 2. SqlSessionFactory Bean 생성
    @Bean(name = "dbNameSqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dbNameDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true); // 스네이크 케이스 → 카멜 케이스 자동 매핑
        sqlSessionFactoryBean.getObject().getConfiguration().setCallSettersOnNulls(true); // NULL 처리
        return sqlSessionFactoryBean.getObject();
    }

    // 3. SqlSessionTemplate Bean 생성
    @Bean(name = "dbNameSqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("dbNameSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    // 4. TransactionManager Bean 생성
    @Bean(name = "dbNameTransactionManager")
    public DataSourceTransactionManager transactionManager(@Qualifier("dbNameDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

 

기본적인 `DBConfig` 파일의 설정은 위에 예시와 같은데요.

이제 각각의 부분에 대해서 좀 더 세부적으로 살펴보겠습니다.

 

클래스 선언 및 어노테이션

@Configuration
@MapperScan(basePackages = {"com.example.mapper.dbname"}, sqlSessionFactoryRef = "dbNameSqlSessionFactory")
public class DBNameConfig {
}

 

`@MapperScan`은 MyBatis 매퍼 인터페이스를 스캔할 패키지를 지정하고(매퍼 인터페이스가 있는 패키지 경로를 지정해 주면 됩니다), `sqlSessionFactoryRef`에는 해당 데이터 소스에 사용할 `SqlSessionFactory`를 설정합니다.

 

DataSource 빈 생성

@Bean(name = "dbNameDataSource")
@ConfigurationProperties(prefix = "spring.datasource.dbname")
public DataSource dbNameDataSource() {
    return DataSourceBuilder.create().build();
}

 

`application.properties` 또는 `application.yaml` 파일에 정의된 DB 관련 설정 값을 읽어 데이터소스를 생성합니다.

  • `name`은 스프링 컨테이너에 등록할 빈 이름입니다.
  • `@ConfigurationProperties`는 지정된 접두사로 시작하는 설정 값을 매핑합니다.

위의 예시처럼 `@ConfigurationProperties(prefix = "spring.datasource.dbname")`로 지정을 했으면

`application.properties`의 설정 이름들은 `spring.datasource.dbname`이 접두사로 붙어서 아래와 같이 명시되어야 합니다.

spring.datasource.dbname.url=jdbc:mysql://localhost:3306/your_db
spring.datasource.dbname.username=root
spring.datasource.dbname.password=your_password
spring.datasource.dbname.driver-class-name=com.mysql.cj.jdbc.Driver

 

SqlSessionFactory 빈 생성

@Bean(name = "dbNameSqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("dbNameDataSource") DataSource dataSource) throws Exception {
    SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
    sqlSessionFactoryBean.setDataSource(dataSource);
    sqlSessionFactoryBean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true); // 스네이크 케이스 → 카멜 케이스 자동 매핑
    sqlSessionFactoryBean.getObject().getConfiguration().setCallSettersOnNulls(true); // NULL 처리
    return sqlSessionFactoryBean.getObject();
}

 

DataSource를 기반으로 MyBatis의 `SqlSessionFactory`를 생성합니다.

`SqlSessionFactory`는 `SqlSession` 객체를 생성하고, 전역 설정을 적용하는 팩토리입니다.

`SqlSessionFactory`에서 `setCallSettersOnNulls`와 같은 설정을 지정하면, 이후 `SqlSessionFactory`를 통해 생성되는 모든 `SqlSession` 객체에 해당 설정이 적용됩니다.
즉, `SqlSessionFactory`는 MyBatis의 전역적인 동작 방식을 설정하고, 이를 기반으로 개별 `SqlSession`을 생성합니다.
  • `setDataSource`는 `SqlSessionFactory`에서 사용할 데이터소스를 지정
  • `setMapUnderscoreToCamelCase`는 데이터베이스의 스네이크 케이스(`snake_case`)를 자바의 카멜 케이스(`camelCase`)로 자동 매핑
  • `setCallSettersOnNulls`는 NULL 값을 명시적으로 설정

 

SqlSessionTemplate 빈 생성

@Bean(name = "dbNameSqlSessionTemplate")
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("dbNameSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
    return new SqlSessionTemplate(sqlSessionFactory);
}

 

스레드에 안전한 `SqlSession`을 제공합니다.

  • `SqlSessionTemplate`는 MyBatis의 `SqlSession`을 래핑 하여 트랜잭션 동기화와 예외 처리를 자동으로 관리합니다.

여기서 그럼 `SqlSession`은 어디 갔는지 궁금하실 수 있을 거 같습니다.

분명히 SQL 실행, 데이터 매핑 등을 수행하는 건 `SqlSession`인데 왜 갑자기 `SqlSessionTemplate`를 반환하는 걸까요?

 

그래서 `SqlSession`과 `SqlSessionTemplate`에 대해서 조금 더 자세하게 설명하려고 합니다.

`SqlSession`과 `SqlSessionTemplate`

우선 방금 얘기한 대로, `SqlSession`은 MyBatis의 핵심 인터페이스로

  • SQL 실행: `insert`, `update`, `delete`, `select` 같은 메서드로 SQL을 실행
  • 매핑 관리: SQL 결과를 자바 객체(POJO)로 변환
  • 트랜잭션 관리: 트랜잭션을 시작, 커밋, 롤백하는 기능 제공

위의 역할을 수행합니다.

간단히 말해서, `SqlSession`은 MyBatis에서 SQL을 실행하고 결과를 처리하는 데이터베이스 작업의 단위 객체입니다.

 

그리고 `SqlSessionTemplate`는 간단히 말해서, 스레드에 안전하도록 `SqlSession`을 래핑 한 구현체입니다.

 

표로 조금 더 상세하게 비교해 보겠습니다.

특징 SqlSession SqlSessionTemplate
역할 SQL 실행 및 매핑 관리 `SqlSession`을 래핑한 쓰레드 안전한 구현체
쓰레드 안전성 쓰레드 안전하지 않음 쓰레드 안전 (멀티스레드 환경에서 안전)
트랜잭션 처리 트랜잭션 동기화를 직접 관리해야 함 스프링이 트랜잭션 동기화를 자동으로 관리
예외 처리 MyBatis 예외를 직접 처리해야 함 MyBatis 예외를 스프링 예외로 변환 처리
Spring 통합 스프링과의 통합 기능 없음
(스프링의 트랜잭션 관리 및 예외 처리 같은 기능과 직접적으로 연결이 안되서 기능을 수동으로 설정 및 개발해주어야함)
스프링과 완전히 통합된 구현체

 

`SqlSession`을 `SqlSessionTemplate`로 래핑하면서 2가지 대표적인 장점을 얻게 되었는데요.

  1. 스레드 안전성: 멀티스레드 환경에서도 안전하게 사용할 수 있게 되었습니다.
  2. 스프링 통합: 스프링의 트랜잭션 및 예외 처리를 자동으로 관리합니다.

결론적으로

  • `SqlSessionFactory`는 `SqlSession` 객체를 생성하고, 전역 설정을 적용하는 팩토리
  • `SqlSession`은 SQL 실행 및 데이터 매핑을 수행하는 핵심 인터페이스
  • `SqlSessionTemplate`는 `SqlSession`을 래핑 한 스레드 안전한 구현체로 스프링 환경에서 권장되는 사용 방식

이라고 할 수 있습니다.

 

TransactionManager 빈 생성

@Bean(name = "dbNameTransactionManager")
public DataSourceTransactionManager transactionManager(@Qualifier("dbNameDataSource") DataSource dataSource) {
    DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
    transactionManager.setDataSource(dataSource);
    return transactionManager;
}

 

데이터소스와 연계된 트랜잭션 관리를 처리합니다.

  • `DataSourceTransactionManager`는 데이터베이스 작업의 트랜잭션 시작, 커밋, 롤백 등을 관리합니다.

만약 단일 데이터소스라면 `DBConfig` 파일을 만들었더라도 `TransactionManager` 부분은 수동으로 따로 만들지 않아도 됩니다.

하지만 다중 데이터소스라면 필수적으로 `TransactionManager`를 생성해야 합니다.

스프링은 여러 데이터소스가 있을 경우, 기본 트랜잭션 매니저를 선택할 수 없으므로, 직접 지정해주어야 합니다.

 

다중 트랜잭션 매니저를 사용하려면 `@Transactional`에서 `@Transactional("dbNameTransactionManager")`와 같이 명시적으로 지정할 수 있습니다.

 

여기서 또 하나의 의문이 생깁니다.

"분명히 `SqlSessionTemplate`에서 트랜잭션 동기화도 자동으로 관리된다고 했는데, `TransactionManager`는 또 왜 필요한 거지?"

결론부터 얘기하면 `SqlSessionTemplate`와 `TransactionManager`는 서로 다른 책임을 맡고 있으며, 트랜잭션 관리와 관련된 역할이 상호보완적입니다.

 

`SqlSessionTemplate`의 역할

`SqlSessionTemplate`는 MyBatis 작업의 스레드 안전성과 스프링 통합을 담당한다고 헀습니다.

SQL 실행 시 자동으로 `SqlSession` 객체를 생성하고, 작업이 끝나면 자동으로 닫으며, 스프링의 트랜잭션 컨텍스트와 동기화하고, MyBatis 예외를 스프링의 DataAccessException으로 변환합니다.

 

하지만 `SqlSessionTemplate`은 트랜잭션 범위를 정의하거나 관리하지 않습니다.

즉, 트랜잭션의 시작과 끝(커밋, 롤백)을 결정할 수 없습니다.

대신, 현재 활성화된 트랜잭션 컨텍스트에 따라 동작합니다.

트랜잭션 컨텍스트: 스프링이 현재 활성화된 트랜잭션에 대한 정보를 저장하는 공간, `TransactionManager`에 의해 관리되는 트랜잭션 생명주기의 한 사이클

`TransactionManager`의 역할

`TransactionManager`는 트랜잭션의 생명주기를 직접 관리합니다.

  • 트랜잭션의 시작, 커밋, 롤백 담당
  • 트랜잭션의 경계를 설정하고, 작업 중간에 오류가 발생하면 롤백 처리
  • 트랜잭션 컨텍스트를 생성하여 `SqlSessionTemplate`과 같은 구성 요소들이 이 컨텍스트를 사용할 수 있도록 함
    • "컨텍스트를 사용한다"는 의미는 트랜잭션 컨텍스트가 데이터베이스 작업을 수행하는 동안 필요한 상태와 리소르를 관리 및 공유한다는 것을 의미합니다.

트랜잭션을 관리할 중앙 컨트롤러로서, 여러 데이터베이스 작업이나 서비스 계층의 비즈니스 로직에서 트랜잭션을 묶어서 처리하기 위해 `TransactionManager`가 필요합니다.

 

정리하자면

`SqlSessionTemplate`은 트랜잭션이 활성화된 상태에서만 동작하며, 트랜잭션의 시작과 경계 설정은 직접적으로 처리하지 않습니다.

`TransactionManager`는 트랜잭션의 생명주기를 관리하며, 스프링과 MyBatis가 트랜잭션 컨텍스트를 공유하도록 해줍니다.

 

둘이 협력해서 동작하는 흐름은 아래와 같습니다.

  1. 스프링이 `@Transactional`을 만나면 `TransactionalManager`를 통해 트랜잭션을 시작합니다.
  2. `TransactionManager`가 트랜잭션 컨텍스트를 생성합니다.
  3. `SqlSessionTemplate`은 트랜잭션 컨텍스트에 동기화된 상태에서 SQL 작업을 수행합니다.
  4. 작업이 끝나면 `TransactionManager`가 트랜잭션을 커밋하거나 롤백합니다.

 

마지막으로 `DBConfig` 파일의 설정흐름을 정리하겠습니다

  1. `DataSource`: DB 연결 정보를 가져와서 해당 정보를 바탕으로 실제 데이터소스를 생성합니다.
  2. `SqlSessionFactory`: DataSource를 사용하여 MyBatis 설정을 적용하고, SQL 실행을 준비합니다.
  3. `SqlSessionTemplate`: SqlSessionFactory를 래핑 하여 스레드 안전하고 트랜잭션 동기화 및 예외 처리가 자동으로 제공되는 MyBatis 작업 환경을 제공합니다.
  4. `TransactionManager`: 데이터베이스 작업의 트랜잭션을 관리합니다.
  5. `@MapperScan`: 지정된 패키지에서 MyBatis 매퍼 인터페이스를 스캔하여 데이터소스와 연결합니다.
반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유