Skip to content

Conversation

@gabean13
Copy link

@gabean13 gabean13 commented Oct 27, 2025

윌슨~ 마지막 리뷰네용
잘부탁드립니다

지난 번에 리뷰 주신 부분 반영했습니다

@gabean13 gabean13 changed the title Step4 [4단계 - Transaction synchronization 적용하기] 가콩(최가빈) 미션 제출합니다. step4 Oct 27, 2025
@gabean13 gabean13 self-assigned this Oct 27, 2025
@gabean13 gabean13 added the step4 label Oct 27, 2025
@gabean13 gabean13 changed the title [4단계 - Transaction synchronization 적용하기] 가콩(최가빈) 미션 제출합니다. step4 [4단계 - Transaction synchronization 적용하기] 가콩(최가빈) 미션 제출합니다. Oct 27, 2025
@coderabbitai
Copy link

coderabbitai bot commented Oct 27, 2025

Walkthrough

DAO 메서드들에서 명시적 Connection 파라미터를 제거했고, JdbcTemplatequeryForObject/query/update가 내부적으로 DataSourceUtils로부터 커넥션을 획득·해제하도록 변경되었습니다. UserService는 인터페이스로 전환되었고 구현은 AppUserService가 담당하며 트랜잭션 처리는 TxUserService로 분리되었습니다. TransactionSynchronizationManager에는 스레드-로컬 리소스 추적 기능이 추가되어 트랜잭션에 바인딩된 연결을 관리합니다.

절차 설명

DAO 계층(UserDao, UserHistoryDao)의 메서드 시그니처에서 Connection 인자를 제거하고 호출부는 JdbcTemplate.update/query/queryForObject(sql, ...) 형태로 변경되었습니다. JdbcTemplate은 내부에서 DataSourceUtils.getConnection으로 연결을 획득하고 실행 후 DataSourceUtils.releaseConnection으로 해제합니다. DataSourceUtils는 트랜잭션 바인딩 여부에 따라 연결 해제 동작을 조정하도록 수정되었고, TransactionSynchronizationManager는 데이터소스별 스레드-로컬 연결 바인딩/조회/해제 기능을 제공하도록 확장되었습니다. 서비스 계층은 UserService 인터페이스와 이를 구현하는 AppUserService, 트랜잭션을 감싸는 TxUserService로 분리되어 역할을 분담합니다.

Pre-merge checks

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description Check ❓ Inconclusive PR 설명 "윌슨~ 마지막 리뷰네용 잘부탁드립니다 지난 번에 리뷰 주신 부분 반영했습니다"는 매우 모호하고 일반적인 표현으로만 이루어져 있습니다. 이전 피드백을 반영했다는 점만 언급되어 있을 뿐, 실제로 변경된 구체적인 내용이나 무엇이 개선되었는지에 대한 의미 있는 정보를 전혀 전달하지 않습니다. DAO 메서드 시그니처 변경, 새 서비스 클래스 생성, UserService의 인터페이스화 등 주요 변경 사항이 하나도 설명되지 않아 변경 사항에 대한 어떤 정보도 제공하지 못하고 있습니다.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed PR 제목 "[4단계 - Transaction synchronization 적용하기] 가콩(최가빈) 미션 제출합니다."는 변경 사항의 핵심 목표인 트랜잭션 동기화 적용을 명확히 설명하고 있습니다. 실제 변경 사항을 살펴보면 TransactionSynchronizationManager에 새로운 기능이 추가되었고, TxUserService 클래스가 생성되어 수동 트랜잭션 관리를 구현하였으며, JdbcTemplate이 내부적으로 연결을 관리하도록 리팩토링되었습니다. 이러한 변경들은 모두 트랜잭션 동기화를 적용하기 위한 것으로, 제목이 변경 사항과 직접적으로 관련이 있습니다.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

♻️ Duplicate comments (1)
app/src/main/java/com/techcourse/dao/UserDao.java (1)

34-37: 동일한 패턴: 여러 getter 호출

36번 라인에서도 insert 메서드와 동일하게 User 객체에서 여러 getter를 호출하고 있습니다. 앞서 언급한 "Tell, Don't Ask" 원칙을 여기에도 적용해보시면 좋을 것 같습니다.

🧹 Nitpick comments (18)
app/src/test/java/com/techcourse/service/MockUserHistoryDao.java (1)

16-18: 시그니처 정렬은 적절합니다. 다만 테스트 더블 품질을 조금만 다듬어보면 좋겠습니다.

  • 불필요한 의존 제거 흐름과 일치합니다. 좋습니다.
  • 현재 파일에 java.sql.Connection import가 남아 있지 않나요? 사용하지 않는 import는 제거하면 가독성과 유지보수성이 좋아집니다.
  • 예외를 무조건 던질 때, 메시지(의도)를 함께 담으면 실패 원인 파악이 빨라집니다. 테스트의 의도를 더 분명히 드러낼 수 있을까요?
app/src/main/java/com/techcourse/dao/UserHistoryDao.java (1)

18-25: DAO 책임과 객체지향 생활 체조 준수 점검이 필요합니다.

  • 규칙 4(한 줄에 점 하나): jdbcTemplate.update(...) 인자에서 다수의 userHistory.*() 호출이 한 줄에 몰려 있습니다. 파라미터 객체/변환 메서드로 의도를 드러내고 점을 줄여볼 수 있을까요? 왜: 결합도 감소, 가독성·캡슐화 향상.
  • 메서드명 log(...)와 로거 필드명 log가 동일해 의미 충돌이 있습니다. 읽기 혼동을 줄일 명확한 이름을 고민해보셨나요? 왜: 의도 표현과 오해 방지.
  • created_by 컬럼과 도메인 접근자 getCreateBy()의 철자/도메인 용어가 일관적인가요? 오타·용어 불일치가 숨어있지 않은지 확인 부탁드립니다. 왜: 데이터 무결성과 유지보수성.
app/src/main/java/com/techcourse/service/TxUserService.java (2)

31-54: 메서드가 길고(10줄 초과) 중첩이 있습니다(들여쓰기 depth>1).

규칙 1, 6 위반에 가깝습니다.

  • 트랜잭션 시작/업무 로직 호출/커밋·롤백/정리 단계를 각각의 사소한 메서드로 추출하면, 들여쓰기 1단계 유지와 의도 분리가 동시에 가능합니다.
  • 왜: 단일 책임 강화, 테스트 용이성, 예외 흐름의 명료화.

14-19: 정적 DataSource 의존은 결합도를 높입니다.

static final DataSource를 직접 참조하면 테스트 격리와 확장성(다중 데이터소스, 설정 변경)에 불리합니다.

  • 생성자 주입으로 DataSource를 받아 Tx 래퍼의 일반성을 높여볼 수 있을까요?
  • 왜: DIP 준수, 환경 교체 용이성, 단위 테스트 편의.
jdbc/src/main/java/com/interface21/transaction/support/TransactionSynchronizationManager.java (1)

24-43: 중첩 바인딩/해제 규약을 명확히 해주세요.

동일 DataSource로 여러 번 bindResource가 호출되면 마지막 값으로 덮어씁니다. 호출자 입장에서 “누가 시작했는지” 추적하지 않으면 상위 경계를 무심코 해제할 수 있습니다.

  • 카운팅(참조 수) 기반 전략 또는 “내가 시작했는지 여부”를 호출자 측에서 판별하도록 명세를 분명히 하면 어떨까요?
  • 왜: 중첩 트랜잭션 안전성, 서비스 간 합성 시 예측 가능성.
app/src/test/java/com/techcourse/service/UserServiceTest.java (3)

41-45: 하드코딩된 ID(1L)로 인한 테스트 취약성

DB 초기화/시퀀스 상태에 따라 1L가 항상 대상 사용자를 가리킨다는 보장이 없습니다. 테스트의 신뢰성을 위해 식별자 하드코딩 대신, 삽입/조회 결과로부터 동적으로 ID를 얻는 방식으로 개선해보면 어떨까요? 예: 계정으로 조회해 ID를 확보하는 흐름.


39-41: createdBy 명칭 일관성

메서드 시그니처는 createdBy인데, 테스트 지역 변수는 createBy/createdBy가 혼재합니다. 한 가지로 통일하면 의도 전달이 선명해집니다. 객체지향 생활 체조 규칙 5(축약 금지, 명확한 이름) 관점에서도 유익합니다.

Also applies to: 58-61


52-56: 트랜잭션 롤백 검증 범위 확장 제안

현재 비밀번호가 되돌아갔는지만 확인합니다. 롤백이 진짜로 원자적으로 동작했는지 강화하려면, user_history 테이블에 이력이 남지 않았음도 함께 검증해보면 어떨까요? 이렇게 하면 “업데이트 성공 + 이력 실패” 같은 부분 성공 상태가 발생하지 않았음을 더 확실히 보장할 수 있습니다.

Also applies to: 60-66

app/src/main/java/com/techcourse/service/AppUserService.java (3)

28-31: 규칙 3(원시값 포장) 적용 검토

id(long), newPassword(String), createdBy(String)은 도메인 의미가 큰 값들입니다. Password, UserId, Actor(또는 CreatedBy) 같은 값 객체로 포장하면 유효성(길이/포맷/인코딩/보안정책)과 불변성 규칙을 한곳에 응집할 수 있습니다. 왜? 도메인 정책이 퍼지지 않고 한곳에서 진화할 수 있기 때문입니다.


28-34: 트랜잭션 경계의 명시성

비밀번호 변경과 이력 기록은 반드시 함께 성공/실패해야 하는 불가분 작업으로 보입니다. 현재 구현은 TxUserService로 감싸면 해결되지만, AppUserService만 단독으로 사용될 가능성도 배제할 수 없습니다. 계약을 코드/주석로 명시하거나, 해당 기능을 반드시 트랜잭션 환경에서만 호출하도록 가드(예: 어노테이션/검증)하는 방식을 고려해보셨나요?


23-26: 서비스 계층의 행위 네이밍

DAO는 insert, 서비스는 save를 사용합니다. 동일 의도를 다른 용어로 표현하면 독자에게 “업데이트도 포함하나?”라는 질문을 유발할 수 있습니다. 생성 전용이라면 register/create, 변경 포함이라면 save의 의미를 문서/네이밍로 더 분명히 하면 어떨까요?

app/src/main/java/com/techcourse/service/UserService.java (1)

7-9: API에서의 원시값 포장과 실패 계약 명시

  • 규칙 3(원시값 포장): id, newPassword, createdBy를 값 객체로 포장하면 검증/정책을 인터페이스 경계에서 강제할 수 있습니다.
  • 조회 실패 계약: findById가 실패 시 예외를 던지는지 Optional로 표현하는지 인터페이스 차원에서 명시되면 호출 측이 명확히 대응할 수 있습니다. 팀 컨벤션과 일관되게 선택해보세요.
app/src/test/java/com/techcourse/dao/UserDaoTest.java (2)

62-66: 자동 증가 ID 가정에 의존하는 단언

insert 후 findById(2L)를 전제하는 것은 시퀀스/초기 데이터에 따라 쉽게 깨질 수 있습니다. 테스트는 “무엇을 검증하나”에 집중하도록 ID 하드코딩 대신 계정으로 조회 후 그 결과를 단언하는 방식으로 바꿔보면 어떨까요?


31-33: 정리 전략 점검: delete vs 스키마/시퀀스 리셋

@AfterEach에서 delete만 수행하면 시퀀스는 리셋되지 않아 이후 테스트의 ID 가정과 충돌할 수 있습니다. 이미 @beforeeach에서 DatabasePopulatorUtils.execute를 호출하므로 AfterEach 정리가 중복/과잉인지, 시퀀스 초기화까지 포함되어 있는지 확인해보셨나요?

jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java (1)

26-51: 자원/패턴 중복 최소화 제안

세 메서드가 연결 획득/해제와 예외 변환 패턴을 반복합니다. 공통 템플릿/헬퍼로 추출하면 가독성과 유지보수성이 개선됩니다. 또한 일부 JDBC 드라이버는 getParameterMetaData 지원이 제한적일 수 있으니 호환성도 점검해보세요.

Also applies to: 71-75, 87-92

app/src/main/java/com/techcourse/dao/UserDao.java (3)

6-6: 사용하지 않는 import 제거

Connection 파라미터를 제거한 리팩토링 이후, java.sql.Connection import가 더 이상 사용되지 않는 것 같습니다. 코드의 명확성을 위해 제거하는 것이 좋을까요?


39-42: 일급 컬렉션 사용 고려

findAll() 메서드가 List<User>를 직접 반환하고 있습니다. 규칙 8: 일급 컬렉션 사용을 고려해볼 수 있습니다.

📖 일급 컬렉션이란?

  • 컬렉션을 감싸는 별도의 클래스를 만드는 것
  • 컬렉션에 대한 비즈니스 로직을 캡슐화할 수 있습니다

💡 고려해볼 점:

  • Users와 같은 컬렉션 전용 클래스를 만들면 어떤 이점이 있을까요?
  • 예를 들어, "활성 사용자만 필터링", "특정 조건의 사용자 찾기" 같은 로직을 어디에 두는 것이 좋을까요?

현재 코드가 잘못된 것은 아니지만, 컬렉션 관련 로직이 늘어날 경우 이 패턴을 고려해보시면 좋을 것 같습니다.


44-52: 원시값과 문자열 포장 고려

findByIdfindByAccount 메서드에서 Long idString account를 직접 사용하고 있습니다. 규칙 3: 모든 원시값과 문자열 포장을 검토해볼 수 있습니다.

📖 왜 포장이 중요한가요?

  • 도메인 개념을 명확하게 표현할 수 있습니다
  • 타입 안정성이 높아집니다 (Long 타입의 다른 값과 혼동 방지)
  • 해당 값에 대한 검증 로직을 한 곳에 모을 수 있습니다

💡 생각해볼 질문:

  • Long idUserId로, String accountAccount로 포장하면 어떤 이점이 있을까요?
  • 만약 account에 대한 유효성 검증(예: 형식 체크, 길이 제한)이 필요하다면 어디에 두는 것이 좋을까요?
// 예시 (직접 구현하진 마시고 방향만 참고하세요)
public User findById(final UserId userId) { ... }
public User findByAccount(final Account account) { ... }

이는 당장 필수는 아니지만, 도메인이 복잡해질수록 가치가 커지는 패턴입니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a683869 and c083f02.

📒 Files selected for processing (11)
  • app/src/main/java/com/techcourse/dao/UserDao.java (1 hunks)
  • app/src/main/java/com/techcourse/dao/UserHistoryDao.java (1 hunks)
  • 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/dao/UserDaoTest.java (2 hunks)
  • app/src/test/java/com/techcourse/service/MockUserHistoryDao.java (1 hunks)
  • app/src/test/java/com/techcourse/service/UserServiceTest.java (2 hunks)
  • jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java (3 hunks)
  • jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java (1 hunks)
  • jdbc/src/main/java/com/interface21/transaction/support/TransactionSynchronizationManager.java (2 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 portPort port, String nameName name

규칙 4: 한 줄에 점 하나만 (디미터 법칙)

  • 메서드 체이닝 제한
    • 📖 이유: 결합도 감소, 캡슐화 향상
    • 💡 나쁜 예: request.getUri().getPath().substring()
    • 💡 좋은 예: request.extractPath()

규칙 5: 축약 금지

  • 명확한 이름 사용 (축약어 금지)
    • 📖 이유: 코드 가독성, 의도 명확화
    • 💡 예시: reqrequest, calcAmtcalculateAmount

규칙 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:

  • app/src/test/java/com/techcourse/service/UserServiceTest.java
  • app/src/main/java/com/techcourse/service/UserService.java
  • jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java
  • jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java
  • app/src/main/java/com/techcourse/service/TxUserService.java
  • app/src/main/java/com/techcourse/dao/UserHistoryDao.java
  • app/src/test/java/com/techcourse/service/MockUserHistoryDao.java
  • app/src/main/java/com/techcourse/dao/UserDao.java
  • jdbc/src/main/java/com/interface21/transaction/support/TransactionSynchronizationManager.java
  • app/src/main/java/com/techcourse/service/AppUserService.java
  • app/src/test/java/com/techcourse/dao/UserDaoTest.java
🧬 Code graph analysis (5)
app/src/main/java/com/techcourse/service/UserService.java (1)
study/src/test/java/aop/stage1/UserService.java (1)
  • UserService (9-36)
jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java (1)
jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java (1)
  • DataSourceUtils (10-39)
jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java (1)
jdbc/src/main/java/com/interface21/transaction/support/TransactionSynchronizationManager.java (1)
  • TransactionSynchronizationManager (8-44)
app/src/main/java/com/techcourse/service/TxUserService.java (3)
jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java (1)
  • DataSourceUtils (10-39)
jdbc/src/main/java/com/interface21/transaction/support/TransactionSynchronizationManager.java (1)
  • TransactionSynchronizationManager (8-44)
app/src/main/java/com/techcourse/config/DataSourceConfig.java (1)
  • DataSourceConfig (7-27)
app/src/main/java/com/techcourse/service/AppUserService.java (2)
app/src/main/java/com/techcourse/dao/UserDao.java (1)
  • UserDao (11-53)
app/src/main/java/com/techcourse/dao/UserHistoryDao.java (1)
  • UserHistoryDao (8-26)

Comment on lines +29 to 32
public void insert(final User user) {
final var sql = "insert into users (account, password, email) values (?, ?, ?)";
jdbcTemplate.update(conn, sql, user.getAccount(), user.getPassword(), user.getEmail());
jdbcTemplate.update(sql, user.getAccount(), user.getPassword(), user.getEmail());
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

객체에게 묻지 말고 시키기 (Tell, Don't Ask)

31번 라인에서 user 객체에 여러 번 getter를 호출하여 데이터를 꺼내고 있습니다. 이는 규칙 9: 게터/세터/프로퍼티 금지와 관련이 있습니다.

📖 왜 이 원칙이 중요한가요?

  • 객체의 내부 구조가 외부로 노출됩니다
  • 객체의 자율성이 떨어지고 진정한 캡슐화가 깨집니다
  • 변경에 취약한 코드가 됩니다 (User의 필드가 바뀌면 DAO도 수정해야 함)

💡 생각해볼 질문:

  • User 객체가 자신의 데이터를 직접 제공하는 메서드를 가지도록 하면 어떨까요?
  • "UserDao가 User에게 무엇을 달라고 요청하는 대신, User가 스스로 필요한 것을 제공하게" 하려면 어떻게 설계할 수 있을까요?

Comment on lines 28 to 34
@Override
public void changePassword(long id, String newPassword, String createdBy) {
final var user = findById(id);
user.changePassword(newPassword);
userDao.update(user);
userHistoryDao.log(new UserHistory(user, createdBy));
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

비밀번호 이력 저장의 개인정보/보안 리스크

UserHistoryDao가 password 컬럼까지 저장하도록 설계되어 있습니다. 비밀번호(원문/해시 포함)를 변경 이력 테이블에 남기는 것은 최소수집·목적제한 원칙과 보존 최소화 측면에서 큰 리스크가 됩니다. 가능한 대안들을 검토해보셨나요?

  • 비밀번호 값 자체는 저장하지 않고 “비밀번호가 변경되었다”는 이벤트 메타데이터만 기록
  • 저장이 불가피하다면, 역추정 불가능한 일방향 지표(예: 해시의 일부 특성값) 등으로 대체
  • 보존기간·암호화·접근통제 정책을 명확히 정의

실서비스라면 반드시 재검토가 필요합니다.

🤖 Prompt for AI Agents
In app/src/main/java/com/techcourse/service/AppUserService.java around lines 28
to 34, the current changePassword flow writes full password data into
UserHistory which is a privacy/security risk; update the implementation and
related UserHistory/UserHistoryDao so that no plaintext or full hash password is
stored—instead log only metadata indicating a password change (e.g., timestamp,
userId, changedBy, changeReason) or, if some artifact is required, store a
non-reversible indicator (e.g., truncated salted hash or a one-way-derived
token) that cannot be used to reconstruct the password; modify the UserHistory
constructor and userHistoryDao.log call to accept the new metadata-only object,
remove any password fields from persistence mapping, and ensure retention,
encryption and access-control policies are defined for the history records.

Comment on lines 48 to 53
Connection bound = null;
if (TransactionSynchronizationManager.hasResource(dataSource)) {
bound = TransactionSynchronizationManager.unbindResource(dataSource);
}
DataSourceUtils.releaseConnection(bound != null ? bound : connection, dataSource);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

예외 경로에서 NPE 위험: 커넥션이 null인 채로 release를 호출할 수 있습니다.

getConnection 실패(예: 드라이버 문제) 시 connection이 null이고, finally에서 releaseConnection(bound != null ? bound : connection, ...)가 호출되면 NPE가 날 수 있습니다.

  • 정리 단계에서 null 여부를 먼저 가드하거나, “이번 메서드에서 바인딩한 리소스만 해제”하도록 안전 장치를 두면 어떨까요?
  • 왜: 실패 경로 신뢰성, 자원 해제의 멱등성(idempotency) 확보.
🤖 Prompt for AI Agents
In app/src/main/java/com/techcourse/service/TxUserService.java around lines 48
to 53, the finally block calls DataSourceUtils.releaseConnection(bound != null ?
bound : connection, dataSource) which can cause an NPE if getConnection failed
and connection is null; change the cleanup to only call releaseConnection when
the selected Connection is non-null and only release the connection you actually
obtained (or unbound) — e.g. track whether you created/obtained the connection
in this method (or check bound != null first), and guard the release call with a
null check so releaseConnection is never invoked with a null reference.

if (TransactionSynchronizationManager.hasResource(dataSource)) {
bound = TransactionSynchronizationManager.unbindResource(dataSource);
}
DataSourceUtils.releaseConnection(bound != null ? bound : connection, dataSource);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

3항 연산자 사용 금지 규칙 위반

가이드라인에서 3항 연산자 금지를 명시하고 있습니다. 가드 클로즈나 명시적 분기로 의도를 드러내는 방식으로 바꿔보실까요?

  • 왜: 가독성 향상, 디버깅 용이성.
🤖 Prompt for AI Agents
In app/src/main/java/com/techcourse/service/TxUserService.java around line 52,
replace the ternary usage in DataSourceUtils.releaseConnection(bound != null ?
bound : connection, dataSource) with an explicit branch: determine the
connectionToRelease using an if (bound != null) { connectionToRelease = bound; }
else { connectionToRelease = connection; } (or return early/guard-close by
checking bound first), then call
DataSourceUtils.releaseConnection(connectionToRelease, dataSource); this removes
the ternary and makes the intent and flow explicit for readability and
debugging.

pstmt.executeUpdate();
}
} catch (SQLException e) {
throw new DataAccessException("DB 조회에 실패했습니다. :" + sql, e);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

예외 메시지 일관성

update에서도 “DB 조회에 실패”라는 메시지를 사용합니다. 작업 유형(조회/갱신/삭제)에 맞추어 메시지를 구분하면 디버깅과 관측 가능성이 좋아집니다.

🤖 Prompt for AI Agents
In jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java around line
86, the thrown DataAccessException uses the message "DB 조회에 실패했습니다." even for
update operations; change the message to reflect the actual operation (e.g., "DB
갱신에 실패했습니다." for update, or "DB 삭제에 실패했습니다." for delete) while preserving the
appended SQL and the wrapped exception (e); ensure the message matches the
operation performed to keep exception messages consistent and improve
observability.

Comment on lines +30 to +34
Connection txConn = TransactionSynchronizationManager.getResource(dataSource);

if (txConn == null || txConn != connection) {
connection.close();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

null‑세이프가 아닙니다: connection이 null이면 NPE 발생

releaseConnection(null, ds) 호출 가능성이 있습니다(상위 호출자가 실패 경로에서 finally로 진입하는 경우 등).

  • connection이 null이면 즉시 반환하는 가드 또는 명세 문서화가 필요합니다.
  • 또한, 이 메서드는 멱등적으로 설계되어야 합니다(여러 번 호출/다양한 경로에서 호출돼도 안전).
  • 왜: 안정적인 자원 관리, 실패 시 회복력 향상.
🤖 Prompt for AI Agents
In jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java
around lines 30 to 34, the release logic is not null-safe and can throw an NPE
if connection is null; add an early guard that returns immediately when
connection is null to make the method idempotent, and only attempt to close the
connection when it is non-null and not the same instance held by
TransactionSynchronizationManager; also catch and handle SQLExceptions from
close (log or ignore per project convention) so multiple/failed calls remain
safe, and document in the method javadoc that releaseConnection accepts null and
is safe to call repeatedly.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
app/src/main/java/com/techcourse/service/TxUserService.java (1)

57-62: connection이 null인 상태로 release를 호출할 위험이 있습니다.

DataSourceUtils.getConnection에서 예외가 나면 connection은 끝까지 null이고, finally에서 releaseConnection이 null 인자를 받으면서 NPE가 발생해 버그 원인을 가립니다. 또한 이 지점의 삼항 연산자는 미션 가이드라인(3항 연산자 금지)에도 어긋납니다. null 여부를 먼저 가드하고, 이번 메서드에서 직접 획득·언바인딩한 커넥션만 안전하게 해제하는 흐름을 다시 설계해볼 수 있을까요? 그렇게 정리하면 예외 시나리오에서도 자원이 일관되게 정리되고, 규칙도 지킬 수 있습니다.

jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java (1)

86-86: update 단계에서 조회 실패 메시지를 재사용하면 진단이 어려워집니다.

갱신/삭제 등 작업 성격에 맞는 예외 메시지로 구분해 두면 모니터링과 디버깅이 쉬워집니다. 이번 기회에 update용 메시지를 별도로 정리해보실 수 있을까요?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba4a957 and ab0d617.

📒 Files selected for processing (4)
  • app/src/main/java/com/techcourse/service/TxUserService.java (1 hunks)
  • app/src/test/java/com/techcourse/dao/UserDaoTest.java (2 hunks)
  • app/src/test/java/com/techcourse/service/UserServiceTest.java (3 hunks)
  • jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java (3 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 portPort port, String nameName name

규칙 4: 한 줄에 점 하나만 (디미터 법칙)

  • 메서드 체이닝 제한
    • 📖 이유: 결합도 감소, 캡슐화 향상
    • 💡 나쁜 예: request.getUri().getPath().substring()
    • 💡 좋은 예: request.extractPath()

규칙 5: 축약 금지

  • 명확한 이름 사용 (축약어 금지)
    • 📖 이유: 코드 가독성, 의도 명확화
    • 💡 예시: reqrequest, calcAmtcalculateAmount

규칙 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:

  • jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java
  • app/src/test/java/com/techcourse/dao/UserDaoTest.java
  • app/src/main/java/com/techcourse/service/TxUserService.java
  • app/src/test/java/com/techcourse/service/UserServiceTest.java
🧠 Learnings (2)
📓 Common learnings
Learnt from: egaeng09
Repo: woowacourse/java-jdbc PR: 1181
File: app/src/main/java/com/techcourse/dao/UserHistoryDao.java:15-26
Timestamp: 2025-10-21T09:53:41.119Z
Learning: egaeng09의 java-jdbc 프로젝트에서는 도메인 모델의 순수성을 우선시하며, 도메인 객체가 JDBC와 같은 기술적 구현체에 의존하지 않도록 합니다. DAO 계층에서 도메인 객체의 getter를 사용하는 것은 계층 분리를 유지하기 위해 허용됩니다.
📚 Learning: 2025-10-21T09:53:41.119Z
Learnt from: egaeng09
Repo: woowacourse/java-jdbc PR: 1181
File: app/src/main/java/com/techcourse/dao/UserHistoryDao.java:15-26
Timestamp: 2025-10-21T09:53:41.119Z
Learning: egaeng09의 java-jdbc 프로젝트에서는 도메인 모델의 순수성을 우선시하며, 도메인 객체가 JDBC와 같은 기술적 구현체에 의존하지 않도록 합니다. DAO 계층에서 도메인 객체의 getter를 사용하는 것은 계층 분리를 유지하기 위해 허용됩니다.

Applied to files:

  • jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java
🧬 Code graph analysis (2)
jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java (1)
jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java (1)
  • DataSourceUtils (10-39)
app/src/main/java/com/techcourse/service/TxUserService.java (3)
jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java (1)
  • DataSourceUtils (10-39)
jdbc/src/main/java/com/interface21/transaction/support/TransactionSynchronizationManager.java (1)
  • TransactionSynchronizationManager (8-44)
app/src/main/java/com/techcourse/config/DataSourceConfig.java (1)
  • DataSourceConfig (7-27)

Comment on lines +36 to +42
if (!existingTx) {
connection = DataSourceUtils.getConnection(dataSource);
connection.setAutoCommit(false);
TransactionSynchronizationManager.bindResource(dataSource, connection);
} else {
connection = DataSourceUtils.getConnection(dataSource);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

else 예약어 사용이 객체지향 생활 체조 규칙을 위반하고 있습니다.

규칙 2에서는 else를 제거해 분기 흐름을 단순화하도록 요구합니다. 지금은 if (!existingTx) { ... } else { ... }로 분기가 깊어져 가독성이 떨어집니다. 가드 클로즈를 두거나, 이미 존재하는 트랜잭션을 먼저 처리해 조기 반환하는 식으로 재구성하면 어떨까요? 이런 방식이 책임을 더 명확하게 보여 주고, 복잡도를 줄여줍니다.

🤖 Prompt for AI Agents
In app/src/main/java/com/techcourse/service/TxUserService.java around lines 36
to 42, the current if(!existingTx){ ... } else { ... } uses an else that
violates the OOP gymnastics rule; refactor to remove the else by handling the
existing transaction case first (guard clause): if existingTx, obtain the
connection and proceed without binding or changing auto-commit (early path), and
then return or continue so the remaining code can assume no existing transaction
and perform connection = DataSourceUtils.getConnection(dataSource);
connection.setAutoCommit(false);
TransactionSynchronizationManager.bindResource(dataSource, connection); — ensure
the binding and setAutoCommit only occur in the non-existing-transaction path
and do not use else blocks.

Copy link

@phk1128 phk1128 left a comment

Choose a reason for hiding this comment

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

안녕하세요 가콩! DM으로 말씀드린부분들 수정하신거 확인했습니다. 여기서 머지해도 될 것 같아요.
마지막 미션까지 고생 많으셨고, 그럼 남은 우테코 생활도 건강하게 잘 마무리 하시길 !

@phk1128 phk1128 merged commit 6c1ff53 into woowacourse:gabean13 Nov 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants