PlatformTransactionManager
트랜잭션 [Transaction]
※ 트랜잭션[Transaction]
트랜잭션은 데이터베이스 관리 시스템(DBMS)에서 데이터의 무결성을 보장하기 위해 사용되는 기본 단위입니다. 간단하게 말하자면, 트랜잭션은 데이터베이스에 대한 하나 이상의 연산들의 그룹으로, 이들 연산은 하나의 단일 작업으로 취급됩니다. 트랜잭션의 주요 특징[ACID]은 다음과 같습니다:
1. 원자성(Atomicity): 트랜잭션 내의 모든 연산들은 전체적으로 완료되거나 전혀 실행되지 않아야 합니다. 즉, 모든 단계가 성공적으로 완료되거나, 하나라도 실패하면 전체 트랜잭션이 취소(롤백)됩니다.
2. 일관성(Consistency): 트랜잭션이 완료된 후에는 데이터베이스가 일관된 상태를 유지해야 합니다. 이는 트랜잭션 전후로 데이터베이스의 모든 무결성 규칙이 유지되어야 함을 의미합니다.
3. 격리(Isolation): 각 트랜잭션은 다른 트랜잭션의 중간 결과를 볼 수 없어야 합니다. 이는 여러 트랜잭션이 동시에 실행될 때, 서로의 연산이 서로를 방해하지 않도록 보장합니다.
4. 지속성(Durability): 트랜잭션이 성공적으로 완료되면, 그 결과는 시스템 장애가 발생하더라도 데이터베이스에 영구적으로 반영되어야 합니다.
이러한 특성들은 데이터베이스의 안정성과 무결성을 유지하는 데 필수적입니다. 트랜잭션은 데이터베이스의 중요한 변경 사항을 관리하고, 오류 발생 시 이전의 안전한 상태로 복구할 수 있는 메커니즘을 제공합니다. 예를 들어, 은행 시스템에서 계좌 이체는 트랜잭션을 통해 처리됩니다. 이체 과정에서 발생할 수 있는 다양한 문제를 트랜잭션을 통해 안전하게 관리할 수 있습니다.
※ 무결성
데이터베이스 무결성은 데이터베이스 내에 저장된 데이터가 일관되고 정확하며, 실제 세계의 정보를 정확히 반영하는 것을 의미합니다. 데이터베이스 무결성에는 여러 유형이 있으며, 각각은 데이터베이스의 정확성과 신뢰성을 유지하기 위한 중요한 역할을 합니다. 주요 무결성 유형은 다음과 같습니다:
1. 개체 무결성(Entity Integrity): 이는 데이터베이스의 각 행(튜플)이 유일하게 식별되어야 한다는 원칙입니다. 이를 위해 기본 키(primary key)가 사용되며, 각 튜플은 고유한 기본 키 값을 가져야 하며, 이 값은 NULL이 될 수 없습니다.
2. 참조 무결성(Referential Integrity): 외래 키(foreign key)는 참조 무결성을 보장하기 위해 사용됩니다. 외래 키 값은 참조되는 테이블의 기본 키와 일치하거나 NULL이어야 합니다. 이는 데이터베이스 내에서 무효한 참조를 방지하고 관계의 정확성을 보장합니다.
3. 도메인 무결성(Domain Integrity): 모든 데이터베이스 필드는 정의된 도메인(유효한 값의 집합)에 속해야 합니다. 이는 데이터 타입, 길이 제한, 가능한 값의 범위 등을 포함할 수 있으며, 필드의 데이터가 이러한 제약 조건을 준수하도록 보장합니다.
추가적인 무결성 유형들은 다음과 같습니다:
1. 사용자 정의 무결성(User-Defined Integrity): 이는 특정 애플리케이션의 요구 사항에 맞게 정의된 규칙이나 제약 조건을 말합니다. 이는 비즈니스 규칙이나 특정 데이터 관계를 반영할 수 있습니다.
2. NULL 무결성(Null Integrity): 특정 필드가 NULL 값을 가질 수 없도록 하는 제약 조건입니다. 이는 필수적인 정보가 누락되지 않도록 보장합니다.
3. 고유 무결성(Unique Integrity): 특정 필드의 모든 값이 고유해야 함을 나타냅니다. 이는 기본 키 외에도 중복되지 않는 값을 요구하는 경우에 적용됩니다.
4. 키 무결성(Key Integrity): 테이블에 적어도 하나의 키(기본 키 또는 후보 키)가 존재하여 각 튜플을 고유하게 식별할 수 있어야 한다는 원칙입니다.
5. 관계 무결성(Relational Integrity): 테이블 간의 관계가 정확하게 유지되어야 함을 의미합니다. 이는 참조 무결성과 밀접하게 연관되어 있습니다.
이러한 무결성 유형들은 데이터베이스의 신뢰성과 정확성을 보장하는 데 중요한 역할을 하며, 데이터베이스 관리 시스템(DBMS)의 설계와 운영에 핵심적인 요소입니다.
※ 도메인 무결성
도메인 무결성(Domain Integrity)은 데이터베이스 필드가 특정한 규칙이나 조건에 맞는 값만을 가질 수 있도록 하는 개념입니다. "도메인"은 특정 필드가 가질 수 있는 유효한 값의 집합이나 범위를 의미합니다. 도메인 무결성을 통해 데이터의 정확성과 일관성을 보장합니다.
1. 데이터 타입(Data Type): 각 필드는 특정한 데이터 타입을 가집니다. 예를 들어, 날짜 필드는 'YYYY-MM-DD' 형식의 날짜 데이터만 받아들이고, 숫자 필드는 정수나 소수만을 저장합니다. 문자열 필드는 문자 데이터만을 포함합니다.
2. 길이 제한(Length Constraint): 문자열 필드에 대해 최대 길이를 정할 수 있습니다. 예를 들어, 사용자 이름 필드가 최대 50자까지만 허용될 수 있습니다.
3. 값의 범위(Value Range): 숫자 필드에 대해 특정 범위의 값만을 허용할 수 있습니다. 예를 들어, 나이 필드는 0부터 100 사이의 값만을 저장할 수 있습니다.
4. 기본값(Default Values): 필드에 값이 명시적으로 제공되지 않은 경우, 데이터베이스가 자동으로 설정하는 기본값을 지정할 수 있습니다. 예를 들어, 사용자의 계정 상태 필드에 기본값으로 '활성'을 설정할 수 있습니다.
5. 유효성 검사(Validation)**: 특정 필드에 대해 추가적인 유효성 검사 규칙을 적용할 수 있습니다. 예를 들어, 이메일 필드는 유효한 이메일 주소 형식을 따라야 합니다.
도메인 무결성은 이와 같은 방법으로 데이터베이스 필드의 값이 정의된 규칙과 조건을 준수하도록 하여, 데이터의 정확성과 신뢰성을 유지하는 데 중요한 역할을 합니다. 데이터베이스에 저장되는 각 필드의 데이터가 올바르고, 예상된 형식을 따르도록 하는 것이 이 개념의 핵심입니다.
※ 도메인
도메인(Domain)이라는 용어는 컴퓨터 과학과 정보 기술 분야에서 여러 가지 의미로 사용됩니다. 여기서는 두 가지 주요한 맥락에서 도메인의 의미를 기술합니다.
1. 데이터베이스에서의 도메인
데이터베이스 분야에서 도메인은 특정 필드(컬럼)에 저장될 수 있는 값의 집합을 의미합니다. 도메인은 그 필드에 들어갈 수 있는 유효한 값들의 범위, 타입, 제약 조건 등을 정의합니다. 이는 데이터의 유형, 형식, 길이, 범위 등을 지정하여 데이터의 정확성과 무결성을 유지하는 데 도움을 줍니다. 예를 들어, 성별 필드의 도메인은 '남', '여'의 값만을 포함할 수 있고, 날짜 필드의 도메인은 특정 형식의 날짜 데이터만을 포함할 수 있습니다.
2. 네트워크 및 인터넷에서의 도메인
인터넷과 네트워크에서 도메인은 일반적으로 도메인 이름 시스템(Domain Name System, DNS)의 컨텍스트에서 사용되며, 인터넷 상의 컴퓨터, 서비스, 또는 네트워크를 식별하는 이름을 의미합니다. 예를 들어, "http://www.example.com"은 웹사이트 주소를 위한 도메인 이름입니다. 이 도메인 이름은 보다 쉽게 기억하고 사용할 수 있도록 IP 주소(인터넷 프로토콜 주소)를 인간이 읽을 수 있는 형태로 변환하는 역할을 합니다.
이 두 맥락에서의 도메인은 전혀 다른 개념이지만, 공통적으로는 어떤 특정 범위나 영역을 정의한다는 의미를 갖고 있습니다. 데이터베이스에서는 값의 유형과 범위를, 인터넷에서는 네트워크 자원의 주소를 나타냅니다.
※ 자바 프로젝트에서 도메인
자바 프로젝트에서 도메인은 소프트웨어가 해결하려고 하는 비즈니스 문제 영역 또는 애플리케이션의 핵심 기능과 관련된 영역을 말합니다. 도메인은 애플리케이션의 비즈니스 로직과 데이터 모델을 정의하며, 구체적으로 다음과 같은 요소들을 포함합니다:
1. 엔티티(Entities): 비즈니스 도메인의 핵심 개체나 개념을 나타내는 클래스로, 고유한 식별자를 가지고 있습니다. 예를 들어, 온라인 쇼핑 애플리케이션에서는 '상품', '고객', '주문' 등이 엔티티가 될 수 있습니다.
2. 밸류 오브젝트(Value Objects): 엔티티와 달리 식별성을 가지지 않는 불변의 객체로, 주로 간단한 속성을 나타냅니다. 예를 들어, '주소', '돈', '이름' 등이 밸류 오브젝트가 될 수 있습니다.
3. 도메인 서비스(Domain Services): 특정 엔티티나 밸류 오브젝트에 속하지 않는 복잡한 비즈니스 로직을 구현합니다. 이들은 여러 엔티티 간의 상호 작용이나 비즈니스 규칙을 나타내는 데 사용됩니다.
4. 도메인 이벤트(Domain Events): 도메인 내에서 발생하는 중요한 사건이나 변화를 나타내며, 이벤트 주도 아키텍처에서 사용됩니다.
5. 레포지토리(Repositories): 엔티티의 지속성을 관리하며, 데이터베이스와의 상호작용을 캡슐화합니다.
자바 프로젝트에서 도메인을 중심으로 개발하는 것은 소프트웨어의 비즈니스 요구사항을 정확히 반영하고, 시스템의 복잡성을 관리하는 데 도움을 줍니다. 도메인 중심 설계는 개발자가 비즈니스 로직에 집중할 수 있게 하며, 코드의 재사용성과 유지보수성을 높여줍니다. 또한, 도메인 모델은 비즈니스 요구사항의 변화에 유연하게 대응할 수 있는 설계를 가능하게 합니다.
트랜잭션의 경계설정[Transaction demarcation ]
트랜잭션 경계설정(transaction demarcation)이란 애플리케이션 내에서 트랜잭션의 시작과 끝을 명확히 정의하는 것을 말합니다. 이것은 데이터의 일관성과 무결성을 보장하기 위해, 일련의 데이터베이스 연산들이 하나의 단위로 성공적으로 수행되거나, 오류가 발생할 경우 이전 상태로 되돌릴 수 있도록 합니다. 트랜잭션 경계설정은 프로그래밍적으로 애플리케이션 코드에 의해 수행될 수도 있고, 선언적으로(예를 들어 어노테이션을 통해) 애플리케이션의 구성을 통해 수행될 수도 있습니다.
이는 트랜잭션을 시작하고, 커밋하고, 롤백하는 절차를 포함합니다. 프로그래밍에서 이것은 애플리케이션 코드가 트랜잭션 경계를 명시적으로 제어하는 프로그래밍 방식과, 경계가 애플리케이션의 설정을 통해 선언적으로 정의되는 방식, 종종 주석이나 XML 설정 파일을 통해 이루어집니다.
프로그래밍 방식의 디마케이션은 특정 API 호출을 사용하여 트랜잭션을 시작하고 끝내는 것을 포함하며, 선언적 디마케이션은 EJB 컨테이너와 같은 컨테이너가 트랜잭션 경계를 관리할 수 있게 하여 애플리케이션 코드에서 명시적인 트랜잭션 제어의 필요성을 줄입니다.
결론적으로, 트랜잭션 디마케이션은 데이터의 일관성과 무결성을 유지하고, 여러 작업이 모두 성공하거나 단위로 실패하도록 보장하는 데 중요합니다. 이는 특히 여러 리소스를 조정해야 하는 분산 시스템과 데이터베이스 애플리케이션에서 중요합니다.
스프링의 트랜잭션
멀티 스레드 환경에서의 JDBC 트랜잭션 관리 이해
JDBC에서 트랜잭션은 데이터베이스 연결로 시작하여 커밋 또는 롤백으로 끝나는 경계를 가집니다. 애플리케이션 내에서 트랜잭션이 시작하고 끝나는 위치를 트랜잭션 경계라고 합니다. 이것은 특히 여러 관련 데이터베이스 작업을 단일 원자 단위로 실행해야 할 때 데이터 무결성을 보장하기 위해 중요합니다.
Spring Framework는 JDBC 트랜잭션 처리의 복잡성을 추상화함으로써 트랜잭션 관리를 단순화합니다.
여기에서 TransactionSynchronizationManager가 핵심 역할을 합니다.
트랜잭션 흐름의 멀티 스레드 환경
1. 트랜잭션 시작: 트랜잭션 컨텍스트가 필요한 서비스 메소드가 호출되면 새 데이터베이스 연결이 생성됩니다. 이 연결은 setAutoCommit(false)로 트랜잭션을 시작합니다.
2. 트랜잭션 동기화 적용: 그 다음 TransactionSynchronizationManager를 사용하여 현재 스레드에 연결을 바인딩합니다. 이렇게 하면 동일 스레드 내의 후속 데이터 액세스 작업이 동일한 트랜잭션에 참여할 수 있습니다.
3. 비즈니스 로직 실행: 서비스 레이어는 DAO 레이어와 상호 작용하며, DAO는 데이터베이스 작업에 JdbcTemplate을 사용합니다. JdbcTemplate은 서비스 레이어에 의해 시작된 트랜잭션에, 스레드에 바인딩된 데이터베이스 연결을 검색하여 원활하게 참여할 수 있습니다.
4. 커밋 또는 롤백: 모든 작업이 성공적으로 수행되면 트랜잭션이 커밋됩니다. 예외가 발생하면 작업의 원자성을 유지하기 위해 트랜잭션이 롤백됩니다.
5. 리소스 정리: 트랜잭션이 완료되거나 롤백된 후에는 연결이 해제되고 모든 리소스가 정리됩니다. TransactionSynchronizationManager는 스레드에 바인딩된 리소스가 제대로 정리되어 메모리 누수나 연결 고갈 문제를 방지합니다.
TransactionSynchronizationManager와 JdbcTemplate 사용의 장점
토비의 스프링 내용중에,,,
아래 upgradeLevel 메소드는 서비스 계층의 UserService 클래스의 인스턴스 메소드입니다.
protected void upgradeLevel(User user) {
user.upgradeLevel();
userDao.update(user);
}
이 upgradeLevel 메소드에서 호출하는 DAO 계층의 userDao.update 메소드는 UserDaoJdbc클래스가 구현하였습니다.
@Override
public void update(User user) {
this.jdbcTemplate.update(
"update users set "
+ "name = ?, "
+ "password = ?, "
+ "level = ?, "
+ "login = ?, "
+ "recommend = ? "
+ "where id = ? ",
user.getName(),
user.getPassword(),
user.getLevel().intValue(),
user.getLogin(),
user.getRecommend(),
user.getId());
}
위 DAO 계층의 인스턴스 메소드는 JdbcTemplate의 인스턴스 메소드를 호출하고 있습니다.
JdbcTemplate은 내부적으로 데이터베이스와의 커넥션을 성립하고 데이터베이스 트랜잭션을 수행합니다.
즉 위 메소드의 jdbcTemplate.update는 단일의 데이터베이스 트랜잭션을 수행합니다.
public void upgradeLevels() {
List<User> users = userDao.getAll();
for(User user : users) {
if (canUpgradeLevel(user)) {
upgradeLevel(user);
}
}
}
위와 같은 호출 문맥 구조에서 서비스 계층의 upgradeLevels 메소드를 특정 단위 테스트에서 호출한다면, 문제가 발생할 수 있습니다.
@Test
public void upgradeAllOrNothing() throws Exception {
UserService testUserService =
new TestUserService(users.get(3).getId());
testUserService.setUserDao(this.userDao);
testUserService.setDataSource(this.dataSource);
userDao.deleteAll();
for(User user : users) userDao.add(user);
try {
testUserService.upgradeLevels();
fail("TestUserServiceException expected");
}
catch(TestUserServiceException e) {
int a = 0;
a = 10;
}
checkLevelUpgraded(users.get(1), false);
}
static class TestUserService extends UserService {
private String id;
private TestUserService(String id) {
this.id = id;
}
protected void upgradeLevel(User user) {
if (user.getId().equals(this.id))
throw new TestUserServiceException();
super.upgradeLevel(user);
}
}
static class TestUserServiceException extends RuntimeException {
}
위 TestUserService(Nested Static) 클래스는 특정 id 값이 이 클래스의 인스턴스 메소드인 upgradeLevel 메소드에게 아규먼트로 함께 호출된다면, 고의적으로 TestUserServiceException 예외를 발생시킵니다. 이는 현재 서비스 계층에서 데이터베이스 Rollback을 처리 유무를 확인하기 위한 목적입니다.
원활한 트랜잭션 참여: DAO 메소드는 연결이나 트랜잭션 경계를 처리할 필요가 없습니다. 데이터 액세스 로직에만 집중할 수 있으며, 트랜잭션은 투명하게 관리됩니다.
- 단순화된 코드: 서비스 메소드는 트랜잭션 관리 코드로 어수선해지지 않습니다. 관심사의 분리가 유지됩니다.
- 일관된 테스팅: DAO는 트랜잭션 설정이나 정리에 대해 걱정할 필요 없이 테스트될 수 있습니다. `JdbcTemplate`은 테스트를 위해 비트랜잭션 모드에서 독립적으로 운영될 수 있습니다.
- 확장성: 이 모델은 각 스레드가 다른 스레드와 간섭 없이 자체 트랜잭션을 관리할 수 있는 멀티 스레드 환경을 지원합니다.
트랜잭션 동기화 구현
트랜잭션 동기화는 일반적으로 서비스 레이어에서 구현되며, 이 레이어는 트랜잭션 경계를 제어합니다. 이를 통해 서비스 레이어는 트랜잭션 시작과 종료를 지시할 수 있으며, 데이터 액세스 객체(DAO)는 트랜잭션 컨텍스트를 인식하지 못합니다. 명확성을 위해 가상 코드 예제를 제공합니다
public class UserService {
UserDao userDao;
DataSource dataSource;
public void upgradeLevels() {
TransactionSynchronizationManager.initSynchronization();
Connection connection = DataSourceUtils.getConnection(dataSource);
connection.setAutoCommit(false);
try {
// userDao를 사용하는 비즈니스 로직
connection.commit();
} catch (Exception e) {
connection.rollback();
throw e;
} finally {
DataSourceUtils.releaseConnection(connection, dataSource);
TransactionSynchronizationManager.clearSynchronization();
}
}
}
Spring Framework의 이러한 트랜잭션 동기화 접근 방식은 멀티 스레드 서버 애플리케이션에서 트랜잭션을 관리하는 강력하고 스레드-세이프한 메커니즘을 제공하여, 다수의 데이터베이스 작업에 걸쳐 데이터 일관성과 무결성을 보장합니다.
1. 트랜잭션 초기화: UserService는 데이터베이스 작업을 위한 Connection 객체를 생성합니다. 이 단계는 모든 트랜잭션 처리의 시작점입니다.
2. 트랜잭션 동기화: 생성된 Connection은 트랜잭션 동기화 저장소에 보관됩니다. 이 저장소는 트랜잭션 처리 동안 여러 서비스에서 동일한 Connection을 사용할 수 있도록 합니다. 이후 setAutoCommit(false)를 호출하여 트랜잭션을 시작합니다.
3. 첫 번째 데이터베이스 작업: UserService는 첫 번째 update() 메소드를 호출합니다. 이 메소드는 내부적으로 JdbcTemplate을 사용하여 데이터베이스 작업을 수행합니다.
4. Connection 재활용: JdbcTemplate은 트랜잭션 동기화 저장소에서 현재 트랜잭션을 위해 시작된 Connection 객체를 재활용합니다. 이 단계는 시스템의 효율성과 일관성을 보장합니다.
5. 첫 번째 작업 완료: 첫 번째 SQL 작업을 성공적으로 완료한 후, JdbcTemplate은 Connection을 닫지 않고 트랜잭션 동기화 저장소에 유지합니다. 이로써 트랜잭션은 계속 진행 상태에 있습니다.
6. 추가 작업 실행: 이후 userDao.update() 같은 추가 데이터베이스 작업이 호출됩니다. 이 작업들은 모두 동기화된 Connection을 재활용하여 실행됩니다.
7. 트랜잭션 완료: 모든 데이터베이스 작업이 성공적으로 완료되면, Connection의 commit() 메소드를 호출하여 트랜잭션을 완료합니다. 이 단계에서 모든 변경 사항이 데이터베이스에 반영됩니다.
8. 자원 정리: 마지막으로, 사용된 Connection을 닫고 트랜잭션 동기화 저장소에서 제거합니다. 이는 시스템 리소스를 정리하고 다음 트랜잭션을 위한 준비를 마치는 중요한 단계입니다.
이러한 과정은 데이터베이스 작업의 정확성과 무결성을 유지하면서, 동시에 효율적인 리소스 관리를 가능하게 합니다. 특히 대규모 시스템에서 트랜잭션 동기화는 복잡한 데이터베이스 연산을 안정적으로 관리하는 데 필수적인 요소입니다.
스프링 트랜잭션 추상화 기술
스프링은 트랜잭션 기술의 공통점을 담은 트랜잭션 추상화 기술을 제공하고 있습니다.
※ Spring의 명령형[imperative] 트랜잭션 인프라
Spring의 명령형 트랜잭션 인프라는 Spring 프레임워크 내에서 트랜잭션 관리를 위해 사용되는 메커니즘을 말합니다. 이 인프라는 데이터베이스 트랜잭션을 관리하고, 애플리케이션에서 트랜잭션의 일관성과 무결성을 유지하는 데 필수적입니다. 명령형 트랜잭션 관리는 개발자가 트랜잭션의 경계를 명시적으로 지정하고 관리해야 하는 방식을 의미합니다.
Spring에서는 주로 두 가지 방법으로 트랜잭션을 관리합니다:
1. 선언적[Declarative] 트랜잭션 관리: 이 방법은 AOP(Aspect-Oriented Programming)를 기반으로 하며, 트랜잭션 관리 코드를 비즈니스 로직에서 분리합니다. 개발자는 @Transactional 어노테이션을 사용하여 트랜잭션의 경계를 선언적으로 지정할 수 있습니다. 이 방식은 코드의 가독성과 유지 보수성을 향상시키며, 트랜잭션 관리 로직을 분리하여 코드의 중복을 줄일 수 있습니다.
2. 명령형 트랜잭션 관리: 이 방법은 PlatformTransactionManager 인터페이스를 사용하여 트랜잭션을 프로그래밍 방식으로 관리합니다. 개발자는 TransactionTemplate을 사용하거나, PlatformTransactionManager를 직접 사용하여 트랜잭션의 시작, 커밋, 롤백 등을 명시적으로 제어합니다. 명령형 트랜잭션 관리는 더 세밀한 트랜잭션 제어가 필요한 경우에 유용합니다.
Spring의 명령형 트랜잭션 인프라는 유연하고 확장 가능하며, 다양한 유형의 트랜잭션 관리자(예: JDBC, JPA, JMS, JTA 등)를 지원합니다. 이를 통해 개발자는 애플리케이션의 특정 요구 사항에 맞게 트랜잭션 관리 전략을 선택하고 구현할 수 있습니다.
※ @Transactional 어노테이션을 사용하여 트랜잭션의 경계를 선언적으로 지정한다는 의미는?
@Transactional 어노테이션을 사용하여 트랜잭션의 경계를 선언적으로 지정한다는 것은, 개발자가 프로그램 코드 내에서 명시적으로 트랜잭션을 관리하는 코드를 작성하지 않고, 대신 해당 어노테이션을 메서드나 클래스에 적용함으로써 트랜잭션을 자동으로 관리하도록 Spring 프레임워크에 위임한다는 의미입니다.
이 방식에서는 다음과 같은 특징들이 있습니다:
1. 어노테이션 기반: @Transactional 어노테이션을 메서드나 클래스에 적용하여, 해당 메서드나 클래스 내의 모든 메서드가 트랜잭션 범위 내에서 실행되도록 합니다.
2. 자동 트랜잭션 관리: Spring은 이 어노테이션이 적용된 메서드가 호출될 때 자동으로 트랜잭션을 시작하고, 메서드 실행이 정상적으로 완료되면 트랜잭션을 커밋합니다. 예외가 발생하면 자동으로 롤백합니다.
3. 선언적 접근: 코드 내에서 트랜잭션 관리 로직(예: try-catch 블록, 명시적인 트랜잭션 시작 및 종료)을 작성할 필요가 없습니다. 대신, 어노테이션을 통해 트랜잭션 관리를 선언적으로 처리합니다.
4. 유연성과 가독성: 이 방식은 코드의 가독성을 높이고, 트랜잭션 관리 로직을 비즈니스 로직으로부터 분리하여 유지보수를 용이하게 합니다. 또한, @Transactional 어노테이션의 속성을 통해 트랜잭션의 성질(전파 방식, 격리 수준, 읽기 전용, 타임아웃 등)을 조정할 수 있습니다.
이처럼 @Transactional을 사용하는 선언적 트랜잭션 관리는 Spring에서 트랜잭션을 보다 효율적이고 우아하게 다룰 수 있게 해주는 강력한 기능입니다.
스프링의 트랜잭션 추상화 기술을 이용하면 애플리케이션에서 직접 특정 트랜잭션 기술의 API를 이용하지 않고도,
일관된 방식으로 트랜잭션을 제어하는 트랜잭션 경계설정 작업이 가능합니다.
다음은 트랜잭션 추상화 계층구조입니다.
※위 그림에서 DataSourceTxManager는 DataSourceTransactionManager를 의미.
※ DataSourceTransactionManager
DataSourceTransactionManager는 Spring 프레임워크에서 제공하는 트랜잭션 관리자의 한 종류로, 주로 JDBC(Java Database Connectivity) 기반의 데이터베이스 트랜잭션을 관리하는 데 사용됩니다. 이 클래스는 PlatformTransactionManager 인터페이스를 구현하며, JDBC 트랜잭션 관리를 위해 특별히 설계되었습니다. DataSourceTransactionManager의 주요 특징과 사용 방법은 다음과 같습니다:
1. JDBC 트랜잭션 관리: DataSourceTransactionManager는 JDBC 기술을 사용하여 데이터베이스와의 트랜잭션을 관리합니다. 이는 데이터베이스 연결을 관리하고, SQL 문을 실행하는 JDBC 코드에 트랜잭션 관리 기능을 추가합니다.
2. DataSource 연동: 이 클래스는 DataSource 객체와 연동하여 작동합니다. DataSource는 데이터베이스 연결을 제공하는 객체로, DataSourceTransactionManager는 이를 사용하여 데이터베이스 연결을 얻고 트랜잭션을 시작, 커밋 또는 롤백합니다.
3. 스프링 통합: Spring 환경에서 DataSourceTransactionManager를 사용하면, @Transactional 어노테이션을 통한 선언적 트랜잭션 관리가 가능합니다. 이를 통해 개발자는 복잡한 트랜잭션 관리 코드를 작성하지 않고도 트랜잭션을 관리할 수 있습니다.
4. 프로그래밍 방식의 트랜잭션 관리: 개발자는 TransactionTemplate 또는 PlatformTransactionManager API를 직접 사용하여 프로그래밍 방식으로 트랜잭션을 관리할 수도 있습니다. 이는 보다 세밀한 트랜잭션 제어가 필요한 경우에 유용합니다.
5. 일관성 및 격리 수준 관리: DataSourceTransactionManager를 사용하면 트랜잭션의 일관성과 격리 수준을 설정하고 관리할 수 있습니다. 이는 데이터베이스 트랜잭션의 무결성을 유지하는 데 중요한 요소입니다.
DataSourceTransactionManager는 JDBC를 사용하는 애플리케이션에서 트랜잭션 관리를 간소화하고, Spring 프레임워크와의 통합을 통해 트랜잭션 관리를 보다 쉽고 효율적으로 만들어줍니다.
JDBC DataSource는 Java 데이터베이스 연결(JDBC)에서 사용되는 개념으로, 데이터베이스에 연결하기 위한 정보와 방법을 캡슐화한 객체입니다. 이것은 데이터베이스 서버에 연결을 수행하는데 필요한 모든 세부 정보(예: URL, 사용자 이름, 비밀번호)를 포함하며, 애플리케이션에서 데이터베이스 연결을 얻기 위한 표준 방법을 제공합니다. DataSource를 사용하면 데이터베이스 연결을 더 효율적으로 관리하고, 연결 풀링과 같은 고급 기능을 이용할 수 있습니다, 이는 애플리케이션의 성능과 확장성을 향상시킵니다.
※ JTA는 Java Transaction API의 약자로, 애플리케이션이 여러 네트워크 리소스를 아우르는 분산 트랜잭션을 수행할 수 있게 해주는 Java의 엔터프라이즈 수준 API입니다. JTA는 Java EE 플랫폼의 일부로, 하나의 트랜잭션 내에서 두 개 이상의 네트워크 리소스에서 데이터를 액세스하고 업데이트할 수 있는 방법을 제공하여, 모든 관련 시스템 간의 데이터 무결성과 일관성을 보장합니다.
JTA에서 트랜잭션 관리자는 모든 관련 리소스 간의 트랜잭션을 조정하고 최종적으로 트랜잭션을 커밋하거나 롤백할 책임이 있습니다. 이러한 조정은 일반적으로 두 단계 커밋 프로토콜을 통해 수행되며, 모든 리소스가 트랜잭션을 성공적으로 커밋하거나 트랜잭션 중 문제가 발생할 경우 이전 상태로 롤백됩니다.
JTA는 애플리케이션, 애플리케이션 서버 및 리소스 관리자와 트랜잭션 관리자 간의 표준 인터페이스를 정의합니다. 핵심 인터페이스는 UserTransaction, TransactionManager, XAResource입니다. 이 인터페이스들은 분산 트랜잭션의 다양한 구성 요소들이 효과적으로 통신하고 트랜잭션 경계를 관리할 수 있도록 합니다.
UserTransaction 인터페이스는 애플리케이션이 트랜잭션 경계를 프로그래밍 방식으로 제어할 수 있도록 해주며, TransactionManager 인터페이스는 애플리케이션 서버가 애플리케이션을 대신하여 트랜잭션을 제어할 수 있게 합니다. XAResource 인터페이스는 X/Open CAE 명세서(분산 트랜잭션 처리: XA 명세서)의 Java 매핑으로, 리소스 관리자가 분산 트랜잭션에 참여할 수 있게 해줍니다.
분산 트랜잭션 관리의 복잡한 측면을 JTA가 처리하긴 하지만, 일반적으로 애플리케이션 개발자는 이러한 세부 사항을 신경 쓸 필요가 없습니다. 대신, 애플리케이션 서버와 트랜잭션 관리자가 제공하는 인프라에 의존하여 이러한 측면을 투명하게 관리합니다.
JTA에 대한 보다 심층적인 정보는 [Progress](https://www.progress.com)와 [Spring 문서](https://spring.io)에서 참조할 수 있습니다.
PlatformTransactionManager
PlatformTransactionManager은 Spring Framework에서 제공하는 인터페이스로, 일관된 방식으로 트랜잭션을 관리할 수 있게 합니다. 즉, 이 인터페이스를 이용하면 데이터베이스 또는 다른 트랜잭션 리소스에 대한 트랜잭션 관리를 직접적으로 알아서 할 수 있습니다.
예를 들어 JDBC의 로컬 트랜잭션을 이용한다면, PlatformTransactionManager 인터페이스를 구현한 DataSourceTransactionManager 클래스를 사용하면 됩니다. 이용할 데이터베이스의 DataSource(예를 들어SimpleDriverDataSource, HikariDataSource)를 DataSourceTransactionManager 클래스의 생성자 파라미터로 전달하여 DataSourceTransactionManager 클래스 오브젝트를 생성합니다.
PlatformTransactionManager는 주로 세 가지 메소드를 정의하고 있습니다:
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException: 이 메소드는 트랜잭션을 시작합니다. 인자로 받는 TransactionDefinition은 트랜잭션의 속성(예: 전파 설정, 격리 수준 등)을 정의하는 객체입니다.
- void commit(TransactionStatus status) throws TransactionException: 트랜잭션이 성공적으로 완료되면 이 메소드를 호출하여 트랜잭션을 커밋합니다.
- void rollback(TransactionStatus status) throws TransactionException: 만약 중간에 문제가 발생하면, 이 메소드를 호출하여 트랜잭션을 롤백합니다.
이 인터페이스는 JDBC, JTA, Hibernate, JPA, MyBatis 등 다양한 데이터베이스 접근 기술을 위한 구현체를 제공합니다. 이런 구현체들을 이용하면, 다양한 데이터베이스 환경에서 일관된 트랜잭션 관리 코드를 작성할 수 있습니다.
Commit
※ Commit
데이터베이스에서 커밋(commit)은 트랜잭션이 성공적으로 완료되었음을 나타내는 작업입니다. 이 과정은 트랜잭션에 의해 수행된 모든 데이터 변경사항들이 데이터베이스에 영구적으로 반영되어야 함을 의미합니다. 커밋의 주요 특징과 중요성은 다음과 같습니다:
1. 영구성 부여: 커밋은 트랜잭션에 의해 이루어진 모든 변경사항이 데이터베이스에 영구적으로 저장됨을 보장합니다. 이는 트랜잭션의 지속성(Durability) 원칙과 관련이 있습니다.
2. 데이터 무결성 유지: 트랜잭션이 데이터베이스의 무결성 제약조건을 만족할 때, 커밋 작업을 통해 이러한 변경사항들이 안정적으로 데이터베이스에 반영됩니다.
3. 트랜잭션 완료의 표시: 커밋은 트랜잭션이 성공적으로 완료되었음을 나타냅니다. 즉, 모든 데이터 조작 언어(Data Manipulation Language, DML) 명령어들(INSERT, UPDATE, DELETE 등)이 성공적으로 실행되었음을 의미합니다.
4. 시스템 장애 대응: 커밋된 데이터는 시스템 장애가 발생해도 보존됩니다. 예를 들어, 시스템이 다운되더라도 커밋된 트랜잭션 데이터는 손실되지 않습니다.
5. 격리성 보장: 여러 트랜잭션이 동시에 실행될 때, 커밋은 해당 트랜잭션이 완료되었으며 다른 트랜잭션에 의해 보여지거나 접근될 수 있음을 나타냅니다.
커밋은 트랜잭션의 중요한 마지막 단계로, 이를 통해 데이터베이스의 일관성과 무결성이 유지됩니다. 트랜잭션 관리에서 커밋과 반대되는 개념은 롤백(Rollback)이며, 롤백은 트랜잭션이 실패했거나 중단되었을 때 모든 변경사항을 취소하는 작업을 의미합니다.
※ Rollback
롤백(Rollback)은 데이터베이스 관리 시스템에서 사용되는 용어로, 트랜잭션 과정 중 발생한 변경사항들을 취소하고, 트랜잭션 시작 전의 상태로 데이터베이스를 복원하는 과정을 의미합니다. 트랜잭션의 원자성(Atomicity)을 보장하기 위해 사용되며, 트랜잭션 중에 오류가 발생하거나 명시적으로 트랜잭션을 중단할 필요가 있을 때 이루어집니다. 롤백의 주요 특징은 다음과 같습니다:
1. 데이터 무결성 보호: 롤백은 데이터베이스의 무결성을 위협할 수 있는 잘못된 또는 불완전한 트랜잭션으로부터 데이터를 보호합니다.
2. 오류 및 예외 처리: 트랜잭션 실행 중 발생하는 예외 또는 오류 상황에서 롤백을 통해 데이터베이스를 안정적인 상태로 유지할 수 있습니다.
3. 트랜잭션의 원자성 유지: 롤백은 트랜잭션의 원자성을 유지하는 데 중요한 역할을 합니다. 즉, 트랜잭션의 모든 작업이 완전히 실행되거나 전혀 실행되지 않아야 함을 보장합니다.
4. 자동 또는 수동 실행: 일부 시스템에서는 오류가 감지될 때 자동으로 롤백을 실행할 수 있으며, 개발자는 필요에 따라 명시적으로 롤백을 실행할 수도 있습니다.
롤백은 데이터베이스 트랜잭션 관리의 핵심적인 부분으로, 트랜잭션 중 발생할 수 있는 다양한 문제에 대응하여 데이터의 일관성과 무결성을 유지하는 데 중요한 역할을 합니다.
※ Database Constraints
데이터베이스의 제약조건은 데이터베이스 테이블에 저장되는 데이터의 정확성, 무결성, 신뢰성을 보장하기 위해 설정된 규칙 또는 제한입니다. 이러한 제약조건들은 데이터베이스 관리 시스템(DBMS)에 의해 강제되며, 데이터가 테이블에 삽입되거나 변경될 때 적용됩니다. 주요 데이터베이스 제약조건 유형에는 다음과 같은 것들이 있습니다:
1. 개체 무결성(Entity Integrity): 이는 테이블의 각 행(레코드)이 고유하게 식별될 수 있어야 한다는 원칙을 나타냅니다. 이를 위해 기본 키(Primary Key)가 사용되며, 기본 키는 중복되거나 NULL 값이 될 수 없습니다.
2. 참조 무결성(Referential Integrity): 외래 키(Foreign Key)를 통해 다른 테이블의 행을 참조할 때, 해당 참조가 유효한지 보장하는 제약조건입니다. 외래 키 값은 참조하는 테이블의 기본 키 값과 일치하거나 NULL이어야 합니다.
3. 도메인 무결성(Domain Integrity): 이는 테이블의 각 필드(컬럼)가 정의된 도메인(유효한 값의 범위 또는 타입)에 속한 값만을 가질 수 있도록 보장하는 제약조건입니다. 예를 들어, 특정 필드가 날짜 타입이면 해당 필드에는 날짜 값만 저장될 수 있습니다.
4. 고유 제약조건(Unique Constraint): 특정 필드 또는 필드의 조합에 대해 중복되지 않는 값만을 가질 수 있도록 하는 제약조건입니다. 이는 기본 키와 유사하지만 NULL 값을 허용할 수 있습니다.
5. 체크 제약조건(Check Constraint): 특정 필드가 특정 조건을 만족해야 함을 명시하는 제약조건입니다. 예를 들어, 어떤 숫자 필드가 특정 범위의 값만을 가질 수 있도록 설정할 수 있습니다.
6. NOT NULL 제약조건: 이 제약조건은 특정 필드가 NULL 값을 가질 수 없음을 나타냅니다. 이는 필수 데이터 필드가 누락되지 않도록 보장합니다.
이러한 제약조건들은 데이터베이스 설계 및 구현 단계에서 중요하게 고려되며, 데이터의 품질과 무결성을 유지하는 데 필수적인 역할을 합니다. 데이터베이스 시스템은 이러한 제약조건들을 자동으로 강제하여, 잘못된 데이터 입력이나 데이터 손상을 방지합니다.
Spring의 @Transactional 어노테이션을 사용하면 이 PlatformTransactionManager를 자동으로 사용하여 트랜잭션을 관리할 수 있습니다. 이 어노테이션을 메소드나 클래스에 붙이면, 해당 메소드 혹은 클래스 내의 메소드가 실행될 때 자동으로 트랜잭션이 시작되고, 메소드 실행이 성공적으로 끝나면 트랜잭션이 커밋되며, 예외가 발생하면 롤백됩니다. 이렇게 하면 트랜잭션 관리 코드를 작성하지 않아도 트랜잭션 관리가 가능해집니다.
@Transactional
public int update(long id, final UserRequest u) {
Optional<User> oUser = userRepository.findById(id);
if(!oUser.isPresent())
return 0;
User user = oUser.get();
user.setBirthDate(u.getBirthDate());
user.setEmail(u.getEmail());
user.setName(u.getName());
user.setPassword(u.getPassword());
user.setPhoneNumber(u.getPhoneNumber());
user.setSex(u.getSex());
user.setType(u.getType());
userRepository.save(user);
return 1;
}