트랜잭션 : 더 이상 쪼갤 수 없는 최소 단위의 작업
DAO메소드에서 DB커넥션을 매번 만든다. connection 오브젝트를 하나로 사용하려면 호출시마다 파라미터로 전달해주어야 한다.
히지만 이렇게 될 경우 개발자가 connection까지 연결을 고려해야한다.
그래서 스프링은 트랜잭션 동기화를 설정해준다.
DAO가 사용하는JDBCTemplete이 트랜잭션 동기화 방식을 이용하도록 하는 것이다.
실제 스프링을 사용하지 않는 자바에서는 직접 connection연결 관리부터 커밋,롤백을 제어해주어야 한다.
manager.getConnection( getConnectionHandler -> {
if( getConnectionHandler.failed() ) {
return;
}
SQLConnection conn = connectionHandler.getConnection();
conn.setAutoCommit( false, commitHandler -> {
if(commitHandler.failed() ) {
conn.close();
return;
}
......
//트랜잭션 작업 실행
//실패시
if ( completeHandler.failed() ) {
conn.rollback( rollbackHandler -> {
if( rollbackHandler.failed() ) {
logger.info( "rollbackHandler.failed()" );
}
conn.close();
} );
return;
}
//성공시
conn.close();
})
......
})
내부적으로는 setAutoComit(false)를 호출해 트랜잭션을 시작시킨 후에 본격적으로 DAO기능을 이용하기 시작한다. 동작 메소드 내부에서
이용하는 JDBCTemplete메소드에서는 가장 먼저 트랜잭션 동기화 저장소에 현재 시작된 트랜잭션을 가진 connection object가 존재하는지 확인한다.
이미 저장해둔 connection이 있다면 이를 가져와 PreparedStatment를 만들고 수정 SQL을 실행한다. 실행이 끝난후 connection을 닫지 않은 채로 작업
을 마친다.
여전히 connection은 열려있고 트랜잭션은 진행중 인채로 동기화 저장소에 저장되어있다. 모든 작업이 완료되면 commit을 호출해서 트랜잭션을 완료시킨다. 그리고 close()를 호출한다.
이떄 한개의 트랜젝션안에 여러개의 DB에 데이터를 넣는 작업은 해야할때는 어떻게 해야 할까?
로컬 트랜잭션은 하나의 DB Connection에 종속된다. 따라서 별도의 트랜잭션 관리자를 통해 트랜잭션을 관리하는 글로벌 트랜젝션방식을 사용해야한다
자바는 하나이상의 DB가 참여하는 트랜잭션을 만들면 JTA를 사용해야한다. 그외 하버네이트를 이용해 트랜잭션을 관리 할수있다.
하지만 특정 트랜잭션 방법에 의존적이지 않고 독립적이게 만들 수 없을까?
DB에서 제공하는 DB클라이언트 라이브러리와 API는 서로 호환되지 않는 독자적인 방식이다. SQL을 이용하는 방식이라는 공통점으로 만
들어낸것이 JDBC이며 이 추상화된 기술로 DB의 종류에 상관없이 일관적으로 데이터에 엑서스 할수있다. 이것처럼 트랜잭션 처리코드에서 추상화가 도입될수있다.
스프링은 데이터 엑서스 기술과 트랜잭션 서비스 사이의 종속성을 제거하고 스프링이 제공하는 트랜잭셔 추상 계층을 이용해서 서비스의 종류나 환경이 바뀌어도 트랜잭션을 사용하는 코드는 그대로 유지할 수 있는 유연성을 갖추고 있다.
스프링의 트랜잭션 추상 인터페이스 : PlatformTransactionManager
PlatformTransactionManager를 구현한 transactionManger를 주입하는 방식으로 사용한다.
단일 책임 원칙 : 하나의 모듈은 한가지 책임을 가진다.
PlatformTransactionManager은 트랜잭션의 경계를 지정(시작,종료, 종료시 정상, 비정상 판단)한다.
PlatformTransactionManager의 구현 클래스
-DataSourceTransactionManager
Connection의 트랜잭션 API를 이용해서 트랜잭션을 관리한다. 그리고 이 매니저를 사용하려면 빈에 등록되어있어야한다.
이 빈의 ref는 datasource id를 지정한다
-root-context.xml
<bean id="dataSource" class="org.apache.tomcat.dbcp.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="#{contextProperties.driver}"/>
<property name="url" value="#{contextProperties.url}"/>
<property name="username" value="#{contextProperties.username}"/>
<property name="password" value="#{contextProperties.password}">
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
-contextProperties
driver=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@127.0.0.1:1521:XE
username=user
password=1234
-service
@Resource(name="transactionManager")
private DataSourceTransactionManager transactionManager;
DefaultTransactionDefinition defaultTransactionDefinition= new DefaultTransactionDefinition();
/**
* Set the name of this transaction. Default is none.
* <p>This will be used as transaction name to be shown in a
* transaction monitor, if applicable (for example, WebLogic's).
*/
defaultTransactionDefinition.setName("insert tx");
status = transactionManager.getTransaction(defaultTransactionDefinition);
try{
dao의 작업
transactionManager.commit( status );
}catch{
transactionManager.rollback( status );
}
선언적 트랜잭션
코드에서 실행하지않고 설정파일이나 어노테이션을 이용하여 트랜잭션의 범위, 롤백규칙을 정의한다.
트랜잭션은 전파방식, 격리수준, 읽기 전용, 타임아웃, 롤백규칙으로 정의된다.
1.aop와 tx스카마 태그사용
트랙잰션 설정마다 변경이 필요없다.
count 같은 쿼리를 만들때 읽기전용 속성을 메소드마다 추가해줘야하지만 tx 스키마 태그를 이용하면 한번에 처리가능하다.
<tx:annotation-driven transaction-manager="transactionManager" />
<tx:advice id="mybatis_txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="list*" read-only="true" propagation="NOT_SUPPORTED"/>
<tx:method name="count*" read-only="true" propagation="NOT_SUPPORTED"/>
<tx:method name="insert*" timeout="3" rollback-for="Exception" propagation="REQUIRED"/>
<tx:method name="add*" timeout="3" rollback-for="Exception" propagation="REQUIRED"/>
<tx:method name="update*" timeout="3" rollback-for="Exception" propagation="REQUIRED"/>
<tx:method name="delete*" timeout="3" rollback-for="Exception" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="mybatis_operations" expression="execution( *com. *Dao.)"/>
<aop:advisor advice-ref="mybatis_txAdvice" pointcut-ref="mybatis_operations"/>
</aop:config>
2.@Transactional
1번과 같이 일괄적으로 적용하는 방식은 대부부 상황에 잘 들어맞는다. 하지만 클래스나 메소드마다 제각각 속성일 경우 이름 패턴을
통해 적용하는건 적합하지 않다. 이때 직접 타깃에 어노테이션을 사용한다.
<tx:annotation-driven> : @Transactional이 붙은클래스나 인터페이스 또는 메소드를 찾아 트랜잭션 advice를 적용한다
@Target( ElementType.METHOD, ElementType,TYPE}) : 사용할 대상지정, 메소드와 타입처럼 한개이상 지정가능
@Retention( RetentionPolicy.RUNTIME ) 애노테이션 정보가 언제까지 유지되는 지를 지정한다. 런타임때도 정보 얻을수 있다.
@Inherited 상속을 통해서도 애노테이션 정보 얻을 수 있다.
public @interface Transactional {
Isolation isolation() default Isolation.DEFAULT;
boolean readOnly() default false;
}
@Transactional은 동시에 포인트컷의 자동등록에서 사용된다.
DefaultTransaction이 구현하고 있는 TransactionDefinition 인터페이스는 트랜잭션의 동작방식에 영향을 줄수있는 네가지 속성을 정의하고 있다.
전파방식( PROPAGATION )
-트랜잭션의 경계에서 이미 진행중인 트랜잭션이 있을때 또는 없을때 어떻게 동작할 것인가를 결정하는 방식
예를 들면 A의 트랜잭션이 시작돼서 진행중이라면 B의 코드는 어떤 트랜잭션에서 동작해야할까.. 그리고 어딘가에 참여하여 예외발생시 두 트랜잭션은
어떻게 되는 것인가.
getTransaction() 메소드는 항상 트랜잭션을 시작하는 것이 아니라 전파속성과 현재 진행중인 트랙잭션의 존재여부로 반환값을 정한다.
-REQUIRED : default 이미시작된 트랜잭션이 있으면 없으면 새로 시작한다. 하나의 트랜잭션이 시작된 후에 다른 트랜잭션 경계가 설정된 메소드를 호출하면 자연스럽게 하나가 된다. (가장 많이 사용 default) A, B , A>B B>A 모두 가능하다.
-SUPPORTS : 이미 시작된 트랜잭션이 있으면 참여하고 없으면 없이 진행한다. connection, 하이버네이트는 공유가능하다.
-MANDATORY : 이미시작된 트랜잭션이 있으면 없으면 예외발생,
-REQUIRES_NEW : 항상 새로운 트랙잭션 시작한다. 항상 독자적인 트랜잭션이다.
-NOT_SUPPORTED : 사용하지않는다.( aop를 통해 한번에 적용하고 특정메소드가 적용되지않게 예외처리 )
-NEVER : 트랜잭션을 사용하지 않도록 강제한다. 이미 진행중인 트랜잭션이 있다면 예외발생
-NESTED : 이미 진행중인 트랜잭션이 있다면 중첩 트랜잭션을 사용한다. 독립된트랜잭션이 아닌 트랜잭션 안에 다시 트랜잭션을 만드는 것이다. 로그 트랜잭션과 메인 트랜잭션중 부모 자식관계를 만들어 로그는 실패해도 메인트랜잭션은 커밋될수있도록 함
격리수준( IOSLATION )
동시에 여러 트랜잭션이 진행될때 트랜잭션이 작업결과를 다른 트랜잭션에게 어떻게 노출할 것인지 결정
-DEFAULT : 사용하는 데이터엑서스 기술에 따름
-READ_UNCOMMITTED : 하나의 트랜잭션이 커밋되기전에 그 변화가 다른 트랜잭션에 노출 , 빠르나 데이터 일관성이 떨어짐
-READ_COMMITTED : 많이 사용되는 격리수준, 커밋하지 않으면 정보를 읽어올수없다. 하지만 읽는 로우는 다른 트랜잭션이 수정가능하다. SELECT이후 변경된 ROW 가능
-REPEATABLE_READ : 읽은 로우를 다른 트랙잭션이 수정할수없다 하지만 추가는 가능하다. SELECT이후 추가된 ROW가능
-SERIALIZABLE : 동시에 같은 테이블 접근 불가능. 격리수준은 높으나 성능이 가장 떨어지므로 극단적인 안정작업만 추천
제한시간( TIME OUT )
-제한시간을 지정하여 예외 발생가능, 기본설정은 없다.
읽기전용( READ-ONLY )
-읽기전용으로 쓰기 방지
트랜잭션 정의를 수정하려면 TransactionDefinition 오브잭트를 생성하고 사용하는 코드는 트랜잭션 경계설정 기능을 가진 TransactionAdvice다.
하지만 이 속성을 변경하면 모든 트랜잭선이 변경된다.
원하는 메소드만 선택하여 정의를 변경할수있다. 어드바이스의 기능을 확장하면 된다.
메소드 이름 패턴에 따라 다른 트랜잭션 정의가 적용되도록 만들면 되는데 TransactionInterceptor를 사용한다.
<tx:advice> : TransactionInterceptor 빈이 등록된다.
<tx:attributes> : 개별 어트리뷰트를 등록하여 지정할수있다.
롤백 예외
-런타임 예외시 롤백한다.
-런타임 예외시 말고 롤백이 필요하다면 rollback-for, rollbackForClassName을 사용한다.
참고: 토비의 스프링