You will be fine

<토비의스프링> 5.1~5.2 서비스추상화

by BFine
반응형

5.1 사용자레벨 관리 기능 추가

  • 레벨 관리 기능 추가

    • 사용자 레벨을 BASIC, SILVER, GOLD 설정
      1. DB로 처리하는 방법
        • 비효율적
      2. 상수형태로 처리하는 방법
        • 의미없는 숫자를 프로퍼티에 사용할 경우 타입 문제발생
      3. Enum 활용
        • 타입 안전성 (DB에 insert할 경우에는 지정한타입으로 변경 )
  • 사용자 수정 기능 추가

    • 기본키인 id를 제외한 나머지필드는 수정될 가능성이 있다.

    • 수정 기능 테스트 추가

      • 원하는 사용자 외의 정보는 변경 되지 않았음을 직접확인

      • where을 잊어버릴경우 문제가 될 수 있음

        @Test
          public void update() {
              dao.deleteAll();
        
              dao.add(user1);        
              dao.add(user2);    
        
              user1.setName("오민규");
              user1.setPassword("springno6");
              user1.setLevel(Level.GOLD);
              user1.setLogin(1000);
              user1.setRecommend(999);
        
              dao.update(user1);
        
              User user1update = dao.get(user1.getId());
              checkSameUser(user1, user1update);
              User user2same = dao.get(user2.getId());
              checkSameUser(user2, user2same);
          }
  • 코드 개선
    1. 코드에 중복된 건 없는지
    2. 이해하기 불편하지 않은지
    3. 자신이 있어야할 자리에 있는지
    4. 변화에 쉽게 대응할 수 있는지
if(user.getLevel() == Level.BASIC) && user.getLogin() >= 50{
    // 새로운 레벨이 추가될 경우 enum 수정 및 계속 늘어남
  user.setLevel(level.SILVER);
  changed = true; // 아래를 위한 플래그
}
...
if(changed){...}
  • 레벨 분기
public static Level valueOf(int value) {
        switch(value) {
        case 1: return BASIC;
        case 2: return SILVER;
        case 3: return GOLD;
        default: throw new AssertionError("Unknown value: " + value);
        }
    }
  • enum에 레벨 값을 담도록 수정
public enum Level {
    GOLD(3, null), SILVER(2, GOLD), BASIC(1, SILVER);

5.2 트랜잭션 서비스 추상화

  • 서버에 문제가 생긴다면 사용자 레벨을 어떻게 할 것 인가?! → 모든 변경작업 취소

  • 테스트 시나리오

    1. 5명의 사용자 정보를 DB에 추가
    2. 수행하다가 중간에 예외 발생
  • 모든 사용자 레벨을 업그레이드 하는 작업을 하나의 트랜잭션 안에 두어야 한다.

    • 트랜잭션은 더이상 나눌수없는 작업의 단위를 의미한다.
    • 전체가 성공하거나 전체가 실패해야한다.
  • 트랜잭션 경계설정

    • 트랜잭션이 시작되고 끝나는 위치를 트랜잭션 경계라고 부른다.

      
      Connection c = dataSource.getConnection(); 
      c.setAutoCommit(false); 
      // 하나의 트랜잭션
      try { 
        PreparedStatement st1 = c.prepareStatement("...");
        st1.executeUpdate(); 
        PreparedStatement st2 = c.prepareStatement("..."); 
        st2.executeUpdate(); 
      
        c.commit(); // 성공
      } catch (Exception e) { 
        c.rollback(); // 실패
      }
    • 하나의 커넥션이 만들어지고 닫히는 범위 안에 존재

    • 하나의 DB커넥션안에서 만들어지는 트랜잭션을 로컬트랜잭션이라고 한다.

  • UserService와 UserDao의 트랜잭션 문제

    • 트랜잭션 경계설정 코드가 존재하지 않음

      • upgrade 메서드를 개별적으로 호출 → 각각의 트랜잭션을 가짐
    • 해결방법

      1. upgradeLevels 메소드의 내용을 옮기기(X)

        • 비지니스로직과 데이터로직을 한데 묶는 결과
        • UserService와 UserDao를 그대로 둔체로 트랜잭션 적용해야함
      2. 트랜잭션 경계를 upgradeLevels 두기(+ DB 커넥션 )

        class UserService { 
             public void upgradeLevels() throws Exception {
                     Connection c = ...; 
        
             try { 
                   ...
                                 upgradeLevel(c, user);
                   ...
                     } 
        }
        
        protected void upgradeLevel(Connection c, User user) { ... }
        
      • 문제점
        • JdbcTemplate을 더이상 활용 못함
        • UserService에 Connection 파라미터 추가돼야함
        • UserDao는 독립적이지 않게 된다.
  • 트랜잭션 동기화

    • UserService에서 트랜잭션을 시작하기 위해 만든 Connection 오브젝트를 저장소에 보관해두고 이후 호출되는 Dao의 메소드에서는 저장된 Connection을 가져다가 사용하게 하는 것

      public void upgradeLevels() throws Exception {
        // 트랜잭션 동기화 관리자를 이용해 동기화 작업을 초기화 한다.
           TransactionSynchronizationManager.initSynchronization();
        Connection c = DataSourceUtils.getConnection(dataSource);
        c.setAuthCommit(false);
      
        try {
            List<User> users = UserDao.getAll();
            for (User user : users) {
                if (canUpdatedLevel(user)) {
                    upgradeLevel(user);
                }
            }
            c.commit();
        } catch (Exception e) {
            c.rollback();
            throw e;
        } finally {
            DataSourceUtils.releaseConnection(c, dataSource);
              TransactionSynchronizationManager.unbindResource(this.dataSource);
            TransactionSynchronizationManager.clearSynchronization();
        }
      }
    • 스프링이 제공하는 DataSourceUtils 사용

      • 커넥션 오브젝트 생성 뿐 아니라 트랜잭션 동기화 사용하도록 저장소에 바인딩해줌
    • JdbcTemplate은 동기화를 확인하여 저장소에 있는 DB 커넥션을 사용한다.

  • 트랜잭션 서비스 추상화

    • 한개 이상의 DB로의 작업을 하나의 트랜잭션으로 만드는 건 로컬트랜잭션으로 불가능

      • 로컬트랜잭션은 하나의 DB 커넥션에 종속
    • 여러 DB를 사용할 경우 글로벌 트랜잭션을 적용해야한다. → Java의 JTA API

      InitialContext ctx = new InitialContext();
      UserTransaction tx = (UserTransaction) ctx.lookup(USER_TX_JNDI_NAME);// JNDI
      
      tx.begin(); 
      Connection c = dataSource.getConnection(); 
      
      try {
         tx.commit();
      } catch (Exception e) {
      
             tx.rollback();
             throw e;
      
      } finally {
                c.close();
      }
      
    • UserService의 코드를 수정해야하는 문제점

  • 트랜잭션 API의 의존관계 문제와 해결책

    • 스프링의 트랜잭션 서비스 추상화

      class UserService { 
        private PlatformTransactionManager transactionManager;
                = new DataSourceTransactionManager(dataSource);
      
      TransactionStatus ts = transactionManager.getTransaction
                                                                                            (new DefaultTransactionDefintion());
      try{
        ... // 작업
      transactionManager.commit(ts);
      }catch{
        transactionManager.rollback(ts);
      }
    • DataSourceTransactionManager 는 JdbcTemplate 에서 사용될수 있게 트랜잭션 관리해줌

반응형

블로그의 정보

57개월 BackEnd

BFine

활동하기