SpringBoot 를 배워보자

스프링과 MyBatis를 활용한 데이터베이스 프로시저 개발 가이드: 효율적인 설계와 유지보수를 위한 종합적인 안내

_Blue_Sky_ 2024. 12. 28. 20:58
728x90

스프링과 MyBatis에서 모든 데이터베이스 쿼리를 프로시져로 할때 주의 점

스프링(Spring)과 MyBatis를 사용해 데이터베이스와 연동하고, 모든 데이터베이스 쿼리를 프로시저(Stored Procedure)로 처리하려는 경우, 효율적인 개발과 유지보수를 위해 신경 써야 할 몇 가지 중요한 사항이 있습니다. 이와 함께 예제를 통해 더 구체적으로 설명하겠습니다.


프로시저 사용 시 주요 고려 사항

1. 프로시저 설계

  • 명확한 목적 정의: 각 프로시저가 명확한 단일 책임을 가지도록 설계합니다. 프로시저가 한 가지 이상을 처리하려 하면 복잡도가 증가하고 유지보수도 어려워질 수 있습니다.
    • 예: get_user_info (사용자의 정보를 조회)와 update_user_status (사용자의 상태를 업데이트)를 별도의 프로시저로 분리.
  • 파라미터 정의:
    • 입력 파라미터와 출력 파라미터를 명확히 구분합니다.
    • Null 값 처리가 필요한 경우 적절한 기본값을 지정하거나 입력값 검증을 구현하세요.
    • MyBatis는 입력/출력 파라미터와의 매핑이 필요하기 때문에, 이 부분을 신경 써야 합니다.

예제

CREATE PROCEDURE get_user_info(
IN user_id INT,
OUT user_name VARCHAR(100),
OUT user_email VARCHAR(100)
)
BEGIN
SELECT name, email INTO user_name, user_email
FROM users
WHERE id = user_id;
END;

 

728x90

2. 스프링과 MyBatis 통합

A. MyBatis 매핑 설정

MyBatis에서 프로시저 호출은 CALL문을 사용하며, 이를 매핑할 때 명확한 파라미터 매핑이 요구됩니다.

  • 입력/출력 파라미터 정의: 출력 파라미터를 정의하고 SQL의 OUT 매개변수와 매핑해야 합니다.
  • ResultSet 처리: 프로시저가 ResultSet(쿼리의 결과)을 반환할 경우, 이를 처리할 매퍼와 매핑 설정이 필요합니다.

예제:

<!-- 프로시저 호출 및 매핑 -->
<select id="callGetUserInfo" resultType="map">
    CALL get_user_info(#{userId, mode=IN}, #{userName, mode=OUT, jdbcType=VARCHAR}, #{userEmail, mode=OUT, jdbcType=VARCHAR})
</select>

 

B. 스프링 서비스 코드 작성

MyBatis 매퍼를 호출하는 스프링의 서비스 코드를 작성할 때는 적절한 파라미터를 전달하고, 출력 파라미터를 매핑합니다.

예제:

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public Map<String, Object> getUserInfo(int userId) {
        Map<String, Object> params = new HashMap<>();
        params.put("userId", userId);
        params.put("userName", null);  // OUT 파라미터 초기화
        params.put("userEmail", null); // OUT 파라미터 초기화

        userMapper.callGetUserInfo(params);

        // 출력 파라미터 읽기
        String userName = (String) params.get("userName");
        String userEmail = (String) params.get("userEmail");

        Map<String, Object> result = new HashMap<>();
        result.put("userName", userName);
        result.put("userEmail", userEmail);

        return result;
    }
}

3. 트랜잭션 관리

프로시저 내부에서 여러 쿼리가 처리될 경우 데이터 무결성을 보장하기 위해 트랜잭션 관리가 중요합니다.

  • 스프링의 @Transactional 어노테이션을 사용하여 서비스 레벨에서 트랜잭션을 처리할 수 있습니다.
  • 프로시저 내에서 명시적으로 COMMIT 또는 ROLLBACK을 호출하는 경우, 스프링의 트랜잭션 관리와 충돌이 발생할 수 있으므로 주의해야 합니다.

예제:

@Service
@Transactional
public class UserService {
    public void performUserOperation(int userId, String newStatus) {
        // 사용자 상태 업데이트를 포함하는 프로시저 호출
        userMapper.callUpdateUserStatus(userId, newStatus);
    }
}

4. 예외 처리

  • DB 예외 처리: 프로시저 내부에서 발생하는 예외를 명시적으로 처리하거나, 프로시저 호출에 실패했을 때 스프링에서 예외를 적절히 처리해야 합니다.
    • SQL 예외 메시지를 로깅하거나 사용자에게 전달할 적절한 메시지로 변환.
    • 스프링의 DataAccessException 계층 구조를 활용.

프로시저 호출 시 발생할 수 있는 예외 예시

  1. 입력값에 대한 제약 조건 위반
    예: NULL 값이 들어올 수 없는 파라미터가 들어온 경우.
  2. 프로시저 레벨의 데이터 충돌
    예: 테이블 락이나 동시성 이슈가 발생했을 경우.

예제:

try {
    userService.performUserOperation(1, "ACTIVE");
} catch (DataAccessException e) {
    // DB 예외 처리
    System.err.println("Database error occurred: " + e.getMessage());
    throw new CustomServiceException("Operation failed. Please try again later.");
}

5. 리소스 관리

프로시저 호출이 Connection 풀, Statement 리소스를 적절히 사용하도록 관리해야 합니다.

  • MyBatis는 기본적으로 Connection 풀을 사용하여 리소스를 관리하지만, 프로시저가 오래 실행되는 경우 타임아웃이나 커넥션 누수를 방지해야 합니다.
  • 결과를 반환하지 않는 프로시저의 경우, 호출 후 즉시 리소스를 해제하도록 설계합니다.

6. 프로시저 의존성 문제

테이블의 구조 또는 컬럼이 변경되면, 프로시저에도 영향을 미칠 수 있습니다. 따라서 다음과 같이 유지 보수를 쉽게 하기 위한 전략을 수립해야 합니다.

  • 프로시저 문서화: 입력/출력 파라미터와 비즈니스 로직을 문서화하여 향후 변경에 대비.
  • 테스트 케이스 작성: 프로시저 변경 시 기존 기능이 깨지지 않도록 가능한 많이 테스트 케이스를 작성합니다.

예제 시나리오: 스프링과 MyBatis로 프로시저 호출

1. 데이터베이스 프로시저 정의

CREATE PROCEDURE update_user_status(
    IN user_id INT,
    IN new_status VARCHAR(50)
)
BEGIN
    UPDATE users
    SET status = new_status, updated_at = NOW()
    WHERE id = user_id;
END;

2. MyBatis Mapper 설정

<update id="callUpdateUserStatus">
    CALL update_user_status(#{userId, mode=IN}, #{newStatus, mode=IN})
</update>

3. 스프링 서비스 클래스

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    @Transactional
    public void updateUserStatus(int userId, String newStatus) {
        userMapper.callUpdateUserStatus(userId, newStatus);
    }
}

4. 컨트롤러

@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserService userService;

    @PostMapping("/{id}/status")
    public ResponseEntity<String> updateUserStatus(@PathVariable int id, @RequestBody Map<String, String> payload) {
        String newStatus = payload.get("status");
        try {
            userService.updateUserStatus(id, newStatus);
            return ResponseEntity.ok("Status updated successfully.");
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error updating status.");
        }
    }
}

결론

728x90

스프링과 MyBatis 환경에서 프로시저를 사용하는 경우, 목적에 맞는 프로시저 설계 및 서비스 클래스와의 통합에 유의해야 합니다. 데이터베이스 트랜잭션 관리, 예외 처리, 유지보수 전략과 함께 철저히 테스트를 거치면 안정적인 애플리케이션을 구축할 수 있습니다.
스프링과 MyBatis를 활용하여 프로시저를 개발하고 관리하는 것은 데이터베이스와 애플리케이션 로직을 분리하고, 데이터베이스 성능을 향상시키는 효과적인 방법입니다. 하지만 프로시저 설계, MyBatis 매핑, 트랜잭션 관리, 예외 처리, 리소스 관리, 그리고 유지보수 등 다양한 측면을 고려해야 합니다. 본 가이드를 통해 스프링과 MyBatis를 활용하여 프로시저를 개발하고 관리하는 데 필요한 지식을 습득하고, 효율적인 데이터베이스 시스템을 구축할 수 있기를 바랍니다.

728x90