-
Notifications
You must be signed in to change notification settings - Fork 301
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
Conversation
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.
@Controller | ||
public class LoginController { | ||
|
||
private static final Logger log = LoggerFactory.getLogger(LoginController.class); | ||
private final UserDao userDao = new UserDao(DataSourceConfig.getInstance()); |
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.
final에 객체 생성 및 초기화를 함께한다면 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.
여러 컨트롤러에서도 돌려쓰니깐 확실히 static이나 싱글턴으로 하는게 좋아보이네요 변경하겠습니돠
|
||
private ModelAndView redirect(String path) { | ||
return new ModelAndView(new JspView(JspView.REDIRECT_PREFIX + path)); | ||
} |
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.
redirect가 필요한 컨트롤러에서 메소드가 중복되네요!!
클래스로 따로 빼서 관리해도 좋을거 같아요 😊
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.
이 메서드를 누가 가지고있으면 좋을까 생각했습니다.
처음에는 ModelAndView
클래스가 스태틱 메서드로 가지고 있게끔 변경했습니다. 갖고 있어도 그럴듯 하다 라고 생각했습니다. ModelAndView
타입을 리턴해주니까요! 그런데 내부에 JspView
를 알고있다는 점이 굉장히 찝찝했습니다. import를 추가할 필요는 없었지만 이것 또한 의존이라고 생각했습니다.
그래서 최종적으로는 그냥 ViewRedirectHelper
라는 클래스를 mvc 모듈의 view/util 패키지 내부에 추가해주었습니다.
검프라면 어떤식으로 처리했을지 생각들어보고 싶습니돠!
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.
ViewRedirectHelper 좋네요!!
JspView.REDIRECT_PREFIX도 의존할 필요가 있을까? 라는 생각에서, 나온 피드백이었어요!
저도 파즈와 마찬가지로, mvc 모듈에 유틸성 클래스를 추가했을 것 같아요 :)
public final RowMapper<User> userRowMapper() { | ||
return (rs -> new User( | ||
rs.getLong("id"), | ||
rs.getString("account"), | ||
rs.getString("password"), | ||
rs.getString("email") | ||
)); |
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.
저는 rowNumber가 필요한 순간이 있을거라 생각해서 RowMapper의 인자에 rowNumber를 넘겨줬었어요.
파즈가 만드신 JdbcTemplate은 List을 반환 할 수 있어서 rowNumber가 필요하지 않았을 거 같아요!
List말고 다른 자료구조를 반환 해야 하는 경우가 어떤 경우가 있을까요~~? 파즈의 생각이 궁금해요!
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을 사용할 때에도 한 번도 사용해본적이 없어서 어떤 때에 필요한지 정확히 느껴본적이 없습니다...
다른 자료구조를 사용할 때에도 어차피 while(rs.next())
로 반복문을 돌리니깐, 컬렉션 사이즈를 지정하고 생성할 때 외에는 필요할 일이 있나? 라고 느꼈습니다
어떤 때에 사용하시나요 검프는?!
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.
만약 객체가 의존하고 있는 엔티티를 불러와야 한다면 List로만으로는 불러오기 힘들 거라 생각했어요!
저는List<Map<String, Obejct>>와 같은 타입을 반환할 수 있게 했어요. 이럴 경우 의존하는 객체의 엔티티까지 불러와 줄 수 있더라고요.
하지만, 현재 로직에선 의존하고 있지 않으니, 의존관계까지 생각할 필요는 없다고 생각해요 ㅎㅎ
List외의 다른 자료구조를 사용할때 이유가 있으면 좋겠다고 해서 남긴 피드백이었습니다
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 = new UserDao(DataSourceConfig.getInstance()); | ||
final User user = new User("gugu", "password", "hkkang@woowahan.com"); | ||
userDao.insert(user); | ||
} | ||
|
||
@Test | ||
void findAll() { | ||
void findAll() { |
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.
2칸이 띄워져 있네요 :)
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.
진짜 예리하다 이걸 어케봤지
public class JdbcExecutionException extends RuntimeException { | ||
private static final String MESSAGE = "JdbcTemplate Execute Method Error Occurred!!!"; | ||
|
||
public JdbcExecutionException() { | ||
super(MESSAGE); | ||
} | ||
} |
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.
현재와 같은 상황에선 메세지 별로 예외 클래스가 생길거 같아요.
생성자의 인자로 message를 넘겨주는 것은 어떨까요 😁
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 <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(); | ||
} | ||
} |
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 with resource 활용까지..! 멋집니다 :)
private void setStatementArguments(PreparedStatement pstmt, Object[] args) throws SQLException { | ||
for (int i = 0; i < args.length; i++) { | ||
pstmt.setObject(i + 1, args[i]); | ||
} | ||
} |
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.
args가 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.
이걸 놓쳤었는데 정말 좋은 지적이네요. 그리고 지금까지 파라미터 앞에 @NonNull
붙이면 알아서 널쳌이 되는줄 알았는데 그냥 명시하는 것 뿐이었어요! 덕분에 알게되었습니다 감사합니돠
public interface RowMapper<T> { | ||
@Nullable |
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.
처리완료~! 예리해!
검프 역시 꼼꼼하게 봐주셔서 감사합니다!!!!!!!! |
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.
안녕하세요 파즈!! 2번째 pr 이네요 ㅎㅎ
리뷰 반영을 깔끔하게 잘해주셔서 코드가 더 멋져진거 같아요
js랑 db 안되는거는 run 돌리실 때에 gradle 말고 intellij로 돌려주세요~!!
A. 혹시, gralde에서도 돌아가게 해주실 수 있을까요~~? 만약 이후에 gradlew로 빌드된다면 동일한 현상이 발생할거 같아서요!!
간단한 피드백 남겼어요!! 이번 리뷰 반영하신다고 고생많으셨어요
public class ViewRedirectHelper { | ||
|
||
public static ModelAndView redirect(String path) { | ||
return new ModelAndView(new JspView(JspView.REDIRECT_PREFIX + path)); | ||
} | ||
} |
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.
static 클래스에 private 생성자를 추가해주면 좋을 거 같아요
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.
오 이거 진짜 놓치기 좋은 부분인데 감사합니다
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]); | ||
} | ||
} | ||
} | ||
} |
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.
제대로 동작하는지 테스트를 한번 해보는 것은 어떨까요? 😊
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); | ||
} | ||
} |
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.
사실 static 메서드를 제거를 유도한 피드백이었는데, 이것도 되게 좋은 거 같아요 😊
공통의 body를 가지는 abstract 클래스로 한단계 추상화 하면, static을 abstract 클래스로 둘 수 있을 거 같아요!
파즈의 생각은 어떠신가요?
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.
사실 이 예외는 RuntimeException
말고 커스텀한 예외 처리하기 위해서 간단히 만들어 둔 예외였습니다!
JdbcExecution에 관련된 세부 예외들을 만들게되는 경우에는 JdbcExecutionException
을 abstract class로 두고 하위 커스텀 예외들에서는 인자로 String message만 넘기게끔 만들 수 있을 것 같아요! 일단 이 클래스를 하위라고 치부하고 위쪾에 abstract class를 하나 만들어 두겠습니다!
그런데 검프네는 프로젝트 진행 시에 커스텀 익셉션들 안에 상수 필드로 메세지 따로 안가지고 있나요?!
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.
저희 팀에서는 message를 생성자의 인자로만 가지고 있어요!! 생성시에 상위 클래스인 RuntimeException에서 message가 초기화 되구요.
서비스 동작 시 공통적으로 발생 할 수 있는 예외들을 클래스 타입으로 명시했고 (NotFoundException, DuplicateException 등)
상황에 따라 메세지로 에외를 표시했어요.
NotFoundException -> CustomException -> RuntimeException 이런 구조로 되어 있습니다!
(이런 상황에선 이후 enum 필드로 메세지를 관리해야 할 거 같긴 하네요 😊 )
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() { | ||
} | ||
} |
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 with resource를 적용해보는것은 어떨까요 ?😁
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.
안녕하세요 파즈!
처음부터 잘 짜여있던 로직이라 파즈 코드를 통해 많이 배울 수 있었어요!
피드백 반영을 하시며 질문 해주셨던 부분들이 인상 깊었습니다. 저도 피드백을 했던 부분들에 다시한번 고민을 해보는 값진 시간이었어요. 정답을 찾아가는 모습이 멋집니다!
(저는 미션을 진행하며 intellij말고 gradle로 build 해서 많이 해맸던 기억이...ㅎㅎㅎ )
더 성장하리라 믿습니다
피드백 반영하신다고 수고 정말 많으셨어요 😀😀
안녕하세요 검프, 연휴는 잘 보내셨나요 ㅎㅎ 이번주도 화이팅입니다~!
너무 구현한게 적은 느낌이라 이대로 제출해도 되나 싶은데... 리뷰 일단 요청하겠습니다!!
콜백은 2개 추가했습니다! 템플릿 메서드 패턴은 JdbcTemplate의 execute 메서드로 사용했습니다.
네이밍은 실제 JdbcTemplate의 네이밍 참고를 좀 했습니다!!
리뷰 감사합니다. 검프!!