8장(2)은 목록 6 - 8까지 다루고 있습니다.
8-1장의 키워드
# 익셉션 변환 처리
# @Transactional 애노테이션
# 프록시를 이용한 트랜잭션 처리 과정
jdbcTemplate.update("update MEMBER set NAME = ?, PASSWORD = ?, where" +
"EMAIL = ?", // 이 부분에서 오류 발생 : whereEmail = ? => where Email = ?
member.getName(), member.getPassword(), member.getEmail());
org.springframework.jdbc.BadSqlGrammerException: ...생략
...생략
Caused by: com.mysql.jdbc.exception.jdbc4.MySQLSyntaxErrorException: ...생략
exception 발생 과정
try{
...JDBC 사용 코드
} catch(SQLException ex){
throw convertSQLToDataException(ex); //이 과정에서 SQLException이 DataAccessException으로 변경됨
}
SQLException을 DataAccessException으로 변환 시키는 이유
JDBC (SQLException)
JPA (PersistenceException) convert → DataAccessException으로 동일하게 처리하기 위해
하이버네이트 (HibernateException)
try{
//DB 연동 코드
} catch(DataAcessException ex){ //동일하게 DataAcessException로 처리
...
}
JDBC의 트랜잭션 처리 방식
Connection conn = null;
try{
conn = DriverManager.getConnection(jdbcUrl, user, pw);
conn.setAutoCommit(false); //트랜잭션 범위 시작
...쿼리 시작
conn.commit(); //트랜잭션 범위 종료 : 커밋
} catch(SQLException ex){
if(conn != null)
//트랜잭션 범위 종료 : 롤백
try { conn.rollback(); } catch(SQLException e) { }
} finally {
if(conn != null)
try { conn.close(); } catch(SQLException e) { }
}
import.org.springframework.transaction.annotation.Transactional;
public class ChangePasswordService {
@Transcational
public void changePassword(String email, String oldPwd, String newPwd){
Member member = memberDao.selectByEmail(email);
if(member != null)
throw new MemberNotFoundException();
member.changePassword(oldPwd, newPwd);
memberDao.update(member);
}
1. 플랫폼 트랜잭션 매니저(PlatformTransactionaManager) 설정
2. @Transactional 애노테이션 활성화 설정(@EnableTransactionManagement 애노테이션 사용)
@Configuration
@EnableTransactionManagement
public class AppCtx{
@Bean(destoryMethod = "close")
public DataSource dataSource(){
DataSource ds = new DataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost/spring5fs?characterEncoding=utf8");
...생략
reuturn ds;
}
@Bean
public PlatformTransactionManager transactionManager(){
DataSourceTransactionManager tm = new DataSourceTransactionManager();
tm.setDataSource(dataSource()); //dataSource 프로퍼티를 이용하여 트랜잭션 연동에 사용할 DataSource 지정
return tm;
}
}
1. PlatformTransactionaManager : 트랜잭션 매니저 인터페이스
2. @EnableTransactionManagement 애노테이션 : @Transactional 애노테이션이 붙은 메서드를 트랜잭션 범위에서 실행하는 기능을 활성화 한다.
3. 트랜잭션 범위에서 실행하고 싶은 스프링 빈객체의 메서드에 @Transactional 애노테이션을 붙인다.
import.org.springframework.transaction.annotation.Transactional;
public class ChangePasswordService {
@Transcational //트랜잭션 범위에서 실행
public void changePassword(String email, String oldPwd, String newPwd){
Member member = memberDao.selectByEmail(email);
if(member != null)
throw new MemberNotFoundException();
member.changePassword(oldPwd, newPwd);
memberDao.update(member);
}
}
4. ChangePasswordService 클래스를 빈으로 추가한다.
@Configuration
@EnableTransactionManagement
public class AppCtx{
...생략
@Bean
public ChangePasswordService changePwdSvc(){
ChangePasswordService pwdSvc = new ChangePasswordService();
pwdSvc.setMemberDao(memberDao());
return pwdSvc;
}
}
5. changePwdSvc 빈을 이용하여 암호 변경 기능 실행하기
public class MainForCPS {
public static void main(String[] arg){
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppCtx.class);
ChangePasswordService cps = ctx.getBean("changePwdSvc", ChangePasswordService.class);
try{
cps.changePassword("madvirus@madvirus.net", "1234", "1111"); //changePwdSvc 빈을 이용하여 비밀번호 변경
System.out.println("암호를 변경했습니다.");
}
...생략
}
}
6. 커밋이 실제로 시작, 커밋됐는지 알아보기 위해 Logback 사용하기
1. @EnableTransactionManagement 애노테이션 → @Transactional 애노테이션이 적용된 빈을 찾아간다.
2. 알맞은 프록시 객체를 생성한다.
3. PlatformTransactionManagement를 사용하여 트랜잭션을 시작한다
4. chaangePassword() 메서드를 트랜잭션 내에서 실행한다.
5. 성공적으로 실행되면 트랜잭션을 commit() 한다.
:MainForCPS → :프록시(changePwdSvc빈) → :PlatformTransactionManagement → :ChangePasswordService
- changePassword() - getTransaction()
- changePassword()
- commit()
@Transactional(rollbackFor = SQLException.class) //SQLException 발생시 rollback 발생하도록한다.
public void someMethod() {
...
}
속성 | 타입 | 설명 |
value | String | 트랜잭션을 관리할때 사용할 PlatformTransactionManager 빈의 이름을 지정한다. 기본값은 " "이다. |
propagation | Propagation | 트랜잭션 전파 타입을 지정한다. 기본값은 Propagation, REQUIRED이다. |
isolation | isolation | 트랜잭션과 격리 레벨을 지정한다. 기본값은 isolation, DEFAULT 이다. |
timeout | int | 트랜잭션 제한 시간을 지정한다. 기본값은 -1로 이경우 데이터 베이스의 타임아웃 시간을 사용한다. 초 단위로 지정한다. |
값 | 설명 |
REQIRED | 메서드를 수행하는데 트랜잭션이 필요하다는 것을 의미한다. 현재 진행중인 트랜잭션이 존재하면 해당 트랜잭션을 사용한다. 존재하지 않으면 새로운 트랜잭션을 생성한다. |
MANDATORY | 메서드를 수행하는 데 트랜잭션이 필요하다는 것을 의미한다. 하지만 REQUIRED와 달리 진행중인 트랜잭션의 존재하지 않을 경우 익셉션이 발생한다. |
REQUIRES_NEW | 항상 새로운 트랜잭션을 시작한다. 진행 중인 트랜잭션이 존재하면 기존 트랜잭션을 일시 중지하고 새로운 트랜잭션을 시작한다. 새로 시작된 트랜잭션이 종료된 뒤에 기존 트랜잭션이 계속된다. |
SUPPORTS | 메서드가 트랜잭션을 필요로 하지 않지만, 진행 중인 트랜잭션이 존재하면 트랜잭션을 사용한다는 것을 의미한다. 진행 중인 트랜잭션의 존재하지 않더라도 메서드는 정상적으로 작동한다. |
NOT_SUPPORTED | 메서드가 트랜잭션을 필욜 하지 않음을 의미한다. SUPPORTS와 달리 진행 중인 트랜잭션이 존재할 경우 메소드가 실행되는 동안 트랜잭션은 일시 중지되고 메서드 실행이 종료된 후에 트랜잭션을 계속 진행한다. |
NEVER | 메서드가 트랜잭션을 필요로 하지 않는다. 만약 진행 중인 트랜잭션이 존재하면 인셉션이 발생한다. |
NESTED | 진행중인 트랜잭션이 존재하면 기존 트랜잭션에 중첩된 트랜잭션에서 메서드를 실행한다. 진행 중인 트랜잭션이 존재하지 않으면 REQIRED와 동일하게 작동한다. 이 기능은 JDBC 3.0 드라이버를 사용할 때에만 적용된다.(JTA Provider가 이 기능을 지원할 경우에도 사용가능하다) |
값 | 설명 |
DEFAULT | 기본 설정을 사용한다. |
READ_UNCOMMITTED | 다른 트랜잭션이 커밋하지 않은 데이터를 읽을 수 있다. |
READ_COMMITTED | 다른 트랜잭션이 커밋한 데이터를 읽을 수 있다. |
REPEATABLE_READ | 처음에 읽어 온 데이터와 두 번째 읽어 온 데이터가 동일한 값을 갖는다. |
SERIVALZABLE | 동일한 데이터에 대해서 동시에 두 개 이상의 트랜잭션을 수정할 수 없다. |
속성 | 설명 |
proxyTargetClass | 클래스를 이용하여 프록시를 생성할지 여부를 지정한다. 기본값은 false로서 인터페이스를 이용해서 프록시를 생성한다. |
order | AOP 적용 순서를 지정한다. 기본값은 가장 낮은 우선순위에 해당하는 int의 최댓값이다. |
//SomeService 클래스
public class SomeService {
private AnyService anyService;
@Transactional
public void some() {
anyService.any();
}
...생략
}
//AnyService 클래스
public class AnyService {
@Transactional
public void any() {...}
}
@Configuration
@EnableTransactionManagement
public class Config {
@Bean
public SomeService some(){
SomeService some = new SomeService();
...생략
}
@Bean
public AnyService any(){
return new AnyService();
}
...생략
}
트랜잭션의 전파 과정
1. some() 메소드 호출 : 트랜잭션 새로 시작
2. some() 메소드 내부의 any() 메소드 호출 : 이미 트랜잭션 존재 → 호출 시점에서 트랜잭션 생성하지 않는다.
1. some() 메소드 호출 : 트랜잭션 새로 시작
2. some() 메소드 내부의 any() 메소드 호출 : 새로운 트랜잭션 생성
import.org.springframework.transaction.annotation.Transactional;
//ChangePasswordService 클래스
public class ChangePasswordService {
@Transcational
public void changePassword(String email, String oldPwd, String newPwd){
Member member = memberDao.selectByEmail(email);
if(member != null)
throw new MemberNotFoundException();
member.changePassword(oldPwd, newPwd);
memberDao.update(member);
}
}
//MemeberDao 클래스
@Transactional 없음
public class MemberDao {
private JdbcTemplate jdbcTemplate;
public void update(Memeber member){
jdbcTemplate.update(
"update Memeber set NAME = ?, PASSWORD = ?, where EMAIL = ?",
member.getName(), member.getPassword(), member.getEmail());
}
}
1. 트랜잭션 시작
2. changePassword() 메소드 실행
2.1. selectByEmail() 메소드 실행
2.1.1. JdbcTemplate 실행
2.2. update() 메소드 실행
2.2.1. JdbcTemplate 실행
3. commit
1. 연동 기술에 상관 없이 동일하게 익셉션을 처리할 수 있도록 하는 Exception을 (DataAccessException)이라고 한다.
2. 두 개 이상의 쿼리를 한 작업으로 실행해야 할 때 사용하는 것을 (트랜잭션)이라고 한다.
3. 트랜잭션 범위에서 실행하고 싶은 메서드에 (@Transactional)애노테이션을 붙인다.
4. 스프링이 제공하는 트랜잭션 매니저 인터페이스이면서, 트랜잭션을 시작하도록 만드는 인터페이스는(PlatformTransactionManager)이라고 한다.
5. (@EnableTransactionManagement) 애노테이션은 @Transactional 애노테이션이 붙은 메서드를 트랜잭션 범위에서 실행하는 기능을 활성화한다.
6. ChangePasswordService 클래스의 changePwdSvc() 메소드에 @Transactional 애노테이션이 적용되어 getBean("changePwdSvc", ChangePasswordService.class)를 통해 스프링은 트랜잭션 기능을 적용한 changePwdSvc빈의 (프록시 객체)를 생성한다.
7. Propagation 열거 타입 값 중의 (REQIRED) 값은 현재 진행중인 트랜잭션이 존재하면 해당 트랜잭션을 사용한다.
1. 조건에 맞게 빈칸을 채우세요
@_______________
public void changePassword(String email, String oldPwd, String newPwd){
Member member = memberDao.selectByEmail(email);
if(member != null)
throw new MemberNotFoundException();
member.changePassword(oldPwd, newPwd);
memberDao.update(member);
}
@Configuration
@________________________
public class AppCtx{
@Bean(destoryMethod = "close")
public DataSource dataSource(){
DataSource ds = new DataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost/spring5fs?characterEncoding=utf8");
...생략
reuturn ds;
}
@Bean
public _____________________ transactionManager(){
DataSourceTransactionManager tm = new DataSourceTransactionManager();
tm.setDataSource(dataSource()); //dataSource 프로퍼티를 이용하여 트랜잭션 연동에 사용할 DataSource 지정
return tm;
}
}
정답 : @EnableTransactionManagement, PlatformTransactionManager
2. 다음 조건에 맞게 빈칸을 채우세요
@Transactional(______________________________)
public void someMethod() {
...
}
정답 : rollForback = {SQLException.class, IOException.class}
Corner Spring #2
Editor : 스머프냠
[스프링2] 10장. 스프링 MVC 프레임워크 동작 방식 (0) | 2022.12.01 |
---|---|
[스프링2] 9장. 스프링 MVC 시작하기 (0) | 2022.11.24 |
[스프링2] 8장. DB 연동 (1) (0) | 2022.11.17 |
[스프링2] 7장. AOP 프로그래밍 (0) | 2022.11.10 |
[스프링2] 6장. 빈 라이프사이클과 범위 (0) | 2022.10.13 |