스프링과 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;
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
계층 구조를 활용.
프로시저 호출 시 발생할 수 있는 예외 예시
- 입력값에 대한 제약 조건 위반
예:NULL
값이 들어올 수 없는 파라미터가 들어온 경우. - 프로시저 레벨의 데이터 충돌
예: 테이블 락이나 동시성 이슈가 발생했을 경우.
예제:
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.");
}
}
}
결론
스프링과 MyBatis 환경에서 프로시저를 사용하는 경우, 목적에 맞는 프로시저 설계 및 서비스 클래스와의 통합에 유의해야 합니다. 데이터베이스 트랜잭션 관리, 예외 처리, 유지보수 전략과 함께 철저히 테스트를 거치면 안정적인 애플리케이션을 구축할 수 있습니다.
스프링과 MyBatis를 활용하여 프로시저를 개발하고 관리하는 것은 데이터베이스와 애플리케이션 로직을 분리하고, 데이터베이스 성능을 향상시키는 효과적인 방법입니다. 하지만 프로시저 설계, MyBatis 매핑, 트랜잭션 관리, 예외 처리, 리소스 관리, 그리고 유지보수 등 다양한 측면을 고려해야 합니다. 본 가이드를 통해 스프링과 MyBatis를 활용하여 프로시저를 개발하고 관리하는 데 필요한 지식을 습득하고, 효율적인 데이터베이스 시스템을 구축할 수 있기를 바랍니다.
'SpringBoot 를 배워보자' 카테고리의 다른 글
Nuxt.js와 Spring Boot로 Oracle 저장 프로시저 소스 및 파라미터 조회하기 (1) | 2025.03.01 |
---|---|
MyBatis의 mapUnderscoreToCamelCase 설정을 false로 변경하고 수동 매핑하는 방법 (2) | 2025.01.05 |
두 테이블에 공통된 대조 필드가 있을 때, 사용자가 원하는 필드들만 출력되도록 동적 쿼리를 작성 (0) | 2024.12.02 |
쿼리의 페이징을 옵션에 따라 포함시키거나 제외하는 동적 SQL (0) | 2024.12.02 |
오라클 환경에서 MyBatis를 사용하여 SQL을 기능적으로 쪼개어 동적으로 조합하는 예 (0) | 2024.12.02 |