-
Notifications
You must be signed in to change notification settings - Fork 220
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
[JDBC 라이브러리 구현하기 - 1, 2단계] 조엘(조영상) 미션 제출합니다. #48
Conversation
- CheckedException을 UncheckedException으로 변경 - try-with-resources 활용
동갑내기끼리 걸렸네요!! 화이팅 :)) |
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.
테코톡 고생했어 👏👏👏
쉽게 설명해줘서 너무 좋았어. 잘 모르던 내용이었는데 도움이 많이 됐음 🙇♂️🙇♂️🙇♂️
바쁜 와중에도 구현을 잘 해줘서, 코드적으로는 남길 부분이 몇 개 없는데
조엘의 생각이 궁금해서 몇개의 리뷰를 남겼어. 천천히 수정하고 편하게 리뷰 요청해 줘!
이건 여유가 있으면 할만한 것들을 남기는데, 조엘의 의지대로 하면 좋을 듯
- JdbcTemplate 중복 제거
- JdbcTemplate 테스트 만들기
- DatabasePopulatorUtils.java 깔끔하게 정리하기
JdbcTemplate 테스트를 하기 어려울 텐데, 그만큼 깨닫는 부분도 많을 거라 생각해💪
} | ||
|
||
public void update(User user) { | ||
// todo | ||
final String sql = "update users set account = (?), password = (?), email = (?) where id = (?)"; |
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.
물음표를 괄호 안에 넣어주신 이유가 있을까요?
궁금하네요 🤔
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.
개인 취향일 수 있는데 괄호로 감싸면 여기에 변수가 들어가야 하는구나가 더 눈에 잘 띈다고 생각해서요!
미완성된 쿼리문이라는 것이 조금 더 눈에 잘 들어온달까요?
|
||
import java.util.Optional; | ||
|
||
public interface UserRepository { |
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.
UserRepository 인터페이스를 만드셨군요!
Controller 에서 UserRepositoryImpl 과 InMemoryUserRepository 중 유연하게 선택하도록 인터페이스를 만드셨다고 생각이 들어요.
이름이 걸리긴 하지만 UserDao implements UserRepository 를 할 수도 있었을 것 같은데, 그렇게 하지 않은 이유가 궁금하네요 🤔
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.
우선 현재 구조를 가장 건드리지 않으면서 코드를 변경하고 싶었어요!
UserDao는 현재 처럼 DB와 가장 가까이서 데이터 접근만 담당하고, Optional<User> findByAccount
와 같은 Optional 처리 등은 Repository에서 담당하는 구조로요!
사실 UserDao나 UserRepository나 하는 역할이 정말 거의 차이가 없어서 합칠 수는 있지만, 기존 코드와의 상생을 위해 이런 구조로 가져갔습니다!
@@ -6,7 +6,7 @@ | |||
import java.util.Optional; | |||
import java.util.concurrent.ConcurrentHashMap; | |||
|
|||
public class InMemoryUserRepository { | |||
public class InMemoryUserRepository implements UserRepository { | |||
|
|||
private static final Map<String, User> database = new ConcurrentHashMap<>(); |
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.
private static final Map<String, User> database = new ConcurrentHashMap<>(); | |
private final Map<String, User> database = new ConcurrentHashMap<>(); |
InMemoryRepository를 객체화하여 사용하는 방향으로 바꿨으니, 인스턴스 멤버로 바꿔줘야 자연스러운 것 같아요.
각각의 객체가 새로운 데이터 베이스를 생성하여 사용하는 방식으로요.
제가 느끼는 어색함은 아래 같은 상황이에요.
InMemoryUserRepository a = new InMemoryUserRepository();
a.save(user);
InMemoryUserRepository b = new InMemoryUserRepository();
// 새로 생성한 객체에 이미 user가 들어가 있음
만약 하나의 데이터베이스를 사용하기 위해 static 을 사용하고 싶으면, 싱글턴으로 관리하면 좋을 것 같습니다!
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.
동의합니다!
피드백 반영했습니다~
6682f9c
} | ||
|
||
public int update(String sql, Object... args) { | ||
return executeUpdate(connection -> makePreparedStatement(sql, connection, args)); |
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.
connection 으로 PreparedStatement를 만들고 인자를 셋팅하는 부분을 StatementStrategy 함수형 인터페이스로 만들어 주셨는데요.
update, queryForObject, query 메서드에서 같은 전략을 사용하고 있는 것을 보면 함수형 인터페이스를 만든 장점이 떨어지는 것 같아요.
jdbcTemplate 내부에서만 사용하는 콜백이니, 메서드 분리만으로 충분하지 않을까?? 라는 생각입니다. 의견이 궁금해요~!
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.
좋은 지적 감사합니다! 완벽하게 이해했는지는 확실치 않지만 StatementStrategy라는 함수형 인터페이스의 생성을 메서드로 분리해봤어요!! 일일이 StatementStrategy를 생성해주던 코드보다는 훨씬 유연해진 것 같네요!
269c7e9
if (rs.next()) { | ||
return rowMapper.mapRow(rs); | ||
} | ||
return null; |
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.
아래 executeQuery() 메서드와 해당 부분만 다르니, 콜백으로 분리하면 정말 좋은 구조라는 생각이 들어요 !
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.
네 콜백으로 분리해보았습니다~ 감사합니다!
|
||
@Override | ||
public Optional<User> findByAccount(String account) { | ||
return Optional.of(userDao.findByAccount(account)); |
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.
return Optional.of(userDao.findByAccount(account)); | |
return Optional.ofNullable(userDao.findByAccount(account)); |
이 부분 실수가 있네요!
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.
감사합니다!!
- `@BeforeEach` 대신 `@BeforeAll`로 변경
- PreparedStatementCreatorFactory, ResultSetExtractorFactory 생성
JdbcTemplate의 코드들을 리팩토링 해봤습니다! JdbcTemplate의 테스트 코드도 작성해보았어요. 중간곰의 피드백 덕분에 고민해볼 수 있던 주제가 많아서 좋았습니다 :) 감사합니다 :) |
SonarCloud Quality Gate failed. |
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.
워낙 깔끔하게 만들어 주셔서 크게 남길 리뷰가 없네요 😓
테코톡 하느라 바쁜 와중에 미션 하느라 고생하셨습니다 👏
조엘 화이팅 💪💪💪
try (Connection connection = dataSource.getConnection(); | ||
Statement statement = connection.createStatement()) { |
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.
깔끔하게 정리해 주셨네요👍👍
this.price = price; | ||
} | ||
} |
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.
제가 만든 Jdbc 라이브러리가 특정 데이터베이스에 의존하지 않았으면 좋겠다 생각했는데,
h2 의존성 없이는 테스트하기가 참 어려워지더라고요.
이 부분이 걸린다면, 구현한 몇몇 핵심 로직을 메서드 단위의 테스트로 만들어 보는 게 어떨까요 ?
목 테스트이기 때문에 메서드 내부 구현을 알고 테스트를 만드는 어색함이 있지만
이러한 테스트를 만들어 놓음으로써 안정적인 라이브러리가 만들어진다고 생각해요.
동료가 코드를 이해하지 못하고 제멋대로 수정하면 이 테스트가 깨질 테니까요.
그리고 가독성을 좋게 만들어 놓는다면, 자신이 만든 코드를 남이 읽었을 때 이해를 돕는 이정표의 역할도 할 수 있다고 생각해요.
아래 코드는 예시입니다 !
ResultSetExtractorTest.java
@DisplayName("ResultSet 에서 단일 오브젝트를 추출한다.")
@Test
void extractObjectFromResultSet() throws SQLException {
// given
ResultSet resultSet = mock(ResultSet.class);
RowMapper<Object> rowMapper = mock(RowMapper.class);
Object object = new Object();
given(resultSet.next()).willReturn(true);
given(rowMapper.mapRow(resultSet)).willReturn(object);
ResultSetExtractor<Object> resultSetExtractor = ResultSetExtractorFactory.objectResultSetExtractor(rowMapper);
// when
Object extractObject = resultSetExtractor.extract(resultSet);
// then
assertThat(extractObject).isNotNull();
then(resultSet).should(times(1)).next();
then(rowMapper).should(times(1)).mapRow(resultSet);
}
@DisplayName("ResultSet 이 비어있으면 null을 반환한다.")
@Test
void extractObjectFromEmptyResultSet() throws SQLException {
// given
ResultSet resultSet = mock(ResultSet.class);
RowMapper<Object> rowMapper = mock(RowMapper.class);
Object object = new Object();
given(resultSet.next()).willReturn(false);
given(rowMapper.mapRow(resultSet)).willReturn(object);
ResultSetExtractor<Object> resultSetExtractor = ResultSetExtractorFactory.objectResultSetExtractor(rowMapper);
// when
Object extractObject = resultSetExtractor.extract(resultSet);
// then
assertThat(extractObject).isNull();
then(resultSet).should(times(1)).next();
then(rowMapper).should(times(0)).mapRow(resultSet);
}
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.
mocking을 도입하여 테스트를 진행하는 방법도 좋네요!! 감사합니다~~👍👍👍
@@ -8,7 +9,6 @@ | |||
import java.sql.PreparedStatement; | |||
import java.sql.ResultSet; | |||
import java.sql.SQLException; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
|
|||
public class JdbcTemplate { |
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.
JdbcTemplate 클래스가 너무 깔끔해졌네요 👍👍
안녕하세요 중간곰!!!!
잘 지내고 있지 형??? 🙌🙌🙌
이번 미션 어렵네요,,, 제가 생각보다 추상화시키는 것이나 함수형 인터페이스 사용등에 익숙하지 않다는 것을 깨달았어요.
토비의 스프링 3장을 참고하면서 공부하면서 미션했네요.
여전히 아주 잘 와닿지는 않는 것 같아요.
템플릿 콜백 패턴 같은 것등을 과연 나 혼자 코드 짤 때 생각하고 도입할 수 있을까? 라고 하면 그건 아직 아닌 것 같네요ㅜㅜ
조금 혼자 더 해보면 좋겠지만 중간곰 피드백 받으면서 개선해 나가려고 합니다~
소중한 시간 써주셔서 감사합니다 💪💪