프로그래밍/- 남굼성의 Spring 교육

서비스 계층 분리 [ @Transaction , AOP ]

즐겁게 하하하 2022. 3. 12. 18:03
728x90

1. 서비스계층의 분리

2. DAO의 각 메서드는 개별 Connetion을 사용 하지만 [ TransactionManager 를 이용할 수 있다. ]

3. AOP를 이용하여 핵심기능, 부가기능 분리하기

4. @Transactional 은 클래스나 인터페이스에도 사용 가능


1. Transaction , AOP

 

A1Dao.java 

@Repository
public class A1Dao {

    @Autowired
    DataSource ds;

    public int insert(int key , int value) throws Exception{
        Connection conn = null;
        PreparedStatement pstmt = null;

        try {
            conn = ds.getConnection();  
            pstmt = conn.prepareStatement("insert into a1 values(?, ?)");

            pstmt.setInt(1,key);
            pstmt.setInt(2,value);
            return pstmt.executeUpdate();

        } catch (SQLException e) {
            e.printStackTrace(); 
        } finally {
            close(conn, pstmt); // transaction 1개 
        }
    }

    private void close(AutoCloseable... acs) {
        for(AutoCloseable ac :acs)
            try { if(ac!=null) ac.close(); } catch(Exception e) { e.printStackTrace(); }
    } 
}

 

Junit test

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/**/root-context.xml"})
public class A1DaoTest {

    @Autowired
    A1Dao a1Dao;

    @Test
    public void InsertTest() throws Exception {
        a1Dao.insert(1,100);
        a1Dao.insert(2,200);
    }
}

 

 

2. 두가지 일을 하나의 Transaction 으로 묶기

A1Dao.java

@Repository
public class A1Dao {

    @Autowired
    DataSource ds;

    public int insert(int key , int value) throws Exception{
        Connection conn = null;
        PreparedStatement pstmt = null;

        try {
            // conn = ds.getConnection(); // transaction 1개
            conn = DataSourceUtils.getConnection(ds);  // transaction 여러개
            System.out.println("conn = " + conn);

            pstmt = conn.prepareStatement("insert into a1 values(?, ?)");

            pstmt.setInt(1,key);
            pstmt.setInt(2,value);
            return pstmt.executeUpdate();

        } catch (SQLException e) {
            e.printStackTrace();
            throw e;
        } finally {
            // close(conn, pstmt); // transaction 1개
            close(pstmt);                               // transaction 여러개일때
            DataSourceUtils.releaseConnection(conn,ds); // transaction 여러개일때
        }
    }

    private void close(AutoCloseable... acs) {
        for(AutoCloseable ac :acs)
            try { if(ac!=null) ac.close(); } catch(Exception e) { e.printStackTrace(); }
    }

    public void deleteAll() throws Exception{
        Connection conn = ds.getConnection();
        String sql = "delete from a1";
        PreparedStatement pstmt = conn.prepareStatement(sql);
        pstmt.executeUpdate();
        close(conn,pstmt);
    }
}

 

Junit test

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/**/root-context.xml"})
public class A1DaoTest {

    @Autowired
    A1Dao a1Dao;

    @Autowired
    DataSource ds;

    @Test
    public void InsertTest() throws Exception {
        // Tx Manager를 생성 ( 하나의 transaction 으로 묶기 )
        PlatformTransactionManager tm = new DataSourceTransactionManager(ds);
        TransactionStatus status = tm.getTransaction(new DefaultTransactionDefinition());

        // Tx 시작
        try {
            a1Dao.deleteAll();
            a1Dao.insert(1,100);
            a1Dao.insert(1,200); // key 중복때문에 roll back 됨
            tm.commit(status);

        } catch (Exception e) {
            e.printStackTrace();
            tm.rollback(status);
        } finally {

        }
    }
}

 

root-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:mvc="http://www.springframework.org/schema/mvc"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns="http://www.springframework.org/schema/beans"
          xmlns:context="http://www.springframework.org/schema/context"
          xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
      http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">


   <!-- Root Context: defines shared resources visible to all other web components -->
   <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
      <property name="url" value="jdbc:mysql://localhost:3306/springbasic?useUnicode=true&amp;characterEncoding=utf8"></property>
      <property name="username" value="back"></property>
      <property name="password" value="rhrlekd1+"></property>
   </bean>

   <!-- com ( component-scan ) -->
   <!-- 패키지 안에 있는 클래스 중에서 @Component 붙은것을 찾아서 Bean 으로 등록 -->
   <context:component-scan base-package="com.test.ch3" >
      <context:exclude-filter type="regex" expression="com.test.ch3.dpCopy*.*"/>
   </context:component-scan>
</beans>

 

 

결과 :  a1Dao.insert(1,100);  /  a1Dao.insert(1,200);   [  key 중복때문에 roll back 됨 ] 


 

3. @Transactional 의 속성

https://hahagogo.tistory.com/166

 

Transaction, Commit , Rollback

Transaction : 더이상 나눌수 없는 작업의 단위 속성 : ACID    원자성(A) : 나눌수 없는 하나의 작업으로 다뤄져야    일관성(C) : 수행 전 후 가 일관된 상태가 되어야    고립성(I) : 각 트랜젝션은

hahagogo.tistory.com

propagation Tx의 경계 설정하는 방법 지정
isolation Tx의 isolation level을 지정
readOnly Tx이 데이터를 읽기만 하는 경우 true
rollbackFor 지정된 예외가 발생하면 Tx rollback
RuntimeException과 Error는 자동 rollback
noRollbackFor 지정된 예외가 발생해도 Tx를 rollback 하지 않음
timeout 지정된 시간(초) 내에 Tx 종료 안되면 강제 종료

 

A1Dao

@Repository
public class A1Dao {

    @Autowired
    DataSource ds;

    public int insert(int key , int value) throws Exception{
        Connection conn = null;
        PreparedStatement pstmt = null;

        try {
            // conn = ds.getConnection(); // transaction 1개
            conn = DataSourceUtils.getConnection(ds);  // transaction 여러개
            System.out.println("conn = " + conn);

            pstmt = conn.prepareStatement("insert into a1 values(?, ?)");

            pstmt.setInt(1,key);
            pstmt.setInt(2,value);
            return pstmt.executeUpdate();

        } catch (SQLException e) {
            e.printStackTrace();
            throw e;
        } finally {
            // close(conn, pstmt); // transaction 1개
            close(pstmt);                               // transaction 여러개일때
            DataSourceUtils.releaseConnection(conn,ds); // transaction 여러개일때
        }
    }

    private void close(AutoCloseable... acs) {
        for(AutoCloseable ac :acs)
            try { if(ac!=null) ac.close(); } catch(Exception e) { e.printStackTrace(); }
    }

    public void deleteAll() throws Exception{
        Connection conn = ds.getConnection();
        String sql = "delete from a1";
        PreparedStatement pstmt = conn.prepareStatement(sql);
        pstmt.executeUpdate();
        close(conn,pstmt);
    }
}

 

TxService 

    @Transactional(rollbackFor = Exception.class) // Exception Error rollback

    @Transactional //  RuntimeException , Error만 rollback

 

@Service
public class TxService {
    @Autowired A1Dao a1Dao;
    @Autowired B1Dao b1Dao;

    public void insertA1WithoutTx() throws  Exception {

        a1Dao.insert(1,100);
        a1Dao.insert(1,200);
    }

    @Transactional(rollbackFor = Exception.class) // Exception Error rollback
    public void insertA1WithTxFail() throws  Exception {

        a1Dao.insert(1,100); 
        a1Dao.insert(1,200);
    }

    @Transactional //  RuntimeException , Error만 rollback
    public void insertA1WithTxSuccess() throws  Exception {

        a1Dao.insert(1,100);
        a1Dao.insert(2,200);
    }
}

 

TxServiceTest

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/**/root-context.xml"})
public class TxServiceTest {

    @Autowired TxService txService;

    @Test
    public void insertA1WithoutTxTest() throws Exception {
        //txService.insertA1WithoutTx();
        //txService.insertA1WithTxSuccess();
        txService.insertA1WithTxFail();
    }
}
728x90