토비의 스프링 Chapter 7.5.3 ~ 7.6.1 정리

종명·2021년 5월 18일
0

트랜잭션 적용

  • 하나 이상의 SQl을 맵으로 전달받아 한 번에 수정해야 하는 경우에는 자칫 문제가 심각한 문제가 발생할 수 있다.
  • 맵으로 SQL과 키의 쌍을 전달받은 updateSql() 메소드는 한 번에 한 개의 SQL을 수정해주는 같은 이름의 updateSql() 메소드를 맵 안에 있는 SQL의 개수만큼 반복해서 호출하도록 되어있다.
  • 그런데 여러 개의 SQL을 변경하는 작업을 진행한느 중에 존재하지 않는 키가 발견되면 어떻게 될까?
  • 이런 경우에는 예외가 발생하도록 되어 있으니 작업이 중단 될 것이다.
  • 문제는 updateSql() 메소드는 단순히 SimpleJdbcTemplate을 사용해 SQL을 실행하고 있으므로 트랜잭션이 적용되어 있지 않다는 점이다.

스프링에서 트랜잭션 적용 시 트랜잭션 경계가 DAO 밖에 있고 범위가 넓으면 AOP를 이용하는 것이 편리하다. 하지만 Sql 레지스트리라는 제한된 오브젝트 내에서 간단한 트랜잭션이 필요한 경우라면 AOP와 같이 거창한 방법보다는 간단히 트랜잭션 추상화 API를 직접 사용하는게 편리할 것이다.

다중 수정 SQL 수정에 대한 트랜잭션 테스트

  • 트랜잭션이 적용되면 성공하고 아니라면 실패하는 테스트를 만들자.
    @Test
    public void transactionalUpdate() {
        checkFind("SQL1", "SQL2", "SQL3"); // 초기 상태를 확인한다.
        
        Map<String, String> sqlmap = new HashMap<String, String>();
        sqlmap.put("KEY1", "Modified1");
        
        // 두 번째 SQL의 키를 존재하지 않는 것으로 지정한다. 
        // 이 때문에 테스트는 실패할 것이고 그때 과연 롤백이 일어났는지 테스트 한다.
        sqlmap.put("KEY9999!@#$", "Modified9999");
        try {
            sqlRegistry.updateSql(sqlmap);
            fail();
        }catch (SqlUpdateFailureException e) {  
        }
        // 첫번째 SQL은 정상적으로 수정했지만 트랜잭션이 롤백되기 때문에 다시 변경 이전 상태로 돌아와야 한다. 
        // 트랜잭션이 적용되지 않는다면 변경된 채로 남아서 테스트는 실패할 것이다.
        checkFindResult("SQL1", "SQL2", "SQL3");
    }

코드를 이용한 트랜잭션 적용

public class EmbeddedDbSqlRegistry implements UpdatableSqlRegistry {
    SimpleJdbcTemplate jdbc;
    
    // JdbcTemplate과 트랜잭션을 동기화해주는 트랜잭션 템플릿이다. 멀티스레드 환경에서 공유 가능하다.
    TransactionTemplate transactionTemplate;

    public void setDataSource(DataSource dataSource) {
        jdbc = new SimpleJdbcTemplate(dataSource);
        
        //DataSource로 TranscationManager를 만들고 이를 이용해 TranscationTemplate을 생성한다.
        transactionTemplate = new TranscationTemplate(
                new DataSourceTranscationManager(dataSource));
    }

    @Override
    public void updateSql(final Map<String, String> sqlmap) throws SqlUpdateFailureException {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            // 트랜잭션 템플릿이 만드는 트랜잭션 경계 안에서 동작할 코드를 콜백 형태로 만들고 
            // execute() 메소드에 전달한다.
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                for (Map.Entry<String, String> entry : sqlmap.entrySet()) {
                    updateSql(entry.getKey(), entry.getValue());
                }
            }
        });
    }
}

스프링 3.1의 DI

자바 언어의 변화와 스프링

  1. 애노테이션 메타정보 활용
  2. 정책과 관례를 이용한 프로그래밍

자바 코드를 이용한 빈 설정

테스트 컨텍스트의 변경

@RunWith(SpringJunit4ClassRunner.class)
// xml -> java 설정 파일로
@ContextConfiguration(classes=TestApplicationContext.class)
public class UserDaoTest {

<context:annotation-config /> 제거

  • <context:annotation-config />은 @PostConstruct 를 붙인 메소드가 빈이 초기화 된 후에 자동으로 실행되도록 사용했다.
  • <context:annotation-config />에 등록된 빈 후처리기가 @PostConstruct 와 같은 표준 애노테이션을 인식해서 자동으로 메서드를 실행해준다.
  • @Configuration을 사용하면 더 이상 <context:annotation-config />를 넣을 필요가 없다. 컨테이너가 직접 @PostConstruct 를 처리하는 빈 후처리기를 등록해주기 때문이다.

<bean>의 전환

  • <bean>은 @Bean이 붙은 public 메소드로 만들어 주면 된다. 메소드 이름은 <bean> 의 id 값으로 한다.
  • 빈의 구현 클래스가 바뀌면 참조하는 다른 빈의 코드도 변경해하기 때문에 리턴값은 구현클래스보다 인터페이스로 해야한다.
<bean id="dataSource" class="org.springboot.jdbc.datasource.SimpleDriverDataSource">
    <property name="driverClass" value="com.mysql.jdbc.mysql" />
    ...
</bean>
    @Bean
    public DataSource dataSource() {
        SimpleDriverDataSource dataSource = new SimpleDriverDataSource ();

        dataSource.setDriver(Driver.class);
        dataSource.setUrl("jdbc:mysql://localhost/springbook?characterEncoding=UTF-8");
        dataSource.setUsername("spring");
        dataSource.setPassword("book");
        return dataSource;
    }
  • XML로 정의된 transcationManager 빈을 TestApplicationContext 내의 메소드로 전환하면 다음과 같다.
<bean id="transactionManager" class="org.springboot.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>
    @Bean 
    public PlatformTranscationManager transcationManager() {
        DataSourceTransactionManager tm = new DataSourceTransactionManager();
        tm.setDataSource(dataSource());
        return tm;
    }

0개의 댓글