이 기사에서는 예제를 통해 Spring의 트랜잭션 관리를 설명합니다. 참고할 수 있도록 모든 사람과 공유하세요. 구체적인 분석은 다음과 같습니다.
사업 소개:
트랜잭션 관리는 데이터 무결성과 일관성을 보장하기 위해 엔터프라이즈 애플리케이션 개발에 필수적인 기술입니다.
트랜잭션은 단일 작업 단위로 처리되는 일련의 작업입니다. 이 작업이 모두 작동하거나 모두 작동하지 않습니다.
트랜잭션의 4가지 주요 속성(ACID)
① 원자성(Atomicity): 거래실에서의 원자성 작업은 일련의 동작으로 구성됩니다. 트랜잭션의 원자성은 작업이 완전히 완료되거나 전혀 영향을 미치지 않도록 보장합니다. ② 일관성: 모든 트랜잭션 작업이 완료되면 트랜잭션이 커밋됩니다. 데이터와 리소스는 비즈니스 규칙을 충족하는 일관된 상태에 있습니다. ③ 격리성: 동일한 데이터를 동시에 처리하는 트랜잭션이 많을 수 있으므로 데이터 손상을 방지하기 위해 각 트랜잭션을 다른 트랜잭션과 격리해야 합니다. ④ 내구성: 트랜잭션이 완료되면 , 어떤 시스템 오류가 발생하더라도 결과는 영향을 받아서는 안 됩니다. 일반적으로 트랜잭션 결과는 영구 저장소에 기록됩니다.
Spring의 트랜잭션 관리
엔터프라이즈 애플리케이션 프레임워크로서 Spring은 다양한 트랜잭션 관리 API 위에 추상화 계층을 정의합니다. 애플리케이션 개발자는 Spring의 트랜잭션 관리 메커니즘을 사용하기 위해 기본 트랜잭션 관리 API를 이해할 필요가 없습니다.
Spring은 프로그램적 트랜잭션 관리와 선언적 트랜잭션 관리를 모두 지원합니다.
프로그래밍 방식 트랜잭션 관리: 트랜잭션 제출 및 롤백을 제어하려면 트랜잭션 관리 코드를 비즈니스 메서드에 포함하세요. 프로그래밍 방식 트랜잭션에서는 각 비즈니스 작업에 추가 트랜잭션 관리 코드가 포함되어야 합니다.
선언적 트랜잭션 관리: 대부분의 경우 프로그래밍 방식 트랜잭션 관리보다 사용하는 것이 더 좋습니다. 트랜잭션 관리 코드를 비즈니스 메소드와 분리하고 선언적 방식으로 트랜잭션 관리를 구현합니다. 교차 관심 사항인 트랜잭션 관리는 AOP 방법을 통해 모듈화될 수 있습니다. Spring은 Spring AOP 프레임워크를 통해 선언적 트랜잭션 관리를 지원합니다.
Spring 트랜잭션의 전파 속성:
다른 트랜잭션 메서드에서 트랜잭션 메서드를 호출하는 경우 트랜잭션이 전파되는 방법을 지정해야 합니다. 예를 들어 메서드는 기존 트랜잭션에서 계속 실행될 수도 있고, 새 트랜잭션을 시작하고 자체 트랜잭션에서 실행될 수도 있습니다.
트랜잭션의 전파 동작은 전파 속성으로 지정할 수 있습니다. Spring은 7가지 통신 동작을 정의합니다.
의사소통 행동 | 의미 |
PROPAGATION_MANDATORY | 메소드가 트랜잭션 내에서 실행되어야 함을 나타냅니다. 현재 트랜잭션이 없으면 예외가 발생합니다. |
PROPAGATION_NESTED | 현재 트랜잭션이 존재하는 경우 이 메서드가 중첩된 트랜잭션에서 실행됨을 나타냅니다. 중첩된 트랜잭션은 현재 트랜잭션과 독립적으로 커밋되거나 롤백될 수 있습니다. 현재 트랜잭션이 존재하지 않는 경우 동작은 PROPAGATION_REQUIRED와 동일합니다. 이 전파 동작에 대한 각 제조업체의 지원은 다릅니다. 리소스 관리자의 설명서를 참조하여 중첩 트랜잭션을 지원하는지 확인할 수 있습니다. |
전파_절대 안함 | 현재 메서드가 트랜잭션 컨텍스트에서 실행되어서는 안 됨을 나타냅니다. 현재 실행 중인 트랜잭션이 있으면 예외가 발생합니다. |
전파_NOT_지원됨 | 이 메소드가 트랜잭션 내에서 실행되어서는 안 됨을 나타냅니다. 현재 트랜잭션이 존재하는 경우 이 메소드가 실행되는 동안 일시중단됩니다. JTATransactionManager를 사용하는 경우 TransactionManager에 액세스해야 합니다. |
전파_필수 | 현재 메서드가 트랜잭션 내에서 실행되어야 함을 나타냅니다. 현재 트랜잭션이 존재하는 경우 메서드는 해당 트랜잭션에서 실행됩니다. 그렇지 않으면 새로운 거래가 시작됩니다 |
PROPAGATION_REQUIRED_NEW | 현재 메서드가 자체 트랜잭션에서 실행되어야 함을 나타냅니다. 새로운 거래가 시작됩니다. 현재 트랜잭션이 존재하는 경우 이 메서드를 실행하는 동안 해당 트랜잭션이 일시 중지됩니다. JTATransactionManager를 사용하는 경우 TransactionManager에 액세스해야 합니다. |
PROPAGATION_지원 | 현재 메서드에 트랜잭션 컨텍스트가 필요하지 않지만 현재 트랜잭션이 있는 경우 메서드가 이 트랜잭션에서 실행됨을 나타냅니다. |
그 중 PROPAGATION_REQUIRED는 기본 전파 속성입니다.
동시 트랜잭션으로 인한 문제
동일한 애플리케이션이나 다른 애플리케이션의 여러 트랜잭션이 동일한 데이터 세트에서 동시에 실행될 때 예상치 못한 많은 문제가 발생할 수 있습니다.
동시 트랜잭션으로 인해 발생하는 문제는 다음 세 가지 범주로 나눌 수 있습니다.
① 더티 읽기(Dirty Read): 한 트랜잭션이 다른 트랜잭션에 의해 다시 작성되었지만 아직 커밋되지 않은 데이터를 읽는 경우 더티 읽기가 발생합니다. 다시 쓰기가 나중에 롤백되면 첫 번째 트랜잭션에서 얻은 데이터가 유효하지 않게 됩니다.
② 비반복 읽기(Non-repeatable Read): 트랜잭션이 동일한 쿼리를 두 번 이상 실행했지만 매번 다른 데이터를 받는 경우를 반복하지 않는 읽기입니다. 이는 일반적으로 다른 동시 트랜잭션이 쿼리 간 데이터를 업데이트했기 때문입니다.
③ 환상 읽기: 환상 읽기는 비반복 읽기와 유사합니다. 이는 하나의 트랜잭션(T1)이 몇 행의 데이터를 읽은 후 다른 동시 트랜잭션(T2)이 일부 데이터를 삽입할 때 발생합니다. 후속 쿼리에서 첫 번째 트랜잭션(T1)은 원래 존재하지 않았던 레코드가 더 있음을 발견합니다.
코드 예
첫 번째는 데이터베이스 테이블입니다.
book(isbn, book_name, 가격), account(사용자명, 잔액), book_stock(isbn, 재고) 포함
그런 다음 사용된 클래스는 다음과 같습니다.
BookShopDao
다음과 같이 코드 코드를 복사합니다.
패키지 com.yl.spring.tx;
공개 인터페이스 BookShopDao {
//책 번호를 기준으로 책의 단가를 구합니다.
public int findBookPriceByIsbn(String isbn);
//책 번호에 해당하는 인벤토리가 -1이 되도록 책의 인벤토리를 업데이트합니다.
공개 무효 updateBookStock(String isbn);
//사용자 계정 잔액 업데이트: 사용자 이름의 잔액 가격을 만듭니다.
public void updateUserAccount(String 사용자 이름, int 가격);
}
책가게DaoImpl
다음과 같이 코드 코드를 복사합니다.
패키지 com.yl.spring.tx;
import org.springframework.beans.factory.annotation.Autowired;
org.springframework.jdbc.core.JdbcTemplate 가져오기;
org.springframework.stereotype.Repository 가져오기;
@Repository("bookShopDao")
공개 클래스 BookShopDaoImpl은 BookShopDao를 구현합니다.
@Autowired
개인 JdbcTemplate JdbcTemplate;
@보수
공개 int findBookPriceByIsbn(String isbn) {
String sql = "isbn = ?";
return JdbcTemplate.queryForObject(sql, Integer.class, isbn);
}
@보수
공공 무효 updateBookStock(문자열 isbn) {
//책 재고가 충분한지 확인하고, 충분하지 않으면 예외를 발생시킵니다.
String sql2 = "book_stock에서 주식을 선택하세요. isbn = ?";
int stock = JdbcTemplate.queryForObject(sql2, Integer.class, isbn);
if (재고 == 0) {
throw new BookStockException("재고가 부족합니다!");
}
String sql = "book_stock SET 재고 업데이트 = 재고 - 1 isbn = ?";
JdbcTemplate.update(sql, isbn);
}
@보수
공개 무효 updateUserAccount(문자열 사용자 이름, 정수 가격) {
//잔액이 부족한지 확인하고, 부족한 경우 예외 발생
String sql2 = "WHERE 사용자 이름 = ?";
int Balance = JdbcTemplate.queryForObject(sql2, Integer.class, 사용자 이름);
if (잔액 < 가격) {
throw new UserAccountException("잔고가 부족합니다!");
}
String sql = "계정 설정 잔액 업데이트 = 잔액 - ? WHERE 사용자 이름 = ?";
JdbcTemplate.update(sql, 가격, 사용자 이름);
}
}
BookShop서비스
다음과 같이 코드를 복사합니다. package com.yl.spring.tx;
공개 인터페이스 BookShopService {
공개 무효 구매(문자열 사용자 이름, 문자열 isbn);
}
BookShopServiceImpl
다음과 같이 코드 코드를 복사합니다.
패키지 com.yl.spring.tx;
import org.springframework.beans.factory.annotation.Autowired;
org.springframework.stereotype.Service 가져오기;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service("bookShopService")
공개 클래스 BookShopServiceImpl은 BookShopService를 구현합니다.
@Autowired
개인 BookShopDao bookShopDao;
/**
* 1. 거래 주석 추가
* 전파를 사용하여 트랜잭션의 전파 동작, 즉 현재 트랜잭션 메소드가 다른 트랜잭션 메소드에 의해 호출될 때 트랜잭션을 사용하는 방법을 지정합니다.
* 기본값은 REQUIRED입니다. 이는 메소드를 호출하는 트랜잭션을 사용함을 의미합니다.
* REQUIRES_NEW : 자신의 트랜잭션을 사용하고, 호출된 트랜잭션 메소드의 트랜잭션을 정지합니다.
*
* 2. 격리를 사용하여 트랜잭션의 격리 수준을 지정합니다. 가장 일반적으로 사용되는 값은 READ_COMMITTED입니다.
* 3. 기본적으로 Spring의 선언적 트랜잭션은 모든 런타임 예외를 롤백하며 해당 속성을 통해 설정할 수도 있습니다. 일반적으로 기본값이면 충분합니다.
* 4. readOnly를 사용하여 트랜잭션이 읽기 전용인지 여부를 지정합니다. 이 트랜잭션은 데이터를 읽기만 하고 데이터를 업데이트하지는 않음을 나타냅니다. 이는 데이터베이스 엔진이 트랜잭션을 최적화하는 데 도움이 될 수 있습니다. 실제로 데이터베이스를 읽기만 하는 메소드라면 readOnly=true로 설정해야 합니다.
* 5. timeOut을 사용하여 롤백을 강제하기 전에 트랜잭션이 소요될 수 있는 시간을 지정합니다.
*/
@Transactional(전파=전파.REQUIRES_NEW,
격리=격리.READ_COMMITTED,
noRollbackFor={UserAccountException.class},
readOnly=true, 시간 제한=3)
@보수
공개 무효 구매(문자열 사용자 이름, 문자열 isbn) {
//1. 책의 단가를 구합니다.
정수 가격 = bookShopDao.findBookPriceByIsbn(isbn);
//2. 도서 인벤토리 업데이트
bookShopDao.updateBookStock(isbn);
//3. 사용자 잔액 업데이트
bookShopDao.updateUserAccount(사용자 이름, 가격);;
}
}
BookStock예외
다음과 같이 코드 코드를 복사합니다.
패키지 com.yl.spring.tx;
공용 클래스 BookStockException은 RuntimeException을 확장합니다.
/**
*
*/
개인 정적 최종 긴 serialVersionUID = 1L;
공개 BookStockException() {
감독자();
// TODO 자동 생성 생성자 스텁
}
public BookStockException(String arg0, Throwable arg1, boolean arg2,
부울 인수3) {
슈퍼(arg0, arg1, arg2, arg3);
// TODO 자동 생성 생성자 스텁
}
public BookStockException(String arg0, Throwable arg1) {
슈퍼(arg0, arg1);
// TODO 자동 생성 생성자 스텁
}
공개 BookStockException(문자열 arg0) {
슈퍼(arg0);
// TODO 자동 생성 생성자 스텁
}
공개 BookStockException(Throwable arg0) {
슈퍼(arg0);
// TODO 자동 생성 생성자 스텁
}
}
사용자 계정 예외
다음과 같이 코드 코드를 복사합니다.
패키지 com.yl.spring.tx;
공용 클래스 UserAccountException은 RuntimeException을 확장합니다.
/**
*
*/
개인 정적 최종 긴 serialVersionUID = 1L;
공개 UserAccountException() {
감독자();
// TODO 자동 생성 생성자 스텁
}
공용 UserAccountException(문자열 arg0, Throwable arg1, 부울 arg2,
부울 인수3) {
슈퍼(arg0, arg1, arg2, arg3);
// TODO 자동 생성 생성자 스텁
}
공개 UserAccountException(String arg0, Throwable arg1) {
슈퍼(arg0, arg1);
// TODO 자동 생성 생성자 스텁
}
공개 UserAccountException(String arg0) {
슈퍼(arg0);
// TODO 자동 생성 생성자 스텁
}
공개 UserAccountException(Throwable arg0) {
슈퍼(arg0);
// TODO 자동 생성 생성자 스텁
}
}
출납원
다음과 같이 코드 코드를 복사합니다.
패키지 com.yl.spring.tx;
java.util.List 가져오기;
공개 인터페이스 출납원 {
public void checkout(String 사용자 이름, List<String>isbns);
}
계산원Impl. CashierImpl.checkout 및 bookShopService.purchase는 거래 전파 동작을 공동으로 테스트합니다.
다음과 같이 코드 코드를 복사합니다.
패키지 com.yl.spring.tx;
java.util.List 가져오기;
import org.springframework.beans.factory.annotation.Autowired;
org.springframework.stereotype.Service 가져오기;
import org.springframework.transaction.annotation.Transactional;
@Service("계산원")
공개 클래스 CashierImpl은 Cashier를 구현합니다.
@Autowired
개인 BookShopService bookShopService;
@트랜잭션
@보수
public void checkout(String 사용자 이름, List<String> isbns) {
for(문자열 isbn : isbns) {
bookShopService.purchase(사용자 이름, isbn);
}
}
}
테스트 클래스:
다음과 같이 코드 코드를 복사합니다.
패키지 com.yl.spring.tx;
import java.util.Arrays;
org.junit.Test 가져오기;
org.springframework.context.ApplicationContext 가져오기;
org.springframework.context.support.ClassPathXmlApplicationContext 가져오기;
공개 클래스 SpringTransitionTest {
개인 ApplicationContext ctx = null;
개인 BookShopDao bookShopDao = null;
개인 BookShopService bookShopService = null;
개인 계산원 계산원 = null;
{
ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
bookShopDao = ctx.getBean(BookShopDao.class);
bookShopService = ctx.getBean(BookShopService.class);
출납원 = ctx.getBean(Cashier.class);
}
@시험
공공 무효 testBookShopDaoFindPriceByIsbn() {
System.out.println(bookShopDao.findBookPriceByIsbn("1001"));
}
@시험
공공 무효 testBookShopDaoUpdateBookStock(){
bookShopDao.updateBookStock("1001");
}
@시험
공공 무효 testBookShopDaoUpdateUserAccount(){
bookShopDao.updateUserAccount("AA", 100);
}
@시험
공공 무효 testBookShopService(){
bookShopService.purchase("AA", "1001");
}
@시험
공개 무효 testTransactionPropagation(){
cashier.checkout("AA", Arrays.asList("1001", "1002"));
}
}
이 글이 모든 사람의 Spring 프로그래밍 설계에 도움이 되기를 바랍니다.