글또

Mybatis 사용시 return 값을 검사하는 이유는 무엇일까

Gisungcu 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문을 통한 더블 체크는 존재하지 않습니다.