Skip to content

[4단계 - JDBC 라이브러리 구현하기] 조조(조은별) 미션 제출합니다.#816

Merged
cutehumanS2 merged 26 commits intowoowacourse:eun-byeolfrom
eun-byeol:step4
Oct 20, 2024
Merged

[4단계 - JDBC 라이브러리 구현하기] 조조(조은별) 미션 제출합니다.#816
cutehumanS2 merged 26 commits intowoowacourse:eun-byeolfrom
eun-byeol:step4

Conversation

@eun-byeol
Copy link
Copy Markdown

@eun-byeol eun-byeol commented Oct 12, 2024

냥인 안녕하세요~~😸
4단계 구현 완료했고, aop 학습테스트는 곧 빠르게 올리겠습니다!

주요 고민과 변경사항이에요.

1. Service 레이어에서 사용하는 DataSource를 어디서 가져올 것인가?

지난 3단계 질문이었죠! 결론적으로는 DataSourceConfig.getInstance()로 가져오도록 수정했어요.
이유:

  • 프로덕션 코드가 이미 DataSourceConfig 의존적이라고 생각해요. 서비스가 되는 동안 한번 정해진 dataSource 설정들은 변하지 않구요. static이라 외부 주입이 안 된다는 단점이 있지만, 외부 모듈인 JdbcTemplate 생성할 때를 제외하고는 외부 주입할 필요가 없더라구요.
  • LMS 수정된 testTransactionRollback() 테스트에서 힌트를 얻었어요. TxUserService 생성 시 인자로 AppUserService만 받고 있더라구요. 물론 AppUserService에서 getDataSource()를 만들어 사용했을 수도 있지만요~ㅎㅎ

2. Connection 재사용 시, Dao에서 JdbcTemplate으로 Connection을 넘겨줄 필요가 있을까?

LMS에 이런 요구사항이 있었어요. 서비스와 DAO에서 Connection 객체를 가져오는 부분은 DataSourceUtils를 사용하도록 수정하자.
DAO에서 Connection 객체를 가져온다는 의미인데, 결론적으로는 JdbcTemplate에서 Connection 가져오도록 수정했어요.
주석을 붙인 (1),(2) 모두 JdbcTemplate에서 처리할 수 있는 로직이에요. Dao가 알 필요 없다고 생각했어요.

before:

    @Override
    public void update(User user) {
        String sql = "update users set password = ? where id = ?";
        DataSource dataSource = jdbcTemplate.getDataSource(); // (1) JdbcTemplate 메서드 호출
        Connection conn = DataSourceUtils.getConnection(dataSource); // (2) JdbcTemplate과 같은 패키지
        jdbcTemplate.update(conn, sql, user.getPassword(), user.getId());
    }

after:

    @Override
    public void update(User user) {
        String sql = "update users set password = ? where id = ?";
        jdbcTemplate.updateWithActiveConn(sql, user.getPassword(), user.getId());
    }

그럼 이번에도 잘 부탁드립니다~💌

@eun-byeol eun-byeol requested a review from cutehumanS2 October 12, 2024 15:11
@cutehumanS2
Copy link
Copy Markdown
Member

결론적으로는 DataSourceConfig.getInstance()로 가져오도록 수정했어요.

→ 좋습니다. 👍 적절한 근거만 있다면 🆗.


DAO에서 Connection 객체를 가져온다는 의미인데, 결론적으로는 JdbcTemplate에서 Connection 가져오도록 수정했어요.

→ 맞는 방향 같아요. before 처럼 DAO 메서드에서 jdbcTemplate의 메서드로 커넥션을 넘겨주라는 의도는 아니라고 생각합니다. 😸

Copy link
Copy Markdown
Member

@cutehumanS2 cutehumanS2 left a comment

Choose a reason for hiding this comment

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

조조~.~ 1등으로 제출하셨네요. 😸 진짜 머찌당 😆
마지막 미션 정말 잘 구현해 주셔서 감탄하고 갑니다. ㅎ ㅎ

몇 가지 코멘트 남겨 두었으니,
역시나 조조가 보시기에 필요한 부분만 반영하면 될 것 같아요.

진짜 얼마 안 남았어요. . 파이팅이에요 !!!
남은 주말도 즐(거운) 주(말) 🖤

Comment thread jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java Outdated
Comment thread app/src/main/java/com/techcourse/service/TxUserService.java Outdated
Comment thread app/src/main/java/com/techcourse/service/TxUserService.java Outdated
Comment thread jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java
@eun-byeol
Copy link
Copy Markdown
Author

eun-byeol commented Oct 14, 2024

냥인😼 리뷰 반영 후 다시 요청드려요!
aop 학습테스트가 생각보다 까다로웠네요~😤
추가된 커밋입니다~ 리뷰에 참고해주세요

추가 변경 사항이 있어요

TxUserService 내 Tx 공통 로직 TransactionExecutorUtils로 분리

다른 Tx이 생길 때마다, executeInTransaction() 중복 로직이 생겨서 범용적인 사용을 위해 util로 분리했어요

TransactionExecutorUtils 메서드

  • void executeInTransaction(Runnable callback); : 트랜잭션 실행, 반환값 없음
  • <T> T executeInTransaction(Supplier<T> callback); : 트랜잭션 실행, 반환값 있음
  • void releaseActiveConn(); : 활성화된 Connection 있으면 release

이제 정말 얼마 안 남았네요! 마지막 리뷰까지 잘 부탁드려요~
오늘도 화이팅!!🍀

Copy link
Copy Markdown
Member

@cutehumanS2 cutehumanS2 left a comment

Choose a reason for hiding this comment

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

조조 ! 빠른 시간 안에 학습 테스트와 리뷰 반영까지 완료하셨네요.
역시 머찐 사람. 😎

반영해 주신 사항 모두 확인했고, 학습 테스트에 몇 가지 질문 남겼습니다. ㅎ.ㅎ
제가 전에 남겼던 요상한 질문과 ‘트랜잭션이 적용되지 않은 경우의 커넥션 처리’ 부분에 대해서도
좀 까다로웠을 텐데 정말 잘 구현해 주셨더라고요. 👍

그런데…!…!….. 제가 질문드리긴 했지만 아직 해당 부분에 대한 이해도가 낮아서 많이 헷갈리네요. 
왠지 더 좋은 구조로 만들 수 있을 거 같으면서도 또 모르겠고 그런 상태입니다.. 😔
조오금만 더 공부하고 다시 한번 코드 봐도 될까요? ? 🥺 

+) 요구사항은 이미 만족되었고, 코멘트도 질문이 대부분이라
다음 리뷰 요청 때 이만 머지해도 될 것 같아요.
조조의 생각은 어떠신가요~~? 답변은 디엠으로 받겠습니다 !
조금 더 코멘트를 나눠 보고 싶다면 🖤를, 이젠 놓아주고 싶다면 🤍를 남겨주세요.

Comment on lines +21 to +26
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
autoProxyCreator.setProxyTargetClass(true);
return autoProxyCreator;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[질문]
setProxyTargetClass(true)는 왜 해줘야 하는 걸까요? ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

true로 설정해서, 프록시 생성 전략을 CGLib로 바꿀 수 있어요.

디폴트 값인 JDK Proxy를 사용하는 생성 전략은
(1) 인터페이스를 반드시 생성해야 하고,
(2) 메서드 레벨에서만 생성 가능한 불편함 있어요.

반면 CGLib를 사용하면 인터페이스를 만들지 않고 클래스에서 프록시 생성이 가능합니다.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

👍😸

}

@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[질문]
DefaultAdvisorAutoProxyCreator의 역할은 뭔가요~?
스프링 빈으로 등록하면 어떤 일이 가능해지나요?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

이름처럼, 자동적으로 프록시를 생성해주는 역할입니다. 스프링 빈 객체 생성시마다 DefaultAdvisorAutoProxyCreator에게 빈을 전달합니다.
image
출처

동작:

  1. 빈으로 등록된 Advisor를 찾는다.
  2. AdvisorPointcut에 적용되는 대상(클래스와 메서드)를 찾는다.
  3. 대상이면 프록시를 만들고, Advisor를 연결한다.
  4. 프록시 생성 후, 프록시 객체를 컨테이너에 돌려준다.
  5. 컨테이너는 받은 프록시 객체를 빈으로 등록한다.

이점:
매번 ProxyFactoryBean 통해 프록시 만드는 번거로움을 해소할 수 있다.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

👍👍 정리해 보자면, DefaultAdvisorAutoProxyCreator는 자동 프록시 생성 빈 후처리기로, 일일이 ProxyFactoryBean 빈을 등록하지 않아도 타깃 오브젝트에 자동으로 프록시가 적용되게 할 수 있습니다. 번거로움을 덜어주는 존재죠!

Comment thread study/src/test/java/aop/stage1/TransactionPointcut.java

TransactionPointcut pointcut = new TransactionPointcut();
TransactionAdvice advice = new TransactionAdvice(platformTransactionManager);
proxyFactoryBean.addAdvisor(new TransactionAdvisor(pointcut, advice));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[질문]
proxyFactoryBean에는 addAdvice()라는 어드바이스를 등록하는 메서드가 있습니다.
포인트 컷도 어드바이스처럼 그냥 등록할 수도 있었을 텐데,
왜 굳이 Advisor라는 별개의 오브젝트로 묶어서 등록하게 되어 있을까요??

Copy link
Copy Markdown
Author

@eun-byeol eun-byeol Oct 19, 2024

Choose a reason for hiding this comment

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

addAdvice() 사용하지 않고, advisor를 사용한 이유?

advice만 추가하면, 모든 빈을 대상으로 advice가 적용됩니다. 내부적으로 DefaultPointcutAdvisor를 통해 advisor를 생성해서 등록해주고 있었습니다.

public DefaultPointcutAdvisor(Advice advice) {
    this(Pointcut.TRUE, advice);
}

궁금했던 부분이었는데, 덕분에 공부가 되었어요👍

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

👍👍 

제가 읽은 토비의 스프링이라는 책에서는 아래와 같이 나와 있었어요.

프록시 팩토리 빈에는 여러 개의 어드바이스와 포인트컷이 추가될 수 있는데, 포인트컷과 어드바이스를 따로 등록하면 어떤 어드바이스에 대해 어떤 포인트컷을 적용할지 애매해지기 때문이다. 그래서 이 둘을 Advisor 타입의 오브젝트에 담아서 조합을 만들어 등록하는 것이다. 여러 개의 어드바이스가 등록되더라도 각각 다른 포인트컷과 조합될 수 있기 때문에 각기 다른 메소드 선정 방식을 적용할 수 있다.

위 내용만 읽고 아 확실히 애매하겠네 정도로만 넘어갔는데,
DefaultPointcutAdvisor가 모든 빈을 대상으로 어드바이스가 적용되도록 구현되어 있었군요.

조조 덕분에 저도 또 하나 배워갑니다. 😼

Comment thread study/src/test/java/aop/stage0/Stage0Test.java Outdated
@eun-byeol
Copy link
Copy Markdown
Author

냥인~ 리뷰 반영이 많이 늦었죠😿
빨리 반영하고 싶은 마음은 굴뚝 같았는데.. 일정이 쉽지 않았어요. 똑같이 바빴을텐데 냥인은 컨트롤을 잘 하고 있는 것 같아요! 역시 멋진 냥인..👍

냥인 리뷰 받을 때마다 매번 놀라요. 그만큼 깊이 있는 학습을 하신거겠죠? 좋은 질문을 많이 주셔서, 덕분에 좋은 방향으로 개선하고 공부하고 있어요🙇‍♀️

추가적으로 개선한 부분이 있어요.

✅ 커넥션이 열려 있는지 판단하는 책임을 JdbcTemplate으로 이전

before:

// UserDaoImpl
    @Override
    public void update(User user) {
        String sql = "update users set password = ? where id = ?";
        jdbcTemplate.updateWithActiveConn(sql, user.getPassword(), user.getId());
    }

userDao에서 트랜잭션을 사용하는지, 하지 않는지 알아야 했어요. 이 점이 굉장히 불편했어요.
트랜잭션을 사용하는 상황인데(=커넥션이 열려 있는 상황), 커넥션을 새로 여는 JdbcTemplate 메서드를 호출하게 되는 상황도 충분히 발생할 수 있었어요.

after:

// UserDaoImpl
    @Override
    public void update(User user) {
        String sql = "update users set password = ? where id = ?";
        jdbcTemplate.update(sql, user.getPassword(), user.getId());
    }

UserDao에선 알 필요 없이,jdbcTemplate.update()만 호출하면 알아서 처리하도록 했어요.

TxUserService를 거치지 않고 AppUserSerivce를 바로 호출하는 경우 커넥션이 닫히지 않는다

앞서 이야기했던 이런 문제도 발생하지 않아요.
JdbcTemplate이 알아서
(1) 트랜잭션 비활성화 -> 새로운 커넥션을 생성하고 닫아주고
(2) 트랜잭션 활성화 -> 열려있는 커넥션을 사용하고 닫지 않게 했어요.

// JdbcTemplate
    private <T> T execute(String sql, PreparedStatementExecutor<T> executor, Object... params) {
        Connection connection = TransactionSynchronizationManager.getResource(dataSource);
        if (connection == null) {
            return executeWithNewConn(sql, executor, params); // (1)
        }
        Connection aliveConn = DataSourceUtils.getConnection(dataSource);
        return executeWithConn(aliveConn, sql, executor, params); // (2)
    }

이번 변경사항입니다!💌

Copy link
Copy Markdown
Member

@cutehumanS2 cutehumanS2 left a comment

Choose a reason for hiding this comment

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

조조 ~~ ⭐️ 안녕하세요. 냥인입니다.

많이 바쁘시죠. ㅜ.ㅜ
그럼에도 열심히 리뷰 반영해 주셔서 정말 감사합니다.
저 또한 조조가 제 리뷰에 남기신 답변을 볼 때마다 놀랐답니다 !
제가 기대하는 답변보다 훨씬 깊이 있는 답변을 남겨주셨을 때가 꽤 있었거든요. 👏

말씀해 주신 대로 이제 정말로 머지해도 될 것 같은데요,
혹시 몰라 approve만 합니다…ㅎㅎ…
⭐️ < 요 이모지 있는 코멘트 한 번 확인하시고, 다시 리뷰 요청 주시면 바로 머지할게요!

정말 수고 많으셨습니다.
조조의 리뷰어여서 (너어어어어무) 좋았습니당 🤍🤍

Comment on lines +45 to +47
public static void releaseActiveConn() {
DataSourceUtils.releaseActiveConnection(DataSourceConfig.getInstance());
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[⭐️]
엇 근데 이 메서드는 테스트에서만 쓰이고 있네요...?

이 메서드를 사용하지 않아도 try-with-resource에 의해 커넥션은 닫히는 것 같아서,
변경 필수는 아닌데 그래도 인지하고 가면 좋을 것 같아 언급합니다 !

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

(반영 완료) 테스트에서만 사용한 TransactionExecutorUtils.releaseActiveConn(), DataSourceUtils.releaseActiveConnection() 정리했습니다!
TransactionExecutorUtilsTestreleaseActiveConn 동작을 검증하는 테스트케이스가 있었는데, 불필요해져서 삭제했어요~

}

@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

👍👍 정리해 보자면, DefaultAdvisorAutoProxyCreator는 자동 프록시 생성 빈 후처리기로, 일일이 ProxyFactoryBean 빈을 등록하지 않아도 타깃 오브젝트에 자동으로 프록시가 적용되게 할 수 있습니다. 번거로움을 덜어주는 존재죠!

Comment on lines +21 to +26
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
autoProxyCreator.setProxyTargetClass(true);
return autoProxyCreator;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

👍😸


TransactionPointcut pointcut = new TransactionPointcut();
TransactionAdvice advice = new TransactionAdvice(platformTransactionManager);
proxyFactoryBean.addAdvisor(new TransactionAdvisor(pointcut, advice));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

👍👍 

제가 읽은 토비의 스프링이라는 책에서는 아래와 같이 나와 있었어요.

프록시 팩토리 빈에는 여러 개의 어드바이스와 포인트컷이 추가될 수 있는데, 포인트컷과 어드바이스를 따로 등록하면 어떤 어드바이스에 대해 어떤 포인트컷을 적용할지 애매해지기 때문이다. 그래서 이 둘을 Advisor 타입의 오브젝트에 담아서 조합을 만들어 등록하는 것이다. 여러 개의 어드바이스가 등록되더라도 각각 다른 포인트컷과 조합될 수 있기 때문에 각기 다른 메소드 선정 방식을 적용할 수 있다.

위 내용만 읽고 아 확실히 애매하겠네 정도로만 넘어갔는데,
DefaultPointcutAdvisor가 모든 빈을 대상으로 어드바이스가 적용되도록 구현되어 있었군요.

조조 덕분에 저도 또 하나 배워갑니다. 😼

@eun-byeol
Copy link
Copy Markdown
Author

냥인~ 항상 빠르게 리뷰해주어 정말 감사합니다❤️

불필요한 메서드 정리하였고,
수정된 부분은 여기에서 확인해주세요~

덕분에 정말 즐겁게 미션 했어요 << 진짜 진짜 즐거웠어요
남은 미션과, 프로젝트 모두 응원할게요🍀
레벨5에서 꼭 봐요~☕️

@cutehumanS2 cutehumanS2 merged commit b9ee416 into woowacourse:eun-byeol Oct 20, 2024
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.

2 participants