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

다즐을 사칭한 누누 #14

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0ab7397
[1단계 - 웹 자동차 경주] 다즐(최우창) 미션 제출합니다. (#45)
woo-chang Apr 15, 2023
1999be3
refactor: 범용 Exception은 Server Error로 처리하도록 수정
woo-chang Apr 18, 2023
fac4aef
refactor: 서비스 레이어에서 레이싱 게임을 생성하도록 수정
woo-chang Apr 18, 2023
173c37a
refactor: 테스트 난수 생성기는 마지막 원소를 제거하도록 수정
woo-chang Apr 18, 2023
ab63e63
refactor: properties -> yml 파일 수정
woo-chang Apr 18, 2023
43fcd82
chore: validation 의존성 추가
woo-chang Apr 18, 2023
43b56af
feat: 게임 승자 테이블 생성
woo-chang Apr 18, 2023
80eff78
feat: DB 테이블과 매핑되는 엔티티 구현
woo-chang Apr 18, 2023
41ed14d
refactor: 엔티티 -> 도메인 생성을 위한 생성자 수정
woo-chang Apr 18, 2023
24ef594
fix: entity id를 설정할 수 있도록 수정
woo-chang Apr 18, 2023
d16475c
feat: 자동차 경주가 끝난 자동차 정보 저장 및 조회 기능 구현
woo-chang Apr 18, 2023
54c5c7d
feat: 자동차 경주 게임 정보 저장 및 조회 기능 구현
woo-chang Apr 18, 2023
9b090dd
feat: 자동차 경주 우승자 정보 저장 기능 구현
woo-chang Apr 18, 2023
31868f4
feat: 자동차 경주 도메인 저장 및 조회 기능 구현
woo-chang Apr 18, 2023
96c63b0
feat: 자동차 경주 게임 플레이 이력 조회 기능 구현
woo-chang Apr 18, 2023
fabeaca
refactor: 자동차 목록을 생성해서 주입하도록 수정
woo-chang Apr 18, 2023
50f676f
feat: 자동차 경주 게임 플레이 이력 조회 요청 및 응답 기능 구현
woo-chang Apr 18, 2023
97ac688
feat: 요청 데이터 검증 예외 처리 기능 구현
woo-chang Apr 18, 2023
17c545b
feat: 콘솔 애플리케이션 레포지토리 구현
woo-chang Apr 19, 2023
fab6d55
refactor: 웹 애플리케이션과 콘솔 애플리케이션 중복 코드 제거
woo-chang Apr 19, 2023
2817635
refactor: @Component -> @Repository 명시적 수정
woo-chang Apr 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,120 @@
# jwp-racingcar

## 개요

해당 레포지토리는 자동차 경주 게임을 웹 애플리케이션으로 전환하고 DB를 연동한 레포지토리입니다.

## 기능 목록

### 컨트롤러

- [x] 자동차 경주 게임을 플레이하기 위한 요청을 받아 우승자와 최종 위치를 응답한다.

- 요청 형식
```text
POST /plays HTTP/1.1

content-type: application/json; charset=UTF-8
host: localhost: 8080

{
"names": "브리,토미,브라운",
"count": 10
}
```

Choose a reason for hiding this comment

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

꼼꼼하시네요!


- 응답 형식
```text
HTTP/1.1 200
Content-Type: application/json

{
"winners": "브리",
"racingCars": [
{
"name": "브리",
"position": 9
},
{
"name": "토미",
"position": 7
}
]
}
```
- [x] 자동차 경주 게임 플레이 이력 조회 요청을 받아 플레이 이력을 응답한다.

- 요청 형식
```text
GET /plays HTTP/1.1
```

- 응답 형식
```text
HTTP/1.1 200
Content-Type: application/json

[
{
"winners": "브리",
"racingCars": [
{
"name": "브리",
"position": 6
},
{
"name": "토미",
"position": 4
},
{
"name": "브라운",
"position": 3
},
]
},
{
"winners": "브리,토미,브라운",
"racingCars": [
{
"name": "브리",
"position": 6
},
{
"name": "토미",
"position": 6
},
{
"name": "브라운",
"position": 6
},
]
}
]
```

### 서비스

- [x] 자동차 경주 게임을 진행 후 결과를 생성한다.
- [x] 자동차 경주 게임 플레이 이력을 조회한다.

### 레포지토리

- [x] 자동차 경주 도메인을 저장한다.
- [x] 자동차 경주 도메인 목록을 조회한다.

### Dao

- [x] 자동차 경주 게임 정보를 저장한다.
- [x] 자동차 경주 게임 정보를 조회한다.
- [x] 자동차 경주가 끝난 자동차 정보를 저장한다.
- [x] 자동차 경주가 끝난 자동차 정보를 조회한다.
- [x] 자동차 경주 우승자 정보를 저장한다.

### DB

- [x] RacingGame 테이블 생성한다.
- id, trial_count, date
- [x] RacingCar 테이블 생성한다.
- id, game_id, name, position
- [x] RacingWinner 테이블 생성한다.
- id, game_id, car_id

Choose a reason for hiding this comment

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

정갈하네 역시 누누!

17 changes: 10 additions & 7 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.9'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id 'java'
id 'org.springframework.boot' version '2.7.9'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}

sourceCompatibility = '11'

repositories {
mavenCentral()
mavenCentral()
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-validation'
runtimeOnly 'com.h2database:h2'
}

tasks.named('test') {
useJUnitPlatform()
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RacingCarApplication {

public static void main(String[] args) {
SpringApplication.run(RacingCarApplication.class, args);
}
public class RacingCarApiApplication {

public static void main(String[] args) {
SpringApplication.run(RacingCarApiApplication.class, args);
}
}
34 changes: 34 additions & 0 deletions src/main/java/racingcar/RacingCarConsoleApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package racingcar;

import racingcar.controller.RacingCarConsoleController;
import racingcar.domain.RandomNumberGenerator;
import racingcar.repository.RacingCarConsoleRepository;
import racingcar.service.RacingCarService;
import racingcar.view.InputView;
import racingcar.view.OutputView;

import java.util.Scanner;

public class RacingCarConsoleApplication {

public static void main(String[] args) {
final RacingCarConsoleController racingCarConsoleController = new RacingCarConsoleController(
inputView(),
outputView(),
new RacingCarService(new RacingCarConsoleRepository(), new RandomNumberGenerator())
Comment on lines +15 to +18
Copy link
Member

Choose a reason for hiding this comment

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

변수로 빼주면 가독성이 좋아질거 같아요

);
racingCarConsoleController.run();
}

private static InputView inputView() {
return new InputView(scanner());
}

private static Scanner scanner() {
return new Scanner(System.in);
}

private static OutputView outputView() {
return new OutputView();
}
}
35 changes: 35 additions & 0 deletions src/main/java/racingcar/controller/RacingCarApiController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package racingcar.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import racingcar.dto.request.RacingCarRequest;
import racingcar.dto.response.RacingGameResponse;
import racingcar.service.RacingCarService;

import javax.validation.Valid;
import java.util.List;

@RestController
public class RacingCarApiController {

private final RacingCarService racingCarService;

public RacingCarApiController(final RacingCarService racingCarService) {
this.racingCarService = racingCarService;
}

@PostMapping("/plays")
public ResponseEntity<RacingGameResponse> play(@Valid @RequestBody RacingCarRequest racingCarRequest) {
final RacingGameResponse result = racingCarService.play(racingCarRequest.getNames(), racingCarRequest.getCount());
return ResponseEntity.ok(result);
Copy link
Author

Choose a reason for hiding this comment

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

201이 더 좋은 코드같아보이는데? 어떻게 생각해

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.

누누가 아니라 다즐이었어? ㅇㄴ

}

@GetMapping("/plays")
public ResponseEntity<List<RacingGameResponse>> findPlays() {
final List<RacingGameResponse> result = racingCarService.findPlays();
return ResponseEntity.ok(result);
}
}
34 changes: 34 additions & 0 deletions src/main/java/racingcar/controller/RacingCarConsoleController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package racingcar.controller;

import racingcar.dto.response.RacingGameResponse;
import racingcar.service.RacingCarService;
import racingcar.view.InputView;
import racingcar.view.OutputView;

public class RacingCarConsoleController {

private final InputView inputView;
private final OutputView outputView;
private final RacingCarService racingCarService;

public RacingCarConsoleController(final InputView inputView, final OutputView outputView, final RacingCarService racingCarService) {
this.inputView = inputView;
this.outputView = outputView;
this.racingCarService = racingCarService;
}

public void run() {
final RacingGameResponse racingGameResponse = playRacingGame();
outputView.printRacingGameResult(racingGameResponse);
}

private RacingGameResponse playRacingGame() {
while (true) {
try {
return racingCarService.play(inputView.readCarNames(), inputView.readCount());
} catch (IllegalArgumentException e) {
outputView.printErrorMessage(e.getMessage());
}
}
}
}
53 changes: 53 additions & 0 deletions src/main/java/racingcar/dao/RacingCarDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package racingcar.dao;

import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import racingcar.entity.RacingCarEntity;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

@Repository
public class RacingCarDao {

private final JdbcTemplate jdbcTemplate;

public RacingCarDao(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

public void saveAll(final List<RacingCarEntity> racingCarEntities) {
final String sql = "INSERT INTO RACING_CAR (game_id, name, position) VALUES (?, ?, ?)";

jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {

Choose a reason for hiding this comment

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

쩌는데?

batchUpdateNamedParameter 랑 같이 쓸 수는 없나요?

@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setLong(1, racingCarEntities.get(i).getGameId());
ps.setString(2, racingCarEntities.get(i).getName());
ps.setInt(3, racingCarEntities.get(i).getPosition());
}

@Override
public int getBatchSize() {
return racingCarEntities.size();
}
});
Comment on lines +24 to +36
Copy link
Author

Choose a reason for hiding this comment

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

simpleInsert 사용 해보면 어때?
코드 진짜 깔끔해지던데

Choose a reason for hiding this comment

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

써봤다가 Insert 쿼리 1개에 너무 의존적인 객체인 것 같아서 모든 쿼리에 범용적으로 사용할 수 있는 JdbcTemplate으로 돌아왔어

진짜 깔끔해지기는 하더라 좋은 의견 감사합니다 👍

}

public List<RacingCarEntity> findAllByGameId(final Long gameId) {
final String sql = "SELECT * FROM RACING_CAR WHERE game_id = ?";

return jdbcTemplate.query(
sql,
(rs, rowNum) -> RacingCarEntity.builder()
.id(rs.getLong(1))
.gameId(rs.getLong(2))
.name(rs.getString(3))
.position(rs.getInt(4))
.build(),
gameId
Comment on lines +44 to +50
Copy link
Author

Choose a reason for hiding this comment

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

왜 빌더를 둔거야? 궁금스 궁금스
빌더의 장점이 뭐라고 생각해?

Copy link

@woo-chang woo-chang Apr 23, 2023

Choose a reason for hiding this comment

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

@Builder 어노테이션만 사용해오다가 직접 구현해보고 싶었고, 필드가 많아져서 동일한 타입의 파라미터가 많아졌을 때 이름을 통해 실수를 줄일 수 있다는 장점이 있는 것 같아

사실 필드가 적다면 생성자나 정적 팩터리 메서드 사용하는게 훨씬 좋을 것 같아 🥳

);
}
}
48 changes: 48 additions & 0 deletions src/main/java/racingcar/dao/RacingGameDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package racingcar.dao;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import racingcar.entity.RacingGameEntity;

import java.sql.PreparedStatement;
import java.sql.Timestamp;
import java.util.List;
import java.util.Objects;

@Repository
public class RacingGameDao {
Comment on lines +14 to +15
Copy link
Author

Choose a reason for hiding this comment

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

이거까지 빈이라면 개발자가 어떤 것을 사용해야되는지 감을 잡기 어려울 것 같아서
repository 만 빈으로 등록하는게 어떨까?

Choose a reason for hiding this comment

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

어떤 것을 사용해야되는지가 어떤 의미일까용?


private final JdbcTemplate jdbcTemplate;

public RacingGameDao(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

public Long save(final RacingGameEntity racingGameEntity) {
final String sql = "INSERT INTO RACING_GAME (trial_count) VALUES (?)";

final KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
final PreparedStatement preparedStatement = connection.prepareStatement(sql, new String[]{"id"});
preparedStatement.setInt(1, racingGameEntity.getTrialCount());
return preparedStatement;
}, keyHolder);

return Objects.requireNonNull(keyHolder.getKey()).longValue();
Copy link
Author

Choose a reason for hiding this comment

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

Objects.requireNonNull(); 사용한 이유는 뭐야??

Choose a reason for hiding this comment

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

https://hudi.blog/java-requirenonnull/

intelliJ의 추천이었지만, 리뷰를 보고 저도 찾아보게 되었습니다 👍

}

public List<RacingGameEntity> findAll() {
final String sql = "SELECT * FROM RACING_GAME";

return jdbcTemplate.query(
sql,
(rs, rowNum) -> RacingGameEntity.builder()
.id(rs.getLong(1))
.trialCount(rs.getInt(2))
.date(new Timestamp(rs.getDate(3).getTime()).toLocalDateTime())
.build()
);
}
}
Loading