-
Notifications
You must be signed in to change notification settings - Fork 379
[4단계 - Transaction synchronization 적용하기] 가콩(최가빈) 미션 제출합니다. #1217
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
WalkthroughDAO 메서드들에서 명시적 절차 설명DAO 계층(UserDao, UserHistoryDao)의 메서드 시그니처에서 Pre-merge checks❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 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: 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.Connectionimport가 남아 있지 않나요? 사용하지 않는 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.Connectionimport가 더 이상 사용되지 않는 것 같습니다. 코드의 명확성을 위해 제거하는 것이 좋을까요?
39-42: 일급 컬렉션 사용 고려
findAll()메서드가List<User>를 직접 반환하고 있습니다. 규칙 8: 일급 컬렉션 사용을 고려해볼 수 있습니다.📖 일급 컬렉션이란?
- 컬렉션을 감싸는 별도의 클래스를 만드는 것
- 컬렉션에 대한 비즈니스 로직을 캡슐화할 수 있습니다
💡 고려해볼 점:
Users와 같은 컬렉션 전용 클래스를 만들면 어떤 이점이 있을까요?- 예를 들어, "활성 사용자만 필터링", "특정 조건의 사용자 찾기" 같은 로직을 어디에 두는 것이 좋을까요?
현재 코드가 잘못된 것은 아니지만, 컬렉션 관련 로직이 늘어날 경우 이 패턴을 고려해보시면 좋을 것 같습니다.
44-52: 원시값과 문자열 포장 고려
findById와findByAccount메서드에서Long id와String account를 직접 사용하고 있습니다. 규칙 3: 모든 원시값과 문자열 포장을 검토해볼 수 있습니다.📖 왜 포장이 중요한가요?
- 도메인 개념을 명확하게 표현할 수 있습니다
- 타입 안정성이 높아집니다 (Long 타입의 다른 값과 혼동 방지)
- 해당 값에 대한 검증 로직을 한 곳에 모을 수 있습니다
💡 생각해볼 질문:
Long id를UserId로,String account를Account로 포장하면 어떤 이점이 있을까요?- 만약 account에 대한 유효성 검증(예: 형식 체크, 길이 제한)이 필요하다면 어디에 두는 것이 좋을까요?
// 예시 (직접 구현하진 마시고 방향만 참고하세요) public User findById(final UserId userId) { ... } public User findByAccount(final Account account) { ... }이는 당장 필수는 아니지만, 도메인이 복잡해질수록 가치가 커지는 패턴입니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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 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:
app/src/test/java/com/techcourse/service/UserServiceTest.javaapp/src/main/java/com/techcourse/service/UserService.javajdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.javajdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.javaapp/src/main/java/com/techcourse/service/TxUserService.javaapp/src/main/java/com/techcourse/dao/UserHistoryDao.javaapp/src/test/java/com/techcourse/service/MockUserHistoryDao.javaapp/src/main/java/com/techcourse/dao/UserDao.javajdbc/src/main/java/com/interface21/transaction/support/TransactionSynchronizationManager.javaapp/src/main/java/com/techcourse/service/AppUserService.javaapp/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)
| 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()); | ||
| } |
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.
🛠️ Refactor suggestion | 🟠 Major
객체에게 묻지 말고 시키기 (Tell, Don't Ask)
31번 라인에서 user 객체에 여러 번 getter를 호출하여 데이터를 꺼내고 있습니다. 이는 규칙 9: 게터/세터/프로퍼티 금지와 관련이 있습니다.
📖 왜 이 원칙이 중요한가요?
- 객체의 내부 구조가 외부로 노출됩니다
- 객체의 자율성이 떨어지고 진정한 캡슐화가 깨집니다
- 변경에 취약한 코드가 됩니다 (User의 필드가 바뀌면 DAO도 수정해야 함)
💡 생각해볼 질문:
- User 객체가 자신의 데이터를 직접 제공하는 메서드를 가지도록 하면 어떨까요?
- "UserDao가 User에게 무엇을 달라고 요청하는 대신, User가 스스로 필요한 것을 제공하게" 하려면 어떻게 설계할 수 있을까요?
| @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)); | ||
| } |
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.
비밀번호 이력 저장의 개인정보/보안 리스크
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.
| Connection bound = null; | ||
| if (TransactionSynchronizationManager.hasResource(dataSource)) { | ||
| bound = TransactionSynchronizationManager.unbindResource(dataSource); | ||
| } | ||
| DataSourceUtils.releaseConnection(bound != null ? bound : connection, 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.
예외 경로에서 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); |
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.
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); |
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.
예외 메시지 일관성
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.
| Connection txConn = TransactionSynchronizationManager.getResource(dataSource); | ||
|
|
||
| if (txConn == null || txConn != connection) { | ||
| connection.close(); | ||
| } |
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.
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.
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: 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
📒 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 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:
jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.javaapp/src/test/java/com/techcourse/dao/UserDaoTest.javaapp/src/main/java/com/techcourse/service/TxUserService.javaapp/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)
| if (!existingTx) { | ||
| connection = DataSourceUtils.getConnection(dataSource); | ||
| connection.setAutoCommit(false); | ||
| TransactionSynchronizationManager.bindResource(dataSource, connection); | ||
| } else { | ||
| connection = DataSourceUtils.getConnection(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.
🛠️ 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.
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.
안녕하세요 가콩! DM으로 말씀드린부분들 수정하신거 확인했습니다. 여기서 머지해도 될 것 같아요.
마지막 미션까지 고생 많으셨고, 그럼 남은 우테코 생활도 건강하게 잘 마무리 하시길 !
윌슨~ 마지막 리뷰네용
잘부탁드립니다
지난 번에 리뷰 주신 부분 반영했습니다