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

[Spring JDBC] 인상진 미션 제출합니다. #291

Merged
merged 11 commits into from
Jul 11, 2024

Conversation

sangjin6439
Copy link

인프런에서 Spring JDBC 강의를 들어서 그렇게 어렵지는 않았지만 프로젝트로 직접 사용한 것은 처음이었습니다.

DB 테이블
CommandLineRunner를 통해 애플리케이션 실행 시 reservation 테이블이 있을 시 삭제하고 새로운 reservation 테이블을 만들게 했습니다.

데이터 생성/조회/삭제
JdbcTemplate 기술로 DB에 접근해서 생성/조회/삭제를 구현했습니다.
생성: 데이터 생성 시 insert 쿼리를 사용했고 KeyHolder를 사용해 생성된 데이터의 id 값을 객체에 반영할 수 있게 했습니다. KeyHolder를 사용하지 않고 insert를 하게 되면 DB의 id컬럼의 값을 자동으로 증가시키게 되고 해당 객체의 id 값은 알 수 없게 됩니다.
조회: select 쿼리를 통해 데이터를 불러옵니다.
삭제: delete 쿼리를 통해 데이터를 삭제했습니다. 삭제하고자 하는 데이터의 id가 있다면 정상적으로 삭제되어 1을 반환하게 되고 1을 반환하지 않는다면 예외를 발생시켰습니다.

Comment on lines 20 to 37
public Reservation insert(Reservation reservation) {
String sql = "INSERT INTO reservation (name, date, time) VALUES (?, ?, ?)";

KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(cn->{
PreparedStatement ps = cn.prepareStatement(sql, new String[]{"id"});
ps.setString(1, reservation.getName());
ps.setString(2, reservation.getDate());
ps.setString(3, reservation.getTime());
return ps;
}, keyHolder);
reservation.setId(keyHolder.getKey().longValue());
return reservation;
}

public List<Reservation> findAll() {
return jdbcTemplate.query("select * from reservation", (rs, rowNum) -> new Reservation(rs.getLong("id"), rs.getString("name"), rs.getString("date"), rs.getString("time")));
}

Choose a reason for hiding this comment

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

쿼리로 사용할 문자열의 컨벤션을 맞추면 더 좋을 것 같아요! (대소문자 등)

Copy link
Author

Choose a reason for hiding this comment

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

조언 감사합니다!

Comment on lines 30 to 31
@Autowired
private JdbcTemplate jdbcTemplate;

Choose a reason for hiding this comment

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

필드 주입을 사용하신 이유가 있을까요?

Choose a reason for hiding this comment

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

추가로 jdbcTemplate의 인스턴수 변수가 ReservationRepository에도 있는 것 같은데 왜 두 곳 모두에 존재하나요?

Copy link
Author

Choose a reason for hiding this comment

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

Q: 필드 주입을 사용하신 이유가 있을까요?
A: 생성자 주입이 필드 주입보다 final 선언으로 불변성 보장, 단위 테스트 용이 등의 이점이 있다는 사실을 알고 있지만, 저는 이번 스터디의 목적을 여러 코드를 작성해 보고 왜 해당 코드를 작성하면 안 되고 다른 코드를 작성해야 하는지 등을 공부해 가며 깊이 있게 이해하고자 했습니다. 스터디를 진행하면서 더욱 좋은 품질의 코드로 변경해 갈 예정입니다. 역시 예혁님이 질문하실 줄 알았습니다!

Q: 추가로 jdbcTemplate의 인스턴수 변수가 ReservationRepository에도 있는 것 같은데 왜 두 곳 모두에 존재하나요?
A: Controller에 쿼리를 넣으려 했었는데 Repository로 코드를 옮기며 안 쓰는 코드가 되었습니다. 인지하지 못했는데 감사합니다.

Choose a reason for hiding this comment

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

image

추가로 jdbcTemplate의 인스턴수 변수가 ReservationRepository에도 있는 것 같은데 왜 두 곳 모두에 존재하나요?

많은 차이는 없지만, 사진처럼 깃허브 기능을 통해 질문과 답변을 깔끔하게 작성할 수 있어요!
아니면 > 내용을 통해 직접 작성하실 수도 있습니다!

Comment on lines +19 to +23
@Override
public void run(final String... args) throws Exception {
jdbcTemplate.execute("DROP TABLE reservation IF EXISTS");
jdbcTemplate.execute("CREATE TABLE reservation (id BIGINT NOT NULL AUTO_INCREMENT, name VARCHAR(255) NOT NULL , date DATE NOT NULL , time TIME NOT NULL , PRIMARY KEY (id))");
}
Copy link

Choose a reason for hiding this comment

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

SQL 파일을 이용해서 애플리케이션 시작 시마다 새로운 테이블을 생성하는 방법과 Spring Boot 애플리케이션 클래스에서 run 메서드를 오버라이드하여 직접 테이블을 초기화하는 방법에는 어떤 차이가 있나요?

Copy link
Author

Choose a reason for hiding this comment

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

SQL 파일을 이용해서 애플리케이션 시작 시마다 새로운 테이블을 생성하는 방법과 Spring Boot 애플리케이션 클래스에서 run 메서드를 오버라이드하여 직접 테이블을 초기화하는 방법에는 어떤 차이가 있나요?

run을 사용해서 직접 테이블을 초기화 한다면 같은 자바 코드로 작성하여 일관성이 있고 외부 파일에 대한 의존성이 감소합니다.
sql 파일을 사용 시 관리하기가 용이합니다.

}

@GetMapping("/reservations")
@ResponseBody
Copy link

Choose a reason for hiding this comment

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

@ResponseBody 를 사용하는 이유가 뭔가요?

Copy link
Author

Choose a reason for hiding this comment

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

해당 클래스에 @controller 어노테이션을 사용해서 html파일로 리다이렉트 되는 것이 아닌 json형식으로 값을 반환하기 위해 썼습니다.


Reservation reservation = reservationRepository.insert(new Reservation(requestDto.getName(), requestDto.getDate(), requestDto.getTime()));

URI location = UriComponentsBuilder.fromPath("/reservations/{id}").buildAndExpand(reservation.getId()).toUri();
Copy link

Choose a reason for hiding this comment

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

UriComponentsBuilder를 사용해서 새로 생성된 리소스의 URI를 HttpHeaders의 Location 헤더에 설정하는 방법이 매우 유용해 보여요!
이 외에도 추가적으로 활용할 수 있는 점들이 많은 것 같은데 어떤 장점이 있을까요?

Copy link
Author

Choose a reason for hiding this comment

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

UriComponentsBuilder를 사용해서 새로 생성된 리소스의 URI를 HttpHeaders의 Location 헤더에 설정하는 방법이 매우 유용해 보여요! 이 외에도 추가적으로 활용할 수 있는 점들이 많은 것 같은데 어떤 장점이 있을까요?

URL에 한글, 특수 문자를 넣어야 할 때 반드시 인코딩 해주어야 하는데 UriComponentsBuilder를 사용한다면 자동으로 인코딩 해주고 코드도 직관적으로 작성할 수 있습니다.

public ResponseEntity<Void> deleteReservation(@PathVariable("id") Long id) {
boolean removed = reservationRepository.deleteById(id);
if (!removed) {
throw new CustomException(ErrorCode.RESERVATION_NOT_FOUND);
Copy link

Choose a reason for hiding this comment

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

예외 처리 👍🏻

Comment on lines 23 to 36
public Reservation insert(Reservation reservation) {
String sql = "INSERT INTO reservation (name, date, time) VALUES (?, ?, ?)";

KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(cn->{
PreparedStatement ps = cn.prepareStatement(sql, new String[]{"id"});
ps.setString(1, reservation.getName());
ps.setString(2, reservation.getDate());
ps.setString(3, reservation.getTime());
return ps;
}, keyHolder);
reservation.setId(keyHolder.getKey().longValue());
return reservation;
}
Copy link

Choose a reason for hiding this comment

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

SimpleJdbcInsert를 사용하지 않고 jdbcTemplate을 사용한 이유가 있을까요??

Copy link
Author

Choose a reason for hiding this comment

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

SimpleJdbcInsert를 사용하지 않고 jdbcTemplate을 사용한 이유가 있을까요??

기술적인 이유는 딱히 없고 jdbcTemplate을 사용해 보고 다음 미션 때 SimpleJdbcInsert를 사용해 보는 등, 다양한 코드를 작성하고 싶었습니다.

Comment on lines 46 to 51
public boolean deleteById(Long id) {
if(jdbcTemplate.update("DELETE FROM reservation WHERE id = ?", id)==1){
return true;
};
return false;
}
Copy link

Choose a reason for hiding this comment

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

단순히 예약 id를 조회해서 삭제를 진행하는 것이 아니라 삭제된 행의 수가 1개인 경우를 boolean으로 반환하는 이유가 있을까요?

Copy link
Author

Choose a reason for hiding this comment

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

단순히 예약 id를 조회해서 삭제를 진행하는 것이 아니라 삭제된 행의 수가 1개인 경우를 boolean으로 반환하는 이유가 있을까요?

jdbcTemplate에서 update는 row수를 반환합니다. 그렇기에 delete가 삭제가 정상 작동한다면 해당 row수가 1 줄어들어 1을 반환합니다. 1이 반환된다면 삭제된 행이 있다는 뜻이고 boolean으로 반환하여 controller에 있는 검증 로직에 들어갑니다. 조회 후 삭제를 하려면 조회하는 쿼리와 삭제하는 쿼리 두 개가 필요하기 때문에 하나의 코드로 작성했습니다. 각 코드의 트레이드오프가 있다고 생각합니다.

Copy link

@Junyeong-An Junyeong-An left a comment

Choose a reason for hiding this comment

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

고생하셨습니다! 👏

@boorownie boorownie merged commit e9b3077 into next-step:sangjin6439 Jul 11, 2024
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.

5 participants