ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Mybatis 사용시 return 값을 검사하는 이유는 무엇일까
    글또 2024. 12. 18. 22:54
    2024.12.18 과거 글 수정

    들어가며

    안녕하세요.
    이번 글에서는 MyBatis 사용 중 고민되었던 코드 작성 방식에 대해 다뤄보겠습니다.

     

     

    고민

    다음은 새로운 회사의 코드에서 발견한 예제입니다.

    이 코드가 왜 result 값을 검사하는지 고민하게 되었습니다.

    @Service
    class UserService(
        private val userRepository: UserRepository
    ) {
    
        fun register(user: User) {
            val result = userRepository.insert(user)
    
            if (result != 1L) {
                throw RuntimeException("사용자 등록 중 예외가 발생했습니다.")
            }
        }
    }

     

    해당 코드는 DDL 문 실행 후 반환된 row 수를 확인하고, 예상치와 다를 경우 Exception을 발생시키는 구조입니다.
    MyBatis는 DDL 문 실행 시 영향을 받은 row 수를 반환하므로, 위 코드는 단일 row만 처리되었을 때 정상 동작하게 됩니다.

     

    DDL 문 실패 시 Exception 발생 가능성

     

    DDL 문 실행 중 예외가 발생할 수 있는 상황은 다음과 같습니다:

    1. DB 연결 문제
    2. SQL 문법 오류
    3. 제약 조건 미충족

     

     

    1. DB 연결 문제 및 SQL 문법 오류

    이 경우 Spring은 DataAccessException을 자동으로 throw합니다.
    if 문으로는 이러한 예외를 처리할 수 없으며, try-catch 블록을 사용해야 합니다.

    하지만 모든 DDL 문에 try-catch를 적용하는 것은 코드의 가독성을 저하시킬 뿐 아니라 컴파일러 최적화를 방해할 수 있습니다.
    혹은, 전역 예외 핸들링으로 제어하는 방법도 고려할 수 있습니다.

        fun register(user: User) {
            try {
                val result = userRepository.insert(user)
            } catch (e: DataAccessException) {
                throw RuntimeException("사용자 등록 중 데이터베이스 예외가 발생했습니다.", e)
            }
        }

    위와 같이 작성해도 DB 연결 문제나 SQL 문법 오류는 result 값으로 확인할 수 없습니다.

     

     

     

    2. 제약 조건 미충족

     

    제약 조건이 맞지 않을 경우 DDL 문은 실행되지 않고 예외를 발생시킵니다.
    예를 들어, "동명이인의 유저를 생성할 수 없다"는 요구사항이 있을 때, name 컬럼에 UK(Unique Key)를 설정할 수 있습니다.

    이후 다음과 같은 동작이 발생할 수 있습니다:

     

    어플리케이션에서 조건 확인 후 INSERT 실행

    이 방법은 동명이인이 다른 트랜잭션에서 이미 등록된 경우, MyBatis가 result 값을 반환하지 않고 SqlException을 throw하게 됩니다

    @Service
    class UserService(
        private val userRepository: UserRepository
    ) {
        @Transactional
        fun registerUser(name: String) {
            // 1. 조건 확인 (SELECT)
            val existingUser = userRepository.findByName(name)
    
            if (existingUser != null) {
                throw IllegalArgumentException("이미 존재하는 사용자입니다: $name")
            }
    
            // 2. INSERT 실행
            val result = userRepository.insertUser(name)
    
            if (result != 1L) {
                throw RuntimeException("사용자 등록에 실패했습니다.")
            }
        }
    }

     

     

    조건부 INSERT 사용

     

    예외 발생을 방지하고 실행 결과를 확인하려면 INSERT ... WHERE 조건을 추가하여 제어할 수 있습니다.

    INSERT INTO user (name)
    (SELECT '홍길동'
    WHERE NOT EXISTS (
        SELECT 1
        FROM user
        WHERE name = '홍길동'
    ));

    위 SQL은 삽입과 조회를 동시에 처리하여, 예외 대신 result = 0을 반환합니다.

    이를 통해 애플리케이션에서 로직을 제어할 수 있습니다.
    단, 조회 테이블을 대상으로 읽기 잠금 발생하니 주의가 필요합니다.

     

     

    참고 사례

     

    GitHub - macrozheng/mall: mall项目是一套电商系统,包括前台商城系统及后台管理系统,基于SpringBoot+MyB

    mall项目是一套电商系统,包括前台商城系统及后台管理系统,基于SpringBoot+MyBatis实现,采用Docker容器化部署。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程

    github.com

    git에서 가장 많은 star를 받은 Mybatis 사용 코드에서도 if문을 통한 더블 체크는 존재하지 않습니다.

    '글또' 카테고리의 다른 글

    2024 회고  (0) 2024.12.22
    Galera Cluster 알아보기  (0) 2024.12.20
    Sharding 만들어보기  (0) 2024.11.24
    실시간 리더보드 만들어보기  (0) 2024.11.09
    글또10기 삶의 지도  (0) 2024.10.12

    댓글

Designed by Tistory.