[Spring] Spring MyBatis

배창민·2025년 10월 23일
post-thumbnail

1. Spring MyBatis

mybatis-spring 문서

1-1. spring-mybatis-boot-starter

  • 자동 구성
    실행 시 매퍼 스캔, SqlSessionFactory, SqlSessionTemplate을 자동 등록
  • 장점
    설정 최소화, 의존성 관리 일원화

1-2. HikariDataSource

  • 핵심 설정

    • connection-timeout 커넥션 획득 대기 시간
    • idle-timeout 유휴 커넥션 유지 시간
    • max-lifetime 커넥션 최대 수명
    • maximum-pool-size 풀 최대 커넥션 수
  • 권장
    트래픽 패턴과 DB 자원에 맞춰 수치 튜닝

예시

spring:
  datasource:
    url: jdbc:mariadb://localhost:3306/app
    driver-class-name: org.mariadb.jdbc.Driver
    username: app
    password: app
    hikari:
      maximum-pool-size: 10
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000

1-3. SqlSessionFactory와 SqlSessionFactoryBean

  • SqlSessionFactory SqlSession 생성 팩토리
  • SqlSessionFactoryBean 스프링 환경에서 SqlSessionFactory를 만드는 팩토리 빈
  • 주요 구성
    타입 별칭, 매퍼 위치, 카멜케이스 매핑

예시

@Configuration
@MapperScan("com.example.project.mapper")
public class MyBatisConfig {

  @Bean
  public SqlSessionFactory sqlSessionFactory(DataSource ds) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(ds);

    org.apache.ibatis.session.Configuration conf =
        new org.apache.ibatis.session.Configuration();
    conf.setMapUnderscoreToCamelCase(true);
    factory.setConfiguration(conf);

    return factory.getObject();
  }

  @Bean
  public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory f) {
    return new SqlSessionTemplate(f);
  }
}

1-4. SqlSessionTemplate

  • 스레드 세이프한 SqlSession 래퍼
  • 스프링 트랜잭션과 연동
  • 예외를 DataAccessException 계열로 변환

예시

@Repository
public class UserQuery {
  private final SqlSessionTemplate sql;

  public UserQuery(SqlSessionTemplate sql) { this.sql = sql; }

  public User findById(Long id) {
    return sql.selectOne("UserMapper.findById", id);
  }
}

1-5. application.yml로 MyBatis 설정

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

1-6. @MapperScan

@SpringBootApplication
@MapperScan("com.example.project.mapper")
public class Application {
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

2. Spring Test

2-1. 단위 테스트 Mockito

  • 목적
    외부 의존성 제거 후 서비스 로직 검증
  • 장점
    빠른 실행, 로직에만 집중
  • 단점
    인프라 연동 검증은 불가
  • 주요 애노테이션
    @ExtendWith(MockitoExtension.class), @Mock, @InjectMocks

예시

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

  @Mock private UserMapper userMapper;
  @InjectMocks private UserService userService;

  @Test
  void find_user() {
    when(userMapper.findById(1L)).thenReturn(new User(1L, "u"));
    User u = userService.find(1L);
    assertEquals(1L, u.getId());
  }
}

2-2. 통합 테스트

  • 목적
    실제 컨텍스트 로딩으로 빈 구성, 트랜잭션, AOP, MyBatis 연동 검증
  • 장점
    구성 오류와 상호작용 문제 조기 발견
  • 단점
    느린 실행, 환경 의존성

권장 조합
@SpringBootTest, @Transactional, @ActiveProfiles test, H2 인메모리

테스트 설정 예시

# application-test.yml
spring:
  datasource:
    url: jdbc:h2:mem:testdb;MODE=MYSQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
    driver-class-name: org.h2.Driver
    username: sa
    password:
  sql:
    init:
      mode: always
      schema-locations: classpath:db/schema.sql
      data-locations: classpath:db/data.sql
mybatis:
  mapper-locations: classpath:mapper/**/*.xml
  configuration:
    map-underscore-to-camel-case: true

통합 테스트 예시

@SpringBootTest
@ActiveProfiles("test")
@Transactional
class UserServiceIT {

  @Autowired private UserService userService;

  @Test
  void create_and_find() {
    Long id = userService.create(new UserCreate("name"));
    assertNotNull(userService.find(id));
  }
}

3. Transaction

3-1. @Transactional

  • 목적
    여러 DB 작업을 하나의 논리 단위로 묶어 일관성 보장
  • 위치
    서비스 계층 중심

주요 속성

isolation

  • READ_UNCOMMITTED 커밋 전 변경도 조회 가능
  • READ_COMMITTED 커밋된 데이터만 조회
  • REPEATABLE_READ 동일 행 재조회 결과 일관
  • SERIALIZABLE 동시성 제약이 가장 강함

propagation

옵션설명
REQUIRED있으면 참여, 없으면 생성
REQUIRES_NEW항상 새 트랜잭션, 기존은 중단
SUPPORTS있으면 참여, 없으면 비트랜잭션
NOT_SUPPORTED비트랜잭션으로 실행, 기존은 중단
MANDATORY기존 트랜잭션 필수, 없으면 예외
NEVER트랜잭션 금지, 있으면 예외
NESTED내부 세이브포인트 기반 중첩 처리

rollbackFor

  • 기본
    런타임 예외 시 롤백
  • 체크 예외도 롤백 필요 시
@Transactional(rollbackFor = { Exception.class })
public void process() { ... }

주의 사항

  • 프록시 기반이라 public 메서드 중심 적용
  • 자기 호출은 어드바이스 미적용 가능
  • 읽기 전용 쿼리는 readOnly true 설정을 고려
@Transactional(readOnly = true)
public User find(Long id) { ... }

체크리스트

  • DataSource 풀 수치가 트래픽과 커넥션 한도에 적합한지
  • mybatis mapper-locations와 type-aliases-package 경로가 실제와 일치하는지
  • 단위 테스트는 Mockito로 빠르게, 통합 테스트는 H2와 롤백으로 격리되는지
  • 트랜잭션 경계가 서비스 기준으로 명확한지
  • 동시성 이슈 구간의 isolation과 propagation이 의도대로 설정되었는지
profile
개발자 희망자

0개의 댓글