Spring framework

Transaction

yimoc 2018. 8. 22. 11:22


JaveEE transaction management

global transaction

  - multiple transactional resource를 관리(관계형데이터베이스, 메시지큐)

  > JTA를 이용한다.

     UserTransaction 을 사용하면 JNDI가 필요하다.


  사용 방법

     -  EJB CMT(Container Managed Transaction)

         CMT declarartive transaction management사용(<-> programmatic transaction managemenet)

         JNDI lookup을 제거(JDNI는 사용함),tranaction처리를 위한 Java code코드필요성 없어진다.

         단점: JTA application server환경과 결합된다.


local transaction

  jdbc connetion과 같은 리소소 연계하여 작업

  사용하기 쉽다

  단점: 여러 개의 transaction resource에서 작업할 수 없다.

      global JTA transaction에서 실행 할수 없다.

  

Spring transaction

다른 환경에서 spring을 이용하셔 일관된 모델을 사용 할 수 있다.

선언적 , 프로그램적transaction 관리를 제공한다.

 

추상화 이해하기

PlatformTransactionManager

-transaction strategy 이며 

org.springframework.transaction.PlatformTransactionManager interface으로 정의하고 있다.

Spring transaction의 중심 Interface이다. 


public interface PlatformTransactionManager {

 

    TransactionStatus getTransaction(

            TransactionDefinition definition) throws TransactionException;

 

    void commit(TransactionStatus status) throws TransactionException;

 

    void rollback(TransactionStatus status) throws TransactionException;

}


getTransaction()TransactionDefinition인자에 TransactionStatus를 리턴한다.

TransactionStatus는 새로운 transaction일수도 혹은 존재하는 transaction을 나타낼 수도 있다.

TransactionDefinition

* Propagation (전파)

    PROPAGATION_REQUIRED

          : 진행중인 트랜잭션이 없으면 -> 새로 시작

                                       있으면 -> 유지하여 참여

    PROPAGATION_REQUIRES_NEW

          : 항상 새로운 트랜잭션을 시작

    PROPAGATOIN_NOT_SUPPORTED

          :트랜잭션 없이 동작한다.


* Isolation(격리)

   여러 개의 트랜잭션이 동시에 진행하는 여부를 결정

   DB, JDBC driver, Datasource에서 설정가능

    ISOLATION_DEFAULT : Datasource에 설정된 격리수준을 따른다.


* Timeout


* ReadOnly status

 

TransactionStatus

transaction 실행를 제어하고 상태를 조회하는 방법을 제공한다.


public interface TransactionStatus extends SavepointManager {

 

    boolean isNewTransaction();

 

    boolean hasSavepoint();

 

    void setRollbackOnly();

 

    boolean isRollbackOnly();

 

    void flush();

 

    boolean isCompleted();


 



PlatformTransactionManager사용


JDBC, JTA, Hibernate에서 사용


Dataousrce 정의

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">

    <property name="driverClassName" value="${jdbc.driverClassName}" />

    <property name="url" value="${jdbc.url}" />

    <property name="username" value="${jdbc.username}" />

    <property name="password" value="${jdbc.password}" />

</bean>

PlatformTransactionManager bean 정의는 DataSource 정의의 참조를 가진다.

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

    <property name="dataSource" ref="dataSource"/>

</bean>

 

JtaTransactionManager의 사용법 예


<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:jee="http://www.springframework.org/schema/jee"

    xsi:schemaLocation="

        http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans.xsd

        http://www.springframework.org/schema/jee

        http://www.springframework.org/schema/jee/spring-jee.xsd">

 

    <jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>

 

    <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />

 

    <!-- other <bean/> definitions here -->

 

</beans>

 

Hibernate

The txManager bean in this case is of the HibernateTransactionManager type. In the same way as the DataSourceTransactionManager needs a reference to the DataSource, the HibernateTransactionManager needs a reference to the SessionFactory.

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">

    <property name="dataSource" ref="dataSource"/>

    <property name="mappingResources">

        <list>

            <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>

        </list>

    </property>

    <property name="hibernateProperties">

        <value>

            hibernate.dialect=${hibernate.dialect}

        </value>

    </property>

</bean>

 

<bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">

    <property name="sessionFactory" ref="sessionFactory"/>

</bean>

 

 

transactions의 리소스동기화

DataSourceTransactionManager(DataSource)

HibernateTransactionManager(SessionFactory)

 

low-level 동기화 접근

아래 클래스를 이용하여 instance획득, transaction동기화,exception처리한다.

DataSourceUtils(for JDBC)

EntityManagerFactoryUtils(for JPA)

SessionFactoryUtils(for Hibernate)

PersistenceManagerFactoryUtils(for JDO)


예를 들어 Jdbc의 getConnection() 호출 대신 org.springframework.jdbc.datasource.DataSourceUtils 클래스를 이용해야 한다.


Connection conn = DataSourceUtils.getConnection(dataSource);


TransactionAwareDataSourceProxy

 - spring 관리transaction의 awareness가 추가된 javax.sql.DataSource의 Proxy 이다



선언적transaction관리

Spring aspec-oriented programming(AOP)로 처리한다.

개별 메서드 레벨까지 transaction을 지정할수 있다.(EJB CMT와 유사한 점)

cf) 차이점

1.EJB CMT : JTA만 처리

모두 처리 가능 - JDBC, JPA, Hibernate 를 사용여 JTA transaction와 함께 작업가능하다.

2. 선언적 rollback규칙을 제공

3. 사용자 transaction 동작을 AOP를 사용하여 정의 할수 있다.


spring은 EJB와 달리 원격호출로 transaction context의 전파를 지원하지 않는다.( 기능 확인)


rollback은 자동rollback을 발생시키는 exception을 지정할수 있다. 코드가 아닌 선언적으로 지정할수 있다.

custom화된 MyApplicationException은 항상 롤백한다는 규칙을 지정할수 있다. 비지니스객체가 transaction instrastrucutre에 의존하지 않는다는 장점이 있다. 이말은 별도로 하여 할수 있으니 장점이다. (사실 이부분은 별도로 하는것이 낫다)


EJB 컨테이너 기본 동작은 시스템 예외 (일반적으로 런타임 예외)에서 트랜잭션을 자동으로 롤백하지만 EJB CMT는 응용 프로그램 예외 (즉, java.rmi.RemoteException 이외의 확인 된 예외)에서 트랜잭션을 자동으로 롤백하지 않습니다. 선언적 트랜잭션 관리를위한 Spring 기본 동작은 EJB 규칙 (롤백은 검사되지 않은 예외에만 자동으로 수행됨)을 따르지만이 동작을 사용자 정의하는 것이 유용한 경우가 많습니다.



1.4.1. Understanding the Spring Framework’s Declarative Transaction Implementation

@Transactional

@EnableTransactionManagement(Configuration)

TransactionInterceptor를 사용하여 methodinvocation(호출)중심으로 처리한다.




기본 transaction 처리 코드는 아래와 같다. 


 

Public Object transactionInvoke(){

    PlatformTransactionManager = new DataSourceTransactionManager(datasource);

    TransactionStatus status = TransactionManager.getTransaction(new DefaultTransactionDefinition());

    Try {

        Object ret = invoke.,proceed()’

        This.transactionManger.commit()

    } catch (exception e){

        This.transactionManger.rollback(status);

   }

}

 

 

상세)

TransactoinSynchronizationManger

Thread resource(Connection) transaction 동기화를 관리한다.

-ThreadLocal<Map<Object, Object>> resources

key하나에 resource를 지원한다.

 

 

doTransaction{

           TransactionSynchronization.initSynchronization();

           Connection c= DataSourceUtils.getConnection(datasource);

           Try {

                     //service logic

                     doService1()

                     doService2()

           } catch (Exception ) {

                     c.rollback();

           } finally {

                     DataSourceUtils.releaseConnection(c, datasource);

                     TransactionSyncrhonizationManager.unbindResource(this.datasource);

                     TransactionSyncrhonizationManager.clearSynchronization();

           }

}

 

TransactionSynchroniztionManager

private static final ThreadLocal<Map<Object, Object>> resources =

                    new NamedThreadLocal<Map<Object, Object>>("Transactional resources");

 

       private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =

                    new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations");

 

public static void initSynchronization() throws IllegalStateException {

             if (isSynchronizationActive()) {

                    throw new IllegalStateException("Cannot activate transaction synchronization - already active");

             }

             logger.trace("Initializing transaction synchronization");

             synchronizations.set(new LinkedHashSet<TransactionSynchronization>());

       }

public void set(T value) {

        Thread t = Thread.currentThread();

        ThreadLocalMap map = getMap(t);

        if (map != null)

            map.set(this, value);

        else

            createMap(t, value);

    }

 

 

public static void clearSynchronization() throws IllegalStateException {

             if (!isSynchronizationActive()) {

                    throw new IllegalStateException("Cannot deactivate transaction synchronization - not active");

             }

             logger.trace("Clearing transaction synchronization");

             synchronizations.remove();

       }

 

 

public static Object unbindResource(Object key) throws IllegalStateException {

             Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);

             Object value = doUnbindResource(actualKey);

             if (value == null) {

                    throw new IllegalStateException(

                                 "No value for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");

             }

             return value;

       }

private static Object doUnbindResource(Object actualKey) {

             Map<Object, Object> map = resources.get();

             if (map == null) {

                    return null;

             }

             Object value = map.remove(actualKey);

             // Remove entire ThreadLocal if empty...

             if (map.isEmpty()) {

                    resources.remove();

             }

             // Transparently suppress a ResourceHolder that was marked as void...

             if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {

                    value = null;

             }

             if (value != null && logger.isTraceEnabled()) {

                    logger.trace("Removed value [" + value + "] for key [" + actualKey + "] from thread [" +

                                 Thread.currentThread().getName() + "]");

             }

             return value;

       }

 

 

DatasourceUtils.java

Connction 을 가져오고 또한 transaction동기화를 위한 설정 및 resource(connect)를 바인딩한다.

아래 소스 참조

 

 

 

주요 함수

public static Connection doGetConnection(DataSource dataSource) throws SQLException {

             Assert.notNull(dataSource, "No DataSource specified");

 

             ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);

             if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {

                    conHolder.requested();

                    if (!conHolder.hasConnection()) {

                           logger.debug("Fetching resumed JDBC Connection from DataSource");

                           conHolder.setConnection(dataSource.getConnection());

                    }

                    return conHolder.getConnection();

             }

             // Else we either got no holder or an empty thread-bound holder here.

 

             logger.debug("Fetching JDBC Connection from DataSource");

             Connection con = dataSource.getConnection();

 

             if (TransactionSynchronizationManager.isSynchronizationActive()) {

                    logger.debug("Registering transaction synchronization for JDBC Connection");

                    // Use same Connection for further JDBC actions within the transaction.

                    // Thread-bound object will get removed by synchronization at transaction completion.

                    ConnectionHolder holderToUse = conHolder;

                    if (holderToUse == null) {

                           holderToUse = new ConnectionHolder(con);

                    }

                    else {

                           holderToUse.setConnection(con);

                    }

                    holderToUse.requested();

                    TransactionSynchronizationManager.registerSynchronization(

                                 new ConnectionSynchronization(holderToUse, dataSource));

                    holderToUse.setSynchronizedWithTransaction(true);

                    if (holderToUse != conHolder) {

                           TransactionSynchronizationManager.bindResource(dataSource, holderToUse);

                    }

             }

 

             return con;

       }

 

 

TransactionSynchronizationManager

스레드 당 리소스 및 트랜잭션 동기화를 관리하는 내부 클래스입니다

트랜잭션 동기화는 initSynchronization clearSynchronization을 통해 트랜잭션 관리자에 의해 활성화 및 비활성화되어야합니다

 

TransactionSynchronization

트랜잭션 동기화 콜백을위한 인터페이스. AbstractPlatformTransactionManager에서 지원됩니다.

 

 

JTA 트랜잭션

InitialContext ctx = new InitialContext();

UserTransaction tx = (UserTransaction) ctx.lookup(USER_TX_JNDI_NAME);

 

tx.begin();

 

Connection c = dataSource.getConnection();

Try {

           //data access code

           tx.commit();

} catch {

           tx.rollback();

} finally{

           c.close();

}

 

 

 

 

 


 

@Transaction 관리를 위한 의사 코드 :

§ TransactionSynchronizationManager.getResource (EMF) 존재, 사용

 

§  그렇지 않으면EntityManagerFactory 검색하고 처음부터 새로운 EntityManager 인스턴스를 생성하고 TransactionLocalMap.bindResource (emf, em) ThreadLocal 맵에 등록하십시오.

 

§  기본 JPADialect 구현 getJpaDialect (). beginTransaction (...)  호출하여 DB 트랜잭션을 시작합니다 .

 

§  entityManager.getTransaction (). commit ()  호출하여 트랜잭션을 커밋합니다 .

 

§  현재 엔티티 관리자가 처음부터 작성된 경우 ThreadLocal 맵에서 제거하고 닫습니다.