트랜잭션 관리 개요
트랜잭션은 데이터베이스 작업의 단위로, 여러 개의 연산을 하나의 논리적인 작업單位로 묶습니다. 모든 연산이 성공하면 커밋(commit)되고, 하나라도 실패하면 롤백(rollback)되어 이전 상태로 복구됩니다.
사례: 부서 삭제 시 연관 데이터 처리
부서를 삭제할 때 해당 부서에 속한 직원들도 함께 삭제해야 하는 경우가 있습니다. 이때 트랜잭션을 사용하면 부서 삭제와 직원 삭제를 하나의 원자적 연산으로 처리할 수 있습니다.
@Transactional 어노테이션
Spring에서 트랜잭션 관리는 @Transactional 어노테이션을 통해 쉽게 처리할 수 있습니다. 이 어노테이션을 메서드나 클래스에 적용하면 해당 범위内的 데이터베이스 작업이 트랜잭션으로 관리됩니다.
트랜잭션 로그 활성화
트랜잭션 동작 과정을 확인하려면 application.yml에 로깅 설정을 추가하면 됩니다.
코드 예제: 부서 삭제 시 트랜잭션 롤백
다음은 부서 삭제 시 연관된 직원 데이터도 함께 삭제하는 예제입니다. 중간에 오류가 발생하면 전체 트랜잭션이 롤백됩니다.
EmpMapper
package com.example.mapper;
import com.example.entity.Emp;
import org.apache.ibatis.annotations.*;
import java.time.LocalDate;
import java.util.List;
/**
* 직원 데이터 매퍼
*/
@Mapper
public interface EmpMapper {
/**
* 직원 목록 조회
*/
public List<Emp> search(@Param("name") String name,
@Param("gender") Short gender,
@Param("begin") LocalDate begin,
@Param("end") LocalDate end);
/**
* 다중 삭제
* @param ids 삭제할 직원 ID 목록
*/
void delete(@Param("ids") List<Integer> ids);
/**
* 직원 추가
* @param emp 직원 정보
*/
@Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) " +
"VALUES(#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime}) ")
void insert(Emp emp);
/**
* ID로 직원 조회
* @param id 직원 ID
* @return 직원 정보
*/
@Select("select * from emp where id = #{id}")
Emp getById(Integer id);
/**
* 직원 정보 수정
* @param emp 직원 정보
*/
void update(Emp emp);
/**
* 아이디와 비밀번호로 직원 조회
* @param emp 검색 조건
* @return 직원 정보
*/
@Select("select * from emp where username = #{username} and password = #{password}")
Emp getByUsernameAndPassword(Emp emp);
/**
* 부서에 속한 직원 삭제
* @param deptId 부서 ID
*/
@Delete("delete from emp where dept_id = #{deptId}")
void deleteByDeptId(Integer deptId);
}
DeptServiceImpl
package com.example.service.impl;
import com.example.mapper.DeptMapper;
import com.example.mapper.EmpMapper;
import com.example.entity.Dept;
import com.example.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptMapper deptMapper;
@Autowired
private EmpMapper empMapper;
@Override
public List<Dept> search() {
return deptMapper.list();
}
@Transactional
@Override
public void delete(Integer id) {
deptMapper.deleteById(id);
int result = 1 / 0;
empMapper.deleteByDeptId(id);
}
@Override
public void add(Dept dept) {
dept.setCreateTime(LocalDateTime.now());
dept.setUpdateTime(LocalDateTime.now());
deptMapper.insert(dept);
}
@Override
public Dept getByID(Integer id) {
return deptMapper.getByID(id);
}
@Override
public void update(Dept dept) {
dept.setUpdateTime(LocalDateTime.now());
deptMapper.update(dept);
}
}
application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/tlias
username: root
password: root
servlet:
multipart:
max-file-size: 10MB
max-request-size: 100MB
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
aliyun:
oss:
endpoint: *************
accessKeyId: *************
accessKeySecret: *************
bucketName: *************
logging:
level:
org.springframework.jdbc.support.JdbcTransactionManager: debug
실행 결과
delete 메서드에서 의도적으로 예외(1/0)를 발생시켰을 때, 트랜잭션이 관리되고 있으므로 전체 작업이 롤백됩니다. 부서 데이터와 해당 부서에 속한 직원 데이터 모두 삭제되지 않고 원래 상태를 유지합니다. 이것이 트랜잭션의 원자성(atomicity)을 보장하는 Spring의 특징입니다.