Skip to content
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단계] 파즈(강승윤) 미션 제출합니다. #18

Merged
merged 23 commits into from
Sep 26, 2021

Conversation

Be-poz
Copy link
Member

@Be-poz Be-poz commented Sep 22, 2021

안녕하세요 검프, 연휴는 잘 보내셨나요 ㅎㅎ 이번주도 화이팅입니다~!
너무 구현한게 적은 느낌이라 이대로 제출해도 되나 싶은데... 리뷰 일단 요청하겠습니다!!
콜백은 2개 추가했습니다! 템플릿 메서드 패턴은 JdbcTemplate의 execute 메서드로 사용했습니다.
네이밍은 실제 JdbcTemplate의 네이밍 참고를 좀 했습니다!!

리뷰 감사합니다. 검프!!

@Be-poz Be-poz requested a review from Livenow14 September 22, 2021 13:56
Copy link

@Livenow14 Livenow14 left a comment

Choose a reason for hiding this comment

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

안녕하세요 파즈
리뷰어 검프입니다 :)

전반적으로 요구사항을 깔끔히 구현 하셨네요!! JdbcTemplate의 간결함을 보고 깜짝 놀랐어요 :)

전체적인 피드백을 먼저 드리자면,

운영 코드 반영

css 적용

운영코드를 돌려봤는데, 페이지 상 CSS가 적용이 되지 않는거 같아요!! 이부분 반영해주세요

https://user-images.githubusercontent.com/48986787/134700599-b36751d2-8c68-4c09-9f3d-f8cbb2869b1d.png

DB 작동안됨

현재 사이트 상 DB 반영이 되어 있지 않는것 같아요!! 이부분도 피드백 반영하며 반영해주시면 될 것 같아요 😊

https://user-images.githubusercontent.com/48986787/134701227-ace2ec73-f0ec-42a8-91d6-1850585e8aa5.png

https://user-images.githubusercontent.com/48986787/134701089-f6e80e52-db1c-45cb-848d-7b65b8213a6b.png

간단한 피드백도 남겼어요:)

피드백을 반영하다 의문점이 있다면, 고민없이 연락 주세요 :)

@Controller
public class LoginController {

private static final Logger log = LoggerFactory.getLogger(LoginController.class);
private final UserDao userDao = new UserDao(DataSourceConfig.getInstance());

Choose a reason for hiding this comment

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

final에 객체 생성 및 초기화를 함께한다면 static을 고려해봐도 좋을거 같아요!

Copy link
Member Author

Choose a reason for hiding this comment

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

여러 컨트롤러에서도 돌려쓰니깐 확실히 static이나 싱글턴으로 하는게 좋아보이네요 변경하겠습니돠

Comment on lines 37 to 40

private ModelAndView redirect(String path) {
return new ModelAndView(new JspView(JspView.REDIRECT_PREFIX + path));
}

Choose a reason for hiding this comment

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

redirect가 필요한 컨트롤러에서 메소드가 중복되네요!!
클래스로 따로 빼서 관리해도 좋을거 같아요 😊

Copy link
Member Author

Choose a reason for hiding this comment

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

이 메서드를 누가 가지고있으면 좋을까 생각했습니다.
처음에는 ModelAndView 클래스가 스태틱 메서드로 가지고 있게끔 변경했습니다. 갖고 있어도 그럴듯 하다 라고 생각했습니다. ModelAndView 타입을 리턴해주니까요! 그런데 내부에 JspView를 알고있다는 점이 굉장히 찝찝했습니다. import를 추가할 필요는 없었지만 이것 또한 의존이라고 생각했습니다.
그래서 최종적으로는 그냥 ViewRedirectHelper 라는 클래스를 mvc 모듈의 view/util 패키지 내부에 추가해주었습니다.

검프라면 어떤식으로 처리했을지 생각들어보고 싶습니돠!

Choose a reason for hiding this comment

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

ViewRedirectHelper 좋네요!!
JspView.REDIRECT_PREFIX도 의존할 필요가 있을까? 라는 생각에서, 나온 피드백이었어요!
저도 파즈와 마찬가지로, mvc 모듈에 유틸성 클래스를 추가했을 것 같아요 :)

Comment on lines 46 to 52
public final RowMapper<User> userRowMapper() {
return (rs -> new User(
rs.getLong("id"),
rs.getString("account"),
rs.getString("password"),
rs.getString("email")
));

Choose a reason for hiding this comment

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

저는 rowNumber가 필요한 순간이 있을거라 생각해서 RowMapper의 인자에 rowNumber를 넘겨줬었어요.
파즈가 만드신 JdbcTemplate은 List을 반환 할 수 있어서 rowNumber가 필요하지 않았을 거 같아요!

List말고 다른 자료구조를 반환 해야 하는 경우가 어떤 경우가 있을까요~~? 파즈의 생각이 궁금해요!

Copy link
Member Author

Choose a reason for hiding this comment

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

저도 처음에 추가했었다가 현재 안써서 빼게되었는데요!
사실 스프링의 JdbcTemplate을 사용할 때에도 한 번도 사용해본적이 없어서 어떤 때에 필요한지 정확히 느껴본적이 없습니다...
다른 자료구조를 사용할 때에도 어차피 while(rs.next())로 반복문을 돌리니깐, 컬렉션 사이즈를 지정하고 생성할 때 외에는 필요할 일이 있나? 라고 느꼈습니다
어떤 때에 사용하시나요 검프는?!

Choose a reason for hiding this comment

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

만약 객체가 의존하고 있는 엔티티를 불러와야 한다면 List로만으로는 불러오기 힘들 거라 생각했어요!
저는List<Map<String, Obejct>>와 같은 타입을 반환할 수 있게 했어요. 이럴 경우 의존하는 객체의 엔티티까지 불러와 줄 수 있더라고요.
하지만, 현재 로직에선 의존하고 있지 않으니, 의존관계까지 생각할 필요는 없다고 생각해요 ㅎㅎ
List외의 다른 자료구조를 사용할때 이유가 있으면 좋겠다고 해서 남긴 피드백이었습니다

Copy link
Member Author

Choose a reason for hiding this comment

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

코드 없이 읽으늬 이해가 잘 안가네유 ㅜㅜ 검프 코드 한 번 보러가야겠슴다

userDao = new UserDao(DataSourceConfig.getInstance());
final User user = new User("gugu", "password", "hkkang@woowahan.com");
userDao.insert(user);
}

@Test
void findAll() {
void findAll() {

Choose a reason for hiding this comment

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

2칸이 띄워져 있네요 :)

Copy link
Member Author

Choose a reason for hiding this comment

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

진짜 예리하다 이걸 어케봤지

Comment on lines 3 to 9
public class JdbcExecutionException extends RuntimeException {
private static final String MESSAGE = "JdbcTemplate Execute Method Error Occurred!!!";

public JdbcExecutionException() {
super(MESSAGE);
}
}

Choose a reason for hiding this comment

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

현재와 같은 상황에선 메세지 별로 예외 클래스가 생길거 같아요.
생성자의 인자로 message를 넘겨주는 것은 어떨까요 😁

Copy link
Member Author

Choose a reason for hiding this comment

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

옷케이!

Comment on lines 54 to 65
private <T> T execute(String sql, Object[] args, JdbcPreparedStatementExecution<T> execution) {
log.debug("jdbcTemplate execute method");

try (Connection connection = dataSource.getConnection();
PreparedStatement pstmt = connection.prepareStatement(sql)) {
setStatementArguments(pstmt, args);
return execution.execute(pstmt);
} catch (SQLException e) {
log.error(e.getMessage());
throw new JdbcExecutionException();
}
}

Choose a reason for hiding this comment

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

크... 모든 쿼리가 이곳에서 이루어 지는군요!! 이부분을 보며 감탄했어요 👍👍
try with resource 활용까지..! 멋집니다 :)

Comment on lines 67 to 71
private void setStatementArguments(PreparedStatement pstmt, Object[] args) throws SQLException {
for (int i = 0; i < args.length; i++) {
pstmt.setObject(i + 1, args[i]);
}
}

Choose a reason for hiding this comment

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

args가 null이 되는 경우를 검증해도 좋을 거 같아요 😊

Copy link
Member Author

Choose a reason for hiding this comment

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

이걸 놓쳤었는데 정말 좋은 지적이네요. 그리고 지금까지 파라미터 앞에 @NonNull 붙이면 알아서 널쳌이 되는줄 알았는데 그냥 명시하는 것 뿐이었어요! 덕분에 알게되었습니다 감사합니돠

Comment on lines 8 to 9
public interface RowMapper<T> {
@Nullable

Choose a reason for hiding this comment

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

다른곳은 개행이 추가 되어있는데 여기는 붙여있네요!

Copy link
Member Author

Choose a reason for hiding this comment

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

처리완료~! 예리해!

@Be-poz
Copy link
Member Author

Be-poz commented Sep 25, 2021

검프 역시 꼼꼼하게 봐주셔서 감사합니다!!!!!!!!
js랑 db 안되는거는 run 돌리실 때에 gradle 말고 intellij로 돌려주세요~!!
피드백 반영했고 코멘트도 남겨놨습니다 감사합니돠

Copy link

@Livenow14 Livenow14 left a comment

Choose a reason for hiding this comment

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

안녕하세요 파즈!! 2번째 pr 이네요 ㅎㅎ
리뷰 반영을 깔끔하게 잘해주셔서 코드가 더 멋져진거 같아요

js랑 db 안되는거는 run 돌리실 때에 gradle 말고 intellij로 돌려주세요~!!

A. 혹시, gralde에서도 돌아가게 해주실 수 있을까요~~? 만약 이후에 gradlew로 빌드된다면 동일한 현상이 발생할거 같아서요!!

간단한 피드백 남겼어요!! 이번 리뷰 반영하신다고 고생많으셨어요 ☺️

Comment on lines 6 to 11
public class ViewRedirectHelper {

public static ModelAndView redirect(String path) {
return new ModelAndView(new JspView(JspView.REDIRECT_PREFIX + path));
}
}

Choose a reason for hiding this comment

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

static 클래스에 private 생성자를 추가해주면 좋을 거 같아요

Copy link
Member Author

Choose a reason for hiding this comment

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

오 이거 진짜 놓치기 좋은 부분인데 감사합니다

Comment on lines 16 to 76
public class JdbcTemplate {

private static final Logger log = LoggerFactory.getLogger(JdbcTemplate.class);
private final DataSource dataSource;

public JdbcTemplate(DataSource dataSource) {
this.dataSource = dataSource;
}

public <T> T queryForObject(String sql, RowMapper<T> rowMapper, @NonNull Object... args) {
log.debug("jdbcTemplate queryForObject method - query : " + sql);
return execute(sql, args, pstmt -> {
try (ResultSet rs = pstmt.executeQuery()) {
T result = null;
while (rs.next()) {
result = rowMapper.mapRow(rs);
}
return result;
}
});
}

public <T> List<T> query(String sql, RowMapper<T> rowMapper, @NonNull Object... args) {
log.debug("jdbcTemplate query method - query : " + sql);
return execute(sql, args, pstmt -> {
List<T> results = new ArrayList<>();
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
results.add(rowMapper.mapRow(rs));
}
return results;
}
});
}

public void update(String sql, @NonNull Object... args) {
log.debug("jdbcTemplate update method - query : " + sql);
execute(sql, args, PreparedStatement::executeUpdate);
}

private <T> T execute(String sql, Object[] args, JdbcPreparedStatementExecution<T> execution) {
log.debug("jdbcTemplate execute method");

try (Connection connection = dataSource.getConnection();
PreparedStatement pstmt = connection.prepareStatement(sql)) {
setStatementArguments(pstmt, args);
return execution.execute(pstmt);
} catch (SQLException e) {
log.error(e.getMessage());
throw new JdbcExecutionException(e.getMessage());
}
}

private void setStatementArguments(PreparedStatement pstmt, Object[] args) throws SQLException {
if (Objects.nonNull(args)) {
for (int i = 0; i < args.length; i++) {
pstmt.setObject(i + 1, args[i]);
}
}
}
}

Choose a reason for hiding this comment

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

제대로 동작하는지 테스트를 한번 해보는 것은 어떨까요? 😊

Comment on lines 1 to 9
package nextstep.jdbc;

public class JdbcExecutionException extends RuntimeException {
private static final String MESSAGE = "JdbcTemplate Execute Method Error Occurred!!! Deatiled : ";

public JdbcExecutionException(String message) {
super(MESSAGE + message);
}
}

Choose a reason for hiding this comment

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

사실 static 메서드를 제거를 유도한 피드백이었는데, 이것도 되게 좋은 거 같아요 😊
공통의 body를 가지는 abstract 클래스로 한단계 추상화 하면, static을 abstract 클래스로 둘 수 있을 거 같아요!
파즈의 생각은 어떠신가요?

Copy link
Member Author

Choose a reason for hiding this comment

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

사실 이 예외는 RuntimeException 말고 커스텀한 예외 처리하기 위해서 간단히 만들어 둔 예외였습니다!
JdbcExecution에 관련된 세부 예외들을 만들게되는 경우에는 JdbcExecutionException을 abstract class로 두고 하위 커스텀 예외들에서는 인자로 String message만 넘기게끔 만들 수 있을 것 같아요! 일단 이 클래스를 하위라고 치부하고 위쪾에 abstract class를 하나 만들어 두겠습니다!

그런데 검프네는 프로젝트 진행 시에 커스텀 익셉션들 안에 상수 필드로 메세지 따로 안가지고 있나요?!

Copy link

@Livenow14 Livenow14 Sep 26, 2021

Choose a reason for hiding this comment

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

저희 팀에서는 message를 생성자의 인자로만 가지고 있어요!! 생성시에 상위 클래스인 RuntimeException에서 message가 초기화 되구요.
서비스 동작 시 공통적으로 발생 할 수 있는 예외들을 클래스 타입으로 명시했고 (NotFoundException, DuplicateException 등)
상황에 따라 메세지로 에외를 표시했어요.
NotFoundException -> CustomException -> RuntimeException 이런 구조로 되어 있습니다!
(이런 상황에선 이후 enum 필드로 메세지를 관리해야 할 거 같긴 하네요 😊 )

Comment on lines 33 to 50
if (statement != null) {
statement.close();
}
} catch (SQLException ignored) {}
} catch (SQLException ignored) {
}

try {
if (connection != null) {
connection.close();
}
} catch (SQLException ignored) {}
} catch (SQLException ignored) {
}
}
}

private DatabasePopulatorUtils() {}
private DatabasePopulatorUtils() {
}
}

Choose a reason for hiding this comment

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

여기에도 try with resource를 적용해보는것은 어떨까요 ?😁

@Be-poz Be-poz changed the title [JDBC 라이브러리 구현하기 - 1, 2단계] 파즈(강승윤) 미션 제출합니다. [JDBC 라이브러리 구현하기 - 1단계] 파즈(강승윤) 미션 제출합니다. Sep 26, 2021
@Be-poz Be-poz changed the title [JDBC 라이브러리 구현하기 - 1단계] 파즈(강승윤) 미션 제출합니다. [JDBC 라이브러리 구현하기 - 1, 2단계] 파즈(강승윤) 미션 제출합니다. Sep 26, 2021
@sonarqubecloud
Copy link

SonarCloud Quality Gate failed.    Quality Gate failed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 3 Code Smells

0.0% 0.0% Coverage
0.0% 0.0% Duplication

@Be-poz
Copy link
Member Author

Be-poz commented Sep 26, 2021

검프 안녕하세용 피드백 적용해서 다시 제출합니다!!
메인 코멘트에서 A. 혹시, gralde에서도 돌아가게 해주실 수 있을까요~~? 만약 이후에 gradlew로 빌드된다면 동일한 현상이 발생할거 같아서요!! 이 부분은 반영하지 않았습니다!
미션에서 의도한 부분이라서요~!

image
JwpApplication 클래스의 메서드입니다. 톰캣한테 base path를 알려주는 부분입니다.
image
app 모듈의 build.gradle 입니다. idea 스코프를 따로 추가해줬습니다. idea로 돌릴 시에 outputDir를 "webapp/WEB-INF/classes"로 들어가게끔 해주었습니다. 서블릿 디렉토리 표준으로 맞춰줘야 하는데 인텔리제이로 빌드할 때 맞춰주도록 되어있습니다. 궁금해서 구구한테 물어보니깐 다들 ide로 보통 돌리니깐 미션의 용이함을 위해서 설정해둔거라고 하셨습니다! 그리고 어떤 path 로 바꿔야 할지 잘 모르기도 하구요. .. 어쨋든 요거는 기본 미션에서 주어지는 상황이니깐 패쓰하겠습니다! 🙄

Copy link

@Livenow14 Livenow14 left a comment

Choose a reason for hiding this comment

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

안녕하세요 파즈!
처음부터 잘 짜여있던 로직이라 파즈 코드를 통해 많이 배울 수 있었어요!

피드백 반영을 하시며 질문 해주셨던 부분들이 인상 깊었습니다. 저도 피드백을 했던 부분들에 다시한번 고민을 해보는 값진 시간이었어요. 정답을 찾아가는 모습이 멋집니다!
(저는 미션을 진행하며 intellij말고 gradle로 build 해서 많이 해맸던 기억이...ㅎㅎㅎ )

더 성장하리라 믿습니다
피드백 반영하신다고 수고 정말 많으셨어요 😀😀

@Livenow14 Livenow14 merged commit 8a9cf68 into woowacourse:be-poz Sep 26, 2021
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