-
Notifications
You must be signed in to change notification settings - Fork 379
[4단계 - Transaction synchronization 적용하기] 미미(홍향미) 미션 제출합니다. #1196
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
Conversation
Walkthrough이 변경사항은 사용자 서비스 계층의 구조를 재구성합니다. UserService를 구체적인 클래스에서 인터페이스로 전환하고, 비즈니스 로직을 담당하는 AppUserService와 트랜잭션 처리를 담당하는 TxUserService로 분리합니다. DataSourceUtils를 도입하여 연결 관리를 중앙화하고, DAO 메서드에서 명시적인 Connection 파라미터를 제거합니다. JdbcTemplate이 자동으로 DataSourceUtils를 사용하여 연결을 관리하도록 리팩터링하고, 테스트에서 TransactionSynchronizationManager를 활용한 바인딩 기반 접근 방식을 도입합니다. Pre-merge checks❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (1)
study/src/test/java/transaction/stage1/Stage1Test.java (1)
83-88: 스레드 동기화·예외 전파 이슈는 앞선 코멘트와 동일합니다중복 피드백 방지를 위해 상세 제안은 생략합니다.
🧹 Nitpick comments (15)
study/src/test/java/transaction/stage1/Stage1Test.java (5)
43-46: 바인딩/해제의 예외 안전성 보강 제안수동 bind/unbind가 try/finally 없이 흩어져 있어 예외 시 리소스가 남을 수 있습니다. 바인딩 구간을 명확한 경계로 묶고, 해제·롤백·close 순서를 보장하도록 구조를 정리해보세요. 왜: 테스트 안정성과 누수 방지.
52-55: 서브 스레드 컨텍스트 바인딩 해제 타이밍 확인DAO 호출 직후 곧바로 unbind를 호출합니다. 예외 발생 시에도 반드시 해제되도록 finally 경계로 감싸는지 점검해보세요. 왜: 누수 및 다음 테스트 간 교란 방지.
75-80: 트랜잭션 경계와 바인딩 해제 순서바인딩 해제(unbind)를 트랜잭션 종료(커밋/롤백) 이후로 일관되게 배치하면 가독성과 안전성이 올라갑니다. 현재는 질의 후 바로 unbind하고 뒤늦게 rollback을 호출하는 패턴이 섞여 있습니다. 왜: 경계가 명확해야 오독·실수 감소.
Also applies to: 92-97
117-119: 팬텀 리드 시나리오의 관찰 지점 명료화repeatable read 기대 효과를 검증하려면 “첫 조회 스냅샷”과 “후속 변경” 사이의 순서를 확실히 보장해야 합니다. 현재도 의도는 맞지만 스레드 조인 없이 sleep에 의존합니다. 왜: DB/환경에 따라 타이밍 민감. 조인/래치 기반으로 순서를 고정하는 방향을 고려해보세요.
Also applies to: 130-136
138-139: 커넥션/데이터소스 종료 누락 가능성 점검rollback만 호출하고 커넥션 close는 보장되지 않습니다. 또한 HikariDataSource는 명시적으로 닫아야 합니다. 왜: 리소스 누수 방지. 테스트 후반부/후처리에서 종료를 보장할 방법을 고민해보셨나요?
app/src/main/java/com/techcourse/service/TxUserService.java (2)
13-16: 트랜잭션 매니저 의존성 주입으로 결합도 낮추기서비스 내부에서 DataSourceTransactionManager를 직접 생성하면 테스트/확장성이 떨어집니다. 왜: DIP 위반, 대체/설정 어려움. 생성자 주입(인터페이스 타입 권장)으로 바꾸는 걸 고려해보셨나요?
23-26: 쓰기(save)의 트랜잭션 적용 범위 확인save가 트랜잭션 없이 위임됩니다. 의도적으로 비트랜잭션인가요, 아니면 changePassword만 보호하려는 단계적 도입인가요? 도메인 불변성을 기준으로 save에도 경계가 필요한지 점검해보세요.
app/src/test/java/com/techcourse/service/UserServiceTest.java (2)
37-40: 파라미터 명명 일관성(createdBy)여기서는 createBy 변수를 사용하고, 다른 곳(인터페이스/도메인)은 createdBy로 보입니다. 왜: 용어 일관성은 읽기·검색성 향상. 테스트 변수명도 createdBy로 맞출지 검토해보세요.
48-54: 롤백 검증 시나리오가 명료합니다의도적 예외를 통해 DataAccessException을 기대하고, 이후 상태 불변을 확인하는 플로우가 좋습니다. 실패 원인과 롤백 경계를 설명하는 메시지 추가를 고려하면 테스트 문서성이 더 좋아집니다.
Also applies to: 56-60
app/src/main/java/com/techcourse/service/AppUserService.java (3)
19-22: 예외 메시지에 컨텍스트 보강"User not found"에 id를 포함하면 원인 파악과 로그 분석이 쉬워집니다. 왜: 운영 가시성 향상.
24-27: save의 의미적 경계 재고UserDao는 insert인데 서비스는 save로 추상화했습니다. 업데이트 시나리오 포함 여부와 불변성(중복 계정 등) 검증 위치를 어디로 둘지 결정해두면 확장 시 혼돈을 줄일 수 있습니다.
30-36: 도메인에 책임 더 밀기 + 원시값 포장 고려비밀번호와 작성자(createdBy)는 값 객체로 포장하면 검증·정책을 응집시킬 수 있습니다(예: Password, CreatedBy). 또한 히스토리 생성 책임을 서비스가 아닌 도메인(예: user가 자신의 이력을 생성/기록하도록)으로 이동하면 Tell, Don’t Ask가 강화됩니다.
app/src/main/java/com/techcourse/service/UserService.java (1)
5-9: 인터페이스 전환은 방향이 좋습니다 — 계약(트랜잭션/불변성) 문서화 고려메서드별 트랜잭션 경계(읽기/쓰기, 롤백 정책)와 도메인 제약(비밀번호 정책, 중복 방지)을 JavaDoc 등으로 명시하면 구현/프록시 간 기대치가 명확해집니다. 왜: 확장/테스트 용이성.
jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java (1)
19-42: 코드 중복에 대해 생각해볼 점이 있습니다.
TransactionSynchronizationManager를 보니 이미 동일한 시그니처의getConnection(DataSource)와releaseConnection(Connection, DataSource)메서드가 존재하네요.DataSourceUtils는 단순히 이 메서드들을 위임만 하고 있어서 코드 중복이 발생하고 있습니다.몇 가지 질문을 던져볼게요:
- 이 래퍼 계층이 제공하는 추가적인 가치는 무엇일까요?
- 만약 예외 변환(SQLException → CannotGetJdbcConnectionException)이 목적이라면, 왜
TransactionSynchronizationManager에서 직접 처리하지 않았을까요?- 두 클래스의 역할과 책임을 어떻게 구분하면 좋을까요?
생각해볼 점:
TransactionSynchronizationManager: 트랜잭션 동기화를 위한 저수준 상태 관리DataSourceUtils: 애플리케이션 레벨의 Connection 획득/반환 API만약 역할이 명확히 구분된다면, 각 클래스의 책임 범위를 고려해 어떤 메서드를 어디에 두는 것이 더 적절할지 고민해보세요.
study/src/test/java/transaction/stage1/UserDao.java (1)
17-25: 객체에게 묻지 말고 시키세요 (Tell, Don't Ask)
insert와update메서드를 보면 User 객체에게 데이터를 하나하나 꺼내서(get) 사용하고 있네요. 이는 **객체지향 생활 체조 원칙 9번(게터/세터 금지)**과 관련이 있습니다.생각해볼 질문들:
- User 객체가 자신의 데이터를 가지고 있는데, 왜 밖에서 데이터를 꺼내서 처리해야 할까요?
- 만약 User가 "나를 저장하는데 필요한 값들을 배열로 줄게"라고 말할 수 있다면 어떨까요?
PreparedStatement에 파라미터를 설정하는 책임은 누구에게 있는 게 더 자연스러울까요?힌트: User 객체가 스스로 자신을 표현할 수 있는 방법을 제공한다면, DAO는 User에게 **"어떻게 저장되고 싶은지 알려줘"**라고 요청할 수 있습니다.
📖 이 원칙이 중요한 이유: 진정한 캡슐화는 데이터를 숨기는 것뿐만 아니라, 객체가 자신의 데이터를 활용한 행동을 스스로 결정하도록 하는 것입니다.
Based on learnings
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
app/src/main/java/com/techcourse/service/AppUserService.java(1 hunks)app/src/main/java/com/techcourse/service/TxUserService.java(1 hunks)app/src/main/java/com/techcourse/service/UserService.java(1 hunks)app/src/test/java/com/techcourse/service/UserServiceTest.java(2 hunks)jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java(1 hunks)study/src/test/java/transaction/stage1/Stage1Test.java(2 hunks)study/src/test/java/transaction/stage1/UserDao.java(1 hunks)study/src/test/java/transaction/stage1/jdbc/JdbcTemplate.java(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java
⚙️ CodeRabbit configuration file
**/*.java: ## 🎯 코드 품질 중심 리뷰 가이드라인이 리뷰는 코드 품질과 객체지향 원칙에 집중합니다.
미션 달성 여부가 아닌, 코드 설계와 품질 개선에 대한 피드백을 한글로 제공해주세요.📚 학습 원칙
- 직접 코드를 제공하지 마세요 (학습자가 명시적으로 요청하는 경우 제외)
- 문제 해결 과정을 안내하되, 정답을 바로 알려주지 마세요
- 작은 단계로 문제를 분해하여 접근하도록 도와주세요
💡 피드백 방법
- 유도 질문 활용: "만약 ~라면 어떻게 될까요?", "~를 고려해보셨나요?"
- 힌트 제공: 방향은 제시하되, 구체적인 구현은 학습자가 하도록
- 다양한 접근법 제시: 한 가지 해결책이 아닌 여러 가능성을 제안
- 왜?에 집중: 단순히 무엇이 잘못되었는지보다 왜 그런지 이해하도록
⚡ 객체지향 생활 체조 원칙 검토
다음은 객체지향 생활 체조(Object Calisthenics) 9가지 원칙입니다.
위반 시 학습 효과를 위해 반드시 피드백을 제공하되, 왜 이 원칙이 중요한지 설명해주세요:규칙 1: 한 메서드에 오직 한 단계의 들여쓰기만
- 들여쓰기 depth 최대 1 (중첩 제어구조 금지)
- 📖 이유: 메서드 복잡도 감소, 단일 책임 원칙 강화
- 💡 힌트: "이 부분을 별도 메서드로 추출하면 어떨까요?"
규칙 2: else 예약어 금지
- else, switch/case 사용 금지
- 📖 이유: 복잡한 분기 제거, 명확한 코드 흐름
- 💡 힌트: "early return이나 가드 클로즈 패턴을 고려해보세요"
규칙 3: 모든 원시값과 문자열 포장
- 원시 타입과 String을 객체로 포장
- 📖 이유: 도메인 개념 명확화, 비즈니스 로직 응집
- 💡 예시:
int port→Port port,String name→Name name규칙 4: 한 줄에 점 하나만 (디미터 법칙)
- 메서드 체이닝 제한
- 📖 이유: 결합도 감소, 캡슐화 향상
- 💡 나쁜 예:
request.getUri().getPath().substring()- 💡 좋은 예:
request.extractPath()규칙 5: 축약 금지
- 명확한 이름 사용 (축약어 금지)
- 📖 이유: 코드 가독성, 의도 명확화
- 💡 예시:
req→request,calcAmt→calculateAmount규칙 6: 모든 엔티티를 작게 유지
- 클래스 50줄, 메서드 10줄 이하
- 📖 이유: 단일 책임, 이해와 테스트 용이성
- 💡 힌트: "이 클래스가 너무 많은 일을 하고 있지 않나요?"
규칙 7: 인스턴스 변수 3개 이하
- 클래스당 최대 3개의 인스턴스 변수
- 📖 이유: 높은 응집도, 단일 책임 유지
- 💡 힌트: "관련 필드들을 별도 객체로 묶을 수 있을까요?"
규칙 8: 일급 컬렉션 사용
- 컬렉션을 감싸는 클래스 사용
- 📖 이유: 컬렉션 로직 캡슐화, 불변성 보장
- 💡 예시:
List<HttpHeader>→HttpHeaders클래스규칙 9: 게터/세터/프로퍼티 금지
- Tell, Don't Ask 원칙 준수
- 📖 이유: 진정한 캡슐화, 객체 자율성
- 💡 나쁜 예:
if (user.getPassword().equals(input))- 💡 좋은 예:
if (user.authenticate(input))🎯 추가 프로그래밍 요구사항
코드 스타일
- Java Style Guide 준수
- 3항 연산자 사용 금지
테스트 및 구조
- **모든 기능 T...
Files:
study/src/test/java/transaction/stage1/Stage1Test.javaapp/src/test/java/com/techcourse/service/UserServiceTest.javaapp/src/main/java/com/techcourse/service/AppUserService.javaapp/src/main/java/com/techcourse/service/TxUserService.javastudy/src/test/java/transaction/stage1/UserDao.javajdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.javaapp/src/main/java/com/techcourse/service/UserService.javastudy/src/test/java/transaction/stage1/jdbc/JdbcTemplate.java
🧬 Code graph analysis (6)
study/src/test/java/transaction/stage1/Stage1Test.java (2)
jdbc/src/main/java/com/interface21/transaction/support/TransactionSynchronizationManager.java (1)
TransactionSynchronizationManager(12-71)study/src/main/java/transaction/RunnableWrapper.java (1)
RunnableWrapper(6-22)
app/src/main/java/com/techcourse/service/AppUserService.java (1)
app/src/main/java/com/techcourse/dao/UserHistoryDao.java (1)
UserHistoryDao(6-25)
app/src/main/java/com/techcourse/service/TxUserService.java (2)
jdbc/src/main/java/com/interface21/transaction/DataSourceTransactionManager.java (1)
DataSourceTransactionManager(12-73)app/src/main/java/com/techcourse/config/DataSourceConfig.java (1)
DataSourceConfig(7-27)
jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java (1)
jdbc/src/main/java/com/interface21/transaction/support/TransactionSynchronizationManager.java (1)
TransactionSynchronizationManager(12-71)
app/src/main/java/com/techcourse/service/UserService.java (1)
study/src/test/java/aop/stage1/UserService.java (1)
UserService(9-36)
study/src/test/java/transaction/stage1/jdbc/JdbcTemplate.java (1)
jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java (1)
DataSourceUtils(12-43)
🔇 Additional comments (10)
study/src/test/java/transaction/stage1/Stage1Test.java (2)
69-71: 사전 데이터 준비 방식은 깔끔합니다트랜잭션 외부에서 준비 데이터를 넣고 즉시 리소스를 반납한 점 좋습니다.
108-111: 테스트 픽스처 준비는 적절합니다사전 insert를 트랜잭션 외부에서 수행한 점 명확합니다.
app/src/main/java/com/techcourse/service/TxUserService.java (1)
18-21: 읽기 요청 위임은 단순·명확합니다트랜잭션 부하가 필요 없는 조회를 그대로 위임한 선택은 합리적입니다.
app/src/test/java/com/techcourse/service/UserServiceTest.java (1)
34-36: 서비스 합성 방식이 PR 목표와 일치합니다AppUserService 위에 TxUserService를 감싼 구성이 명확하고 테스트 의도가 드러납니다.
app/src/main/java/com/techcourse/service/AppUserService.java (1)
13-16: 생성자 주입과 필드 수(2개)는 응집도가 높습니다의존성 최소화가 잘 지켜졌습니다.
jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java (1)
16-17: 유틸리티 클래스 패턴 잘 적용했습니다!Private 생성자로 인스턴스화를 방지한 점이 좋습니다. 유틸리티 클래스의 올바른 구현 방식이에요.
study/src/test/java/transaction/stage1/UserDao.java (1)
17-50: Connection 파라미터 제거 - 훌륭한 리팩터링입니다!모든 메서드에서
Connection파라미터를 제거하고JdbcTemplate에 위임하도록 변경한 점이 매우 좋습니다. 이를 통해:
- DAO가 커넥션 관리 책임에서 벗어났어요
- 트랜잭션 동기화가 투명하게 처리됩니다
- 테스트하기 더 쉬운 구조가 되었습니다
관심사의 분리(Separation of Concerns)가 잘 적용된 사례입니다!
study/src/test/java/transaction/stage1/jdbc/JdbcTemplate.java (3)
22-35: 트랜잭션 동기화를 고려한 Connection 관리가 잘 되어 있습니다!
DataSourceUtils를 사용해서 Connection을 획득하고 finally 블록에서 반환하는 패턴이 정확합니다. 트랜잭션이 활성화되어 있다면 같은 Connection을 재사용하고, 그렇지 않으면 새로 획득한 Connection을 닫게 되죠.Connection을 null로 초기화하는 패턴이 조금 아쉬워 보일 수 있지만, finally 블록에서 안전하게 해제하기 위한 표준적인 방법입니다.
37-47: 메서드 위임 패턴이 깔끔합니다!가변 인자를 받는 메서드가
PreparedStatementSetter를 생성해서 더 구체적인 메서드로 위임하는 구조가 좋습니다. 코드 중복을 피하면서도 사용자에게 편리한 API를 제공하고 있어요.
queryForObject가 빈 결과에 대해 null을 반환하는 부분은, 향후Optional<T>를 고려해볼 수도 있지만 현재 단계에서는 적절한 선택입니다.
49-68: 조회 로직도 안전하게 구현되었습니다!update 메서드와 동일한 Connection 관리 패턴을 사용하면서, PreparedStatement와 ResultSet은 try-with-resources로 관리하는 구조가 일관성 있고 안전합니다.
ResultSet을 순회하며 List에 담는 로직도 명확하네요!
| public void changePassword(final long id, final String newPassword, final String createdBy) { | ||
| transactionManager.bindConnection(); | ||
| try { | ||
| userService.changePassword(id, newPassword, createdBy); | ||
| transactionManager.commit(); | ||
| } catch (final Exception e) { | ||
| transactionManager.rollback(); | ||
| throw new DataAccessException(e); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
커밋 실패 시 롤백 호출로 인한 이중 해제/NPE 위험
commit 내부에서 이미 release/unbind가 수행된 뒤 예외가 던져지면, catch 블록의 rollback이 바인딩된 커넥션 없이 실행되어 NPE/이중 해제가 날 수 있습니다. 왜: 트랜잭션 수명주기 관리가 불일치. 커밋 예외는 별도로 처리하거나, “비즈니스 예외에만 롤백”하도록 분기하여 이중 해제를 방지하는 구조를 고민해보세요. 또한 이미 발생한 DataAccessException을 다시 래핑하지 않고 유지할지 정책도 명확히 해보세요.
🤖 Prompt for AI Agents
In app/src/main/java/com/techcourse/service/TxUserService.java around lines
29-38, change the transaction error handling so rollback is only called when a
connection is still bound and ensure the connection is always unbound/released
exactly once; specifically, separate handling for commit-time exceptions vs
business exceptions (so you don't call rollback after commit already released
the connection), guard rollback with a
transactionManager.isBound()/hasConnection() check before invoking it, move
unbind/release into a single finally block to guarantee one-time cleanup, and
when rethrowing preserve an existing DataAccessException instead of always
wrapping it again.
| new Thread(RunnableWrapper.accept(() -> { | ||
| // db에 connection(사용자A)이 아닌 새로운 연결인 subConnection(사용자B)을 받아온다. | ||
| final var subConnection = dataSource.getConnection(); | ||
|
|
||
| // 적절한 격리 레벨을 찾는다. | ||
| final int isolationLevel = Connection.TRANSACTION_NONE; | ||
|
|
||
| // 트랜잭션 격리 레벨을 설정한다. | ||
| final int isolationLevel = Connection.TRANSACTION_READ_UNCOMMITTED; | ||
| subConnection.setTransactionIsolation(isolationLevel); | ||
|
|
||
| // ❗️gugu 객체는 connection에서 아직 커밋하지 않은 상태다. | ||
| // 격리 레벨에 따라 커밋하지 않은 gugu 객체를 조회할 수 있다. | ||
| // 사용자B가 사용자A가 커밋하지 않은 데이터를 조회하는게 적절할까? | ||
| final var actual = userDao.findByAccount(subConnection, "gugu"); | ||
| TransactionSynchronizationManager.bindResource(dataSource, subConnection); | ||
| final var actual = userDao.findByAccount("gugu"); | ||
| TransactionSynchronizationManager.unbindResource(dataSource); | ||
|
|
||
| // 트랜잭션 격리 레벨에 따라 아래 테스트가 통과한다. | ||
| // 어떤 격리 레벨일 때 다른 연결의 커밋 전 데이터를 조회할 수 있을지 찾아보자. | ||
| // 다른 격리 레벨은 어떤 결과가 나오는지 직접 확인해보자. | ||
| log.info("isolation level : {}, user : {}", isolationLevel, actual); | ||
| assertThat(actual).isNull(); | ||
| assertThat(actual).isNotNull(); | ||
| })).start(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
스레드 조인/예외 전파 부재로 테스트가 거짓 양성될 수 있습니다
별도 스레드의 assertion/예외가 JUnit에 전파되지 않고, join 없이 sleep으로 동기화하여 타이밍에 따라 통과할 수 있습니다. 왜: 실패를 놓치거나 플래키해짐. CountDownLatch/Executor+Future.get 또는 thread.join으로 동기화하고 예외를 메인 테스트에 전파하는 방식을 고려해보셨나요?
🤖 Prompt for AI Agents
In study/src/test/java/transaction/stage1/Stage1Test.java around lines 47 to 58,
the test spawns a new Thread and runs assertions inside it without joining or
propagating exceptions, causing false positives or flakiness; change the test to
synchronize with the worker thread (use Thread.join or replace with an
ExecutorService and submit a Callable then call Future.get) and capture any
exception thrown in the worker so it can be rethrown in the main test thread (or
let Future.get throw it). Also remove any sleep-based timing and ensure
TransactionSynchronizationManager unbinds in a finally block inside the worker
to avoid leaking resources.
| // 다른 격리 레벨은 어떤 결과가 나오는지 직접 확인해보자. | ||
| log.info("isolation level : {}, user : {}", isolationLevel, actual); | ||
| assertThat(actual).isNull(); | ||
| assertThat(actual).isNotNull(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
검증 위치 재고
assertThat을 서브 스레드 내부에서 수행하고 있어 실패가 테스트 결과에 반영되지 않을 수 있습니다. 메인 스레드에서 결과를 검증하는 구조로 바꿀 수 있을까요?
🤖 Prompt for AI Agents
In study/src/test/java/transaction/stage1/Stage1Test.java around line 57, the
assertion assertThat(actual).isNotNull() is executed inside a worker/sub-thread
so failures may not propagate to the test runner; change the test to collect the
result from the sub-thread (e.g., submit a Callable to an ExecutorService and
get the result, or use a Future/AtomicReference with
thread.join()/CountDownLatch) and perform assertThat(...) on the main thread
after the worker completes so any failure is reported by the test framework.
bowook
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
마지막 레벨4 미션 고생하셨습니다!
전체적으로, 구조가 저랑 비슷해서 쉽게 이해할 수 있었던 것 같아요.
| } catch (SQLException ex) { | ||
| throw new CannotGetJdbcConnectionException("Failed to close JDBC Connection"); | ||
| public static void releaseConnection(final Connection connection, final DataSource dataSource) { | ||
| if (TransactionSynchronizationManager.hasResource(dataSource)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
안녕하세요 우가 대가 🙈🙊🐒🙉🐵
드디어 대망의 우테코 마지막 미션...... 도 한 번 구현해봤습니다.
변경사항과 관련한 설명도 아래에 적어두겠습니다 ㅎㅎ
리뷰 잘부탁드릴게요 ~
주요 변경 사항
비즈니스 로직과 트랜잭션 처리 로직을 분리하여 코드의 명확성과 유지보수성을 높이는 데 중점을 두었습니다. 이를 위해 서비스 계층에 인터페이스르 도입해 트랜잭션 처리를 추상화했습니다. 또한, 트랜잭션 동기화 방식을 도입하여 DAO 계층이 Connection 객체에 직접 의존하지 않도록 DataSourceUtils와 JdbcTemplate을 개선했습니다.
클래스 및 역할
1. UserService (인터페이스)
2. AppUserService (클래스)
3. TxUserService (클래스)
4. DataSourceUtils (클래스)
Connection을 반환하도록 구현했습니다.기대 효과