일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 마이페이지
- ui탬플릿
- 빌드 및 배포
- userManagement
- 파생상품평가
- 달력 라이브러리
- Typesciprt
- jsonwebtoken
- MRC
- mypage
- 회원가입로직
- 밸류즈 홈페이지
- 스프링시큐리티
- 이미지 업로드
- 배포
- RCPS
- 관리자페이지
- Styled Components
- register
- 공통메서드
- 캘린더 라이브러리
- stock option
- Ajax
- react
- 로그인 로직
- Token
- 인증처리
- 로그인
- 밸류즈
- Update
- Today
- Total
I T H
[스프링프로젝트연습 15] 스프링 프로젝트 구현 - 트랜잭션 처리 본문
앞서 진행한 CRUD 작업에서는 트랜잭션을 적용하지 않은 상황으로 해당 문서에서는 트랜잭션 적용을 위해 필요한 라이브러리 주입 및 트랜잭션 처리, 기능 테스트를 진행하고자 한다.
- 트랜잭션 : 데이터베이스의 상태를 변화시키기 해서 수행하는 작업의 단위를 뜻한다. 유사한 시스템에서 상호작용의 단위이다. 특징에는 원자성, 일관성, 독립성, 지속성이 있다.
- 커밋(Commit) : 하나의 트랜잭션이 성공적으로 끝났을 경우 수행이 끝났음을 알려주기 위한 연산으로 데이터베이스의 데이터를 입력하거나 수정 등의 작업이 실제로 반영되는 상태를 의미한다.
- 롤백(Rollback) : 트랜잭션 처리 과정 중 오류 및 예외로 인해 수행이 완료되지 않은 경우 해당 일련의 과정을 취소시키기 위한 연산으로 입력 및 수정 등의 작업 과정이 실제 반영되지 않는다.
- AOP : AOP는 Aspect Oriented Programming의 약자로 관점 지향 프로그래밍이라고 불린다. 관점 지향은 쉽게 말해 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화 하겠다는 것이다. 여기서 모듈화란 어떤 공통된 로직이나 기능을 하나의 단위로 묶는 것을 말한다.
- 기능 테스트의 경우 서비스 단에서 1개의 정상 쿼리를 통해 데이터를 insert 한 후 이어서 오류 발생 쿼리를 통해 데이터를 insert 한 경우 트랜잭션이 적용된 경우에는 앞서 insert 한 데이터가 롤백 되는 것을 확인하고, 트랜잭션이 적용되지 않은 경우에는 앞서 insert 한 데이터가 데이터베이스 테이블에 반영되는 것을 확인한다.
[ pom.xml 파일 수정 ]
- 이미 3장에서 추가한 경우라면 진행하지 않아도 된다.
- 트랜잭션 처리 및 AOP 적용을 위해 사용되는 라이브러리에 대한 의존성을 주입한다.
- 필요한 라이브러리는 아래 그림과 같다. (2개)
- 데이터베이스 연동을 위해 의존성 주입한 경우 해당 라이브러리는 자동으로 다운이 되어 있을 것이다. 추가로 해주어야 하는 부분은 AOP와 관련한 라이브러리에 대한 의존성을 주입하여 준다.
<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- AspectJ (AOP) -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
- 위 라이브러리를 이미 추가한 경우라면 패스해도 된다.

ㄱ. RootConfig.java 파일 수정
- 트랜잭션 관리를 위해 사용되는 인터페이스를 Bean으로 추가하여 준다.
- 인터페이스는 PlatformTransactionManager
- (참고) 스프링 트랜잭션 추상화의 핵심 인터페이스는 PlatformTransactionManager 다. 모든 스프링의 트랜잭션 기능과 코드는 이 인터페이스를 통해서 로우레벨의 트랜잭션 서비스를 이용할 수 있다. PlatformTransactionManager 인터페이스는 다음과 같이 세 개의 메소드를 갖고 있다.
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(Transaction status) throws TransactionException;
// 클래스 내부에 아래 내용 추가
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
ㄴ. TransactionConfig.java 파일 신규 생성
- 트랜잭션 처리를 위해 사용될 룰 관리를 설정하기 위한 클래스이다.
- kr\co\values\init 패키지 아래에 신규 클래스 생성
- TransactionInterceptor : 트랜잭션 어드바이스로 사용하도록 스프링이 제공하는 객체
package kr.co.values.init;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionInterceptor;
@Aspect // AOP를 통한 Global Transaction을 설정
// 트랜잭션을 건별 선언이 아닌 패키지(인터페이스) 단위로 설정하고자 할 때를 의미한다.
@Configuration
@EnableAspectJAutoProxy
public class TransactionConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(TransactionConfig.class);
private static final int TX_METHOD_TIMEOUT = 3;
private static final String AOP_POINTCUT_EXPRESSION = "execution(* kr.co.values..*ServiceImpl.*(..))";
@Autowired
private PlatformTransactionManager transactionManager;
@Bean
public TransactionInterceptor txAdvice() {
TransactionInterceptor txAdvice = new TransactionInterceptor();
// 트랜잭션의 경계를 설정할 때에는 Propagation, Isolation, Timeout, ReadOnly 의
// 네 가지 트랜잭션 속성으로 트랜잭션마다의 설정을 지정할 수 있다.
// 아래는 select, search 등의 접두사로 이루어진 메소드인 경우에는 ReadOnly 속성을 통해 트랜잭션을 읽기 전용으로만 설정
// 그 외 (insert, update 등) 는 롤백룰을 적용하여 트랜잭션 대상으로 지정
Properties txAttributes = new Properties();
List<RollbackRuleAttribute> rollbackRules = new ArrayList<RollbackRuleAttribute>();
rollbackRules.add(new RollbackRuleAttribute(Exception.class));
// 읽기 전용
DefaultTransactionAttribute readOnlyAttribute = new DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED);
readOnlyAttribute.setReadOnly(true);
readOnlyAttribute.setTimeout(TX_METHOD_TIMEOUT);
// 룰 적용 (쓰기 전용)
RuleBasedTransactionAttribute writeAttribute = new RuleBasedTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED, rollbackRules);
writeAttribute.setTimeout(TX_METHOD_TIMEOUT);
String readOnlyTransactionAttributesDefinition = readOnlyAttribute.toString();
String writeTransactionAttributesDefinition = writeAttribute.toString();
LOGGER.info("Read Only Attributes :: {}", readOnlyTransactionAttributesDefinition);
LOGGER.info("Write Attributes :: {}", writeTransactionAttributesDefinition);
// readonly 속성을 적용할 메소드 접두사를 적용
txAttributes.setProperty("retrieve*", readOnlyTransactionAttributesDefinition);
txAttributes.setProperty("select*", readOnlyTransactionAttributesDefinition);
txAttributes.setProperty("get*", readOnlyTransactionAttributesDefinition);
txAttributes.setProperty("list*", readOnlyTransactionAttributesDefinition);
txAttributes.setProperty("search*", readOnlyTransactionAttributesDefinition);
txAttributes.setProperty("find*", readOnlyTransactionAttributesDefinition);
txAttributes.setProperty("count*", readOnlyTransactionAttributesDefinition);
// 그 외에는 트랜잭션 적용
txAttributes.setProperty("*", writeTransactionAttributesDefinition);
txAdvice.setTransactionAttributes(txAttributes);
txAdvice.setTransactionManager(transactionManager);
return txAdvice;
}
@Bean
public Advisor txAdviceAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
// 위에서 선언한 트랜잭션 설정에 aop 적용
// AOP_POINTCUT_EXPRESSION 에 선언한 패키지 (인터페이스)에만 설정을 적용한다.
return new DefaultPointcutAdvisor(pointcut, txAdvice());
}
}
ㄷ. RootConfig.java 수정 – 어노테이션 추가
- 트랜잭션 설정 java (TransactionConfig.java) 파일 신규 생성이 완료된 이후 다시 RootConfig.java 설정 파일로 돌아와서 아래 내용을 추가한다.
- 상단에 어노테이션 추가
@Import({ TransactionConfig.class })
@EnableTransactionManagement

ㄹ.Service 패키지 생성 및 인터페이스 생성
- Insert , update 등의 비즈니스 로직에 트랜잭션 적용을 위해 사용될 서비스 패키지 및 인터페이스를 생성한다.
- 위에서 AOP를 통해 아래와 같이 설정하였으므로 패키지 아래의 ServiceImpl 이 포함된 클래스는 트랜잭션이 적용되도록 되어 있다. 따라서 클래스명 생성 시 주의해야 한다.
- 패키지 및 인터페이스, 인터페이스 구현클래스를 각각 생성한다.
[ 완성된 폴더 구성 ]

- MainService.java 인터페이스 생성 후 테스트용 메소드를 하나 설정한다.
package kr.co.values.service;
import java.util.Map;
public interface MainService {
//TEST
void saveTest(Map<String, Object> map);
}
- 위에서 만든 인터페이스를 구현할 클래스를 하나 생성하고 메소드의 내용을 상세 기술한다.
package kr.co.values.service;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import kr.co.values.persistence.MainMapper;
@Service
public class MainServiceImpl implements MainService{
@Autowired
private MainMapper mainMapper;
@Override
public void saveTest(Map<String, Object> map) {
// 아래는 트랜잭션 테스트를 해보기 위해 insert 2번을 실행한다.
map.put("userId", "test1");
map.put("userPwd", "test1");
map.put("userName", "test1");
map.put("userAuth", "1");
map.put("userTel", "test1");
map.put("userEmail", "test1");
mainMapper.addList(map);
map.put("userId", "test2");
map.put("userPwd", "test2");
map.put("userName", "test2");
map.put("userAuth", "112313123123"); // char 자리수 초과하여 에러를 발생
map.put("userTel", "test2");
map.put("userEmail", "test2");
mainMapper.addList(map);
}
}
ㅁ. 매퍼 메소드 및 쿼리 작성 (11장에서 만든 insert 쿼리 재사용)
- 11장에서 만든 아래 메소드를 그대로 사용할 것이므로 추가로 작성할 내용은 없다.
// 1. DATA INSERT
public void addList(Map<String, Object> map);
ㅂ. 컨트롤러 클래스 수정 – 서비스 호출 위한 컨트롤러 메소드 생성
- MainController.java 파일에 service 인터페이스 호출을 위한 메소드를 하나 추가한다.
- 상단에 어노테이션을 포함하여 서비스 호출을 위한 변수를 지정한다.
@Autowired
private MainService mainService; // 서비스 호출 변수 지정
… 중간 생략
// 트랜잭션 테스트
@RequestMapping("/main/testTransaction.do")
@ResponseBody
public Map<String, Object> testTransaction(@RequestBody Map<String, Object> params) {
System.out.println("트랜잭션 테스트");
mainService.saveTest(params);
Map<String, Object> result = new HashMap<String, Object>();
result.put("result", true);
return result;
}
ㅅ. 화면 수정 – 버튼 추가
- 앞서 백엔드 구현이 완료된 이후 화면에서 컨트롤러 호출을 위한 버튼을 하나 생성하고 해당 버튼에 이벤트를 적용한다.
- home.jsp 파일에 버튼 추가 및 crud.js파일 내 스크립트를 추가한다.
<button id="btnTransaction">Transaction Test</button>
//5. Transaction Test
$("#btnTransaction").on("click", function(){
var obj = {};
dataSaveAjax("/main/testTransaction.do", obj, "POST");
})
[ 트랜잭션 기능 테스트 ]
- 화면에서 트랜잭션 테스트 버튼을 클릭하면 서비스 인터페이스 메소드가 호출되면서 insert 쿼리를 2번 실행할 것이다.
- 이 때 첫번째 쿼리는 오류 없이 동작하여 insert 쿼리가 수행이 될 것이고 두번째 쿼리는 컬럼 사이즈를 초과하는 값을 넣어 오류를 발생하였으므로 쿼리가 정상 실행이 되지 않을 것이다.
- 이 때, 트랜잭션이 정상적으로 적용된 경우라면 첫번째 쿼리 또한 커밋되지 않고 롤백되어 데이터베이스 테이블을 조회할 경우 입력된 데이터가 없어야 한다.
- 반대로 트랜잭션 설정을 제거 (아래 어노테이션 제거) 하고 위 과정을 반복해보면 첫번째 쿼리의 데이터는 테이블에 반영이 될 것이다. -> 트랜잭션이 설정되지 않았으므로 두번째 쿼리 성공 유무와 관계 없이 첫번째 쿼리는 커밋된다.

[결과화면]
- 트랜잭션이 잘 적용 되서 첫번째 insert의 경우 올바른 쿼리임에도 불구하고, 2번째 insert에서 쿼리에 에러가 있기에 2번의 insert 모두 데이터에 반영되지 않음을 알수 있다.

'Spring Basic' 카테고리의 다른 글
[스프링프로젝트연습 17] 스프링 프로젝트 구현 - Junit 테스트 (0) | 2024.01.22 |
---|---|
[스프링프로젝트연습 16] 스프링 프로젝트 구현 - CRUD 서비스 로직 추가 (0) | 2024.01.22 |
[스프링프로젝트연습 14 - 2] .js 파일 분리 및 ajax공통메서드 생성 (0) | 2024.01.22 |
[스프링프로젝트연습 14] 스프링 프로젝트 구현 - ajax사용 /CRUD - DELETE (0) | 2024.01.22 |
[스프링프로젝트연습 13] 스프링 프로젝트 구현 - ajax사용 /CRUD - UPDATE (0) | 2024.01.22 |