Skip to content

6주차 #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open

6주차 #6

wants to merge 7 commits into from

Conversation

kyY00n
Copy link
Member

@kyY00n kyY00n commented Nov 10, 2022

No description provided.

fail("TetUserSericeException expected");
} catch (TestUserServiceException e) {
}
checkLevelUpgraded(users.get(1), false);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p353. 테스트는 실패한다.
모든 사용자의 레벨을 업그레이드하는 작업인 upgradeLevels() 메소드가 하나의 트랜잭션 안에서 동작하지 않았기 때문이다.
트랜잭션이란 더 이상 나눌 수 없는 단위 작업을 말한다. (트랜잭션의 핵심 속성인 원자성)

@kyY00n
Copy link
Member Author

kyY00n commented Nov 10, 2022

p. 353

레벨 업그레이드 작업은 부분적으로 성공하거나~ 여러 번에 걸쳐서 진행할 수 있는 작업이 아니어야한다.
더 이상은 쪼개질 수 없는 원자와같은 성질을 띤다.

작업별 트랜잭션 정의 👉🏻 더이상 쪼개질 수 없는지!
All or Nothing

@kyY00n
Copy link
Member Author

kyY00n commented Nov 10, 2022

UserDao는 JdbcTemplate을 통해 매번 새로운 DB 커넥션과 트랜잭션을 만들어 사용한다.

마찬가지로, upgradeLevel()에서 3번의 update()호출이 일어났다고 쳐보자. (3명의 user)
첫 번째 update()를 호출할 때 작업 성공시 트랜잭션이 종료되며 커밋이 되기 때문에
두 번째 update()를 호출하는 시점에서 오류 발생으로 작업이 중단돼도
첫 번째 트랜잭션의 결과는 DB에 그대로 남는다.

데이터 액세스 코드를 DAO로 만들어서 분리해놓았을 경우에는 이처럼 DAO 메서드를 호출할 때마다 하나의 새로운 트랜잭션이 만들어지는 구조가 될 수 밖에 없다. (jdbc api를 직접 사용하든, JdbcTemplate을 이용하든)
DAO 메서드에서 DB 커넥션을 매번 만들기 때문에 어쩔 수 없다.

// JdbcTemplate에서 sql을 실행할 때 사용하는 메서드
private <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action, boolean closeResources) throws DataAccessException {
    // ...
    Connection con = DataSourceUtils.getConnection(this.obtainDataSource());
    PreparedStatement ps = null;
    // ...
}

@kyY00n
Copy link
Member Author

kyY00n commented Nov 10, 2022

그럼?
DAO 메서드를 호출할 때마다 Connection 오브젝트를 파라미터로 전달해줘야한다..
우리가 원자적 기능으로 정의한 upgradeLevels() 내에서 트랜잭션을 정의해야하니, 그 안에서 호출하는 upgradeLevel()에도 커넥션을 인자로 전달해야한다.


UserService 트랜잭션 경계설정의 문제점

UserServiceUserDao를 이런 식으로 수정하면 트랜잭션 문제는 OK. 그런데 새로운 여러 문제가 발생한다.

  1. JdbcTemplate을 더 이상 활용할 수 없다.
    DB커넥션 등의 리소스의 깔끔한 처리를 가능하게 해주었던 아이를 쓸 수 없고, JDBC API를 직접 사용하는 초기방식으로 돌아가야한다. try-catch-finally 블록은 다시 UserService 내에 존재하게 된다.

  2. UserService는 비즈니스 로직을 담고있는 객체인데, upgradeLevels() 같이 원자적으로 동작하는 메서드에서 DAO를 필요로한다면, 그 안에서 호출되는 모든 메서드에 걸쳐 Connection 객체가 계속 전달돼야한다. (UserService) 는 싱글톤으로 돼있어서 이 Connection을 멤버변수로 저장해서 다른 메서드에서 사용하게 할 수도 없다. 멀티스레드 환경에서는 공유하는 인스턴스 변수에 스레드별로 생성하는 정보를 저장하다가는 서로 덮어쓰는 일이 발생하기 때문이다.

  3. Connection이 UserService에서 다뤄지면, UserService는 데이터 액세스 기술에 종속된다. 우리가 JPA나 하이버네이트로 방식을 변경하려고 하면 Connection 대신 EntityManager나 Session 오브젝트를 UserDao 메서드가 전달받도록 해야 한다. 그럼 UserService도 변경돼야하겠지. 기껏 DAO를 분리하고 인터페이스로 DI를 적용했던 수고가 물거품이 되는 것이다.

  4. 테스트코드도 영향을 받는다. 지금까지 UserService 테스트에서는 Connection은 전혀 신경쓰지않ㅇ르 수 있었는데, 이제 테스트코드에서 직접 Connection 오브젝트를 일일이 만들어서 다뤄야한다.

@kyY00n
Copy link
Member Author

kyY00n commented Nov 10, 2022

IMG_7FA2AD7E21A5-1
과정

  1. UserService가 Connection 생성
  2. TransactionSynchronizations 에 Connection을 저장 & Connection의 setAutoCommit(false) 호출
  3. UserService.update() 실행시, jdbcTemplate 메서드에서는 TransactionSynchronizations 에 Connection 객체가 존재하는지 확인
  4. UserService가 저장해놓은 Connection 객체를 가져온다.
  5. Connection으로 PreparedStatement 생성, SQL 실행 후 Connection 닫지 않고 작업 종료
  6. 두번째, 세번째 UserService.update() 는 TransactionSynchronizations 에 저장된 Connection 객체를 자져와 사용.
  7. UserService는 모든 작업이 정상적으로 끝났을 때, Connection.commit()을 호출하여 트랜잭션을 완료시킨
  8. TransactionSynchronizations에서 Connection 객체 제거.

+) 이 과정에서 예외가 발생하는 때에는 Connection.rollback()을 호출 후 동기화저장소에서 Connection 객체 제거.

트랜잭션 동기화 저장소는 작업 스레드마다 독립적으로 Connection 객체를 저장하고 관리하기 때문에 멀티스레드 환경에서도 충돌이 날 염려는 없다.

@kyY00n
Copy link
Member Author

kyY00n commented Nov 10, 2022

JdbcTemplate 은 TransactionSynchronizations 에 저장된 DB 커넥션/트랜잭션이 없는 경우에는 직접 db 커넥션을 만들고,
있는 경우에는 TransactionSynchronizations의 connection 객체를 가져와서 사용한다.

이렇게 우리가 따로 JdbcTemplate은 수정할 필요도, 다른 요청을 할 필요도 없으므로
트랜잭션 적용 여부(비즈니스 로직에서)가 바뀌더라도
UserDao 코드를 변경할 필요가 없다.


비즈니스 로직 레벨의 트랜잭션을 적용했지만 UserDao는 여전히 데이터 액세스 기술에 종속되지 않는다.
테스트에서 원래 DAO를 직접 호출해서 사용하는 코드들도 변경하지 않아도 된다.

PlatformTransactionManager 를 이용하여 데이터액세스계층에 대한 종속성을 제거했다.
datasource를 통해
각기 다른 transaction manager를 동작시킬 수 있다.
Copy link
Member Author

@kyY00n kyY00n left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스프링의 트랜잭션 서비스 추상화 (PlatformTransactionManager)

Comment on lines +31 to +36
protected void upgradeLevel(User user) {
if (user.getId().equals(this.id)) {
throw new TestUserServiceException();
}
super.upgradeLevel(user);
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UserService에서 protected 접근자로 수정한 덕분에 upgradeLevel을 원하는대로 변경할 수 있게 되었다.

c.setAutoCommit(false);
public void upgradeLevels() {
PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transaction을 시작한다는 의미라고 생각하자.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

트랜잭션이 TransactionStatus 타입의 변수에 저장된다.

try {
List<User> users = userDao.getAll();
for (User user : users) {
upgradeLevel(user);
}
c.commit();
transactionManager.commit(status);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

트랜잭션에 대한 조작이 필요할 때 PlatformTransactionManager의 메서드에 트랜잭션을 인자로 전달해주면 된당.

Comment on lines 49 to 52
} finally {
DataSourceUtils.releaseConnection(c, dataSource);
TransactionSynchronizationManager.unbindResource(this.dataSource);
TransactionSynchronizationManager.clearSynchronization();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

질문. 이런 건 transactionManager가 따로 안해줘도 되는건가

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

답변. 응 . commit() 이랑 rollback()에 relase 로직이 포함돼있어

PlatformTransactionManager 변수에 구현체가 바뀔때마다 코드가 변경됐었다.
구현 클래스의 변경에 열려있지만
변경되지 않는 코드를 작성하기 위해 UserService에 DI 해서 사용한다.
Copy link
Member Author

@kyY00n kyY00n left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이제 UserService는 트랜잭션 기술에서 완전히 독립적인 코드가 됐다.
DAO가 하이버네이트, JPA, JDO 등을 사용하도록 수정한다해도,
UserService의 코드는 수정할 필요가 없다.

Comment on lines +22 to +23
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

구현체를 바꾸고 싶으면 여기에서 클래스를 바꿔주면 된다.
ex) JTA 를 사용하고 싶은 경우

<bean id="transactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- ...빈 주입 -->
</bean>

이런식으로 바꾸면 된당.

@kyY00n
Copy link
Member Author

kyY00n commented Nov 10, 2022

수평적인 구분-애플리케이션 로직의 종류 이든, 수직적인 구분-로직과 기술 이든
스프링의 DI를 통해
결합도를 낮추어 서로 자유롭게 확장될 수 있는 구조가 될 수 있다.😃

dao의 add()와 update() 도 함께 수정
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant