From 8ca4fac5faf591464f6ea5db8630a981fc553f33 Mon Sep 17 00:00:00 2001 From: zillionme Date: Tue, 9 May 2023 20:23:10 +0900 Subject: [PATCH 01/16] =?UTF-8?q?docs:=20=EA=B8=B0=EB=8A=A5=20=EC=9A=94?= =?UTF-8?q?=EA=B5=AC=EC=82=AC=ED=95=AD=20=EA=B5=AC=ED=98=84=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a0f66ac2..d43260dea 100644 --- a/README.md +++ b/README.md @@ -1 +1,69 @@ -# jwp-subway-path \ No newline at end of file +# jwp-subway-path + +## API 기능 요구사항 구현하기 + +1. 노선에 역 등록 API 신규 구현 + 1. 노선 신규 등록 + :ux를 고려했을 때, 신규 노선을 등록할 때, 초기 역을 함께 설정하는 것이 적합하다고 판단함 + ``` + POST /lines + { + "name": "2호선" + "color": "Green" + } + ``` + + 2. 기존 노선에 역 등록 + ``` + POST /lines/{lineId}/stations + { + "upStation": "낙성대id" + "downStation": "사당id" + "distance": 1 + } + ``` + - 빈 노선인 경우,두 역을 동시에 등록해야 한다 + - 빈노선이 아닌 경우, 한 역은 해당 노선에 존재하는 역이어야 하고, 다른 역은 노선에 등록되지 않은 역이어야 한다 + - 거리 정보는 양의 정수로 제한한다 + +2. 노선에 역 제거 API 신규 구현 + 1. 노선 삭제 + :ux를 고려했을 때, 신규 노선을 등록할 때, 초기 역을 함께 설정하는 것이 적합하다고 판단함 + ``` + DELETE /lines/{lineId} + ``` + + 2. 노선에 역 삭제 + ``` + DELETE /lines/{lineId}/stations/{stationId} + ``` + - +3. 노선 조회 API 수정 + 1. 노선 조회 + ``` + GET /lines/{lineId} + ``` + + 2. 노선 목록 조회 + ``` + GET /lines + ``` + + +4. 테스트 하기 + - 노선에 역이 하나도 등록되지 않은 상황에서 최초 등록 시 두 역을 동시에 등록해야 합니다. + - 하나의 역은 여러 노선에 등록이 될 수 있습니다. + (1호선: A-B-C, 2호선: Z-B-D 처럼 B역은 두개 이상의 노선에 포함될 수 있습니다.) + - 노선 가운데 역이 등록 될 경우 거리 정보를 고려해야 합니다. + A-B-C 노선에서 B 다음에 D 역을 등록하려고 하는데 + B-C가 3km, B-D거리가 2km라면 B-D거리는 2km로 등록되어야 하고 D-C 거리는 1km로 등록되어야 합니다. + - 노선 가운데 역이 등록 될 경우 거리는 양의 정수라는 비즈니스 규칙을 지켜야 합니다. + A-B-C 노선에서 B 다음에 D 역을 등록하려고 하는데 + B-C역의 거리가 3km인 경우 B-D 거리는 3km보다 적어야 합니다. + - 노선에 역 제거 테스트 + - 노선에서 역을 제거할 경우 정상 동작을 위해 재배치 되어야 합니다. + A-B-C-D 역이 있는 노선에서 C역이 제거되는 경우 A-B-D 순으로 재배치됩니다. + - 노선에서 역이 제거될 경우 역과 역 사이의 거리도 재배정되어야 합니다. + A-B가 2km, B-C가 3km, C-D가 4km인 경우 C역이 제거되면 B-D 거리가 7km가 되어야 합니다. + - 노선에 등록된 역이 2개 인 경우 하나의 역을 제거할 때 두 역이 모두 제거되어야 합니다. + A-B 노선에서 B를 제거할 때 거리 정보를 포함할 수 없기 때문에 두 역 모두 제거되어야 합니다. From d719f09f3fce9ec403ebe82f5c635229c89edfdb Mon Sep 17 00:00:00 2001 From: zillionme Date: Tue, 9 May 2023 21:59:54 +0900 Subject: [PATCH 02/16] =?UTF-8?q?test:=20LineController=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/subway/ui/LineControllerTest.java | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 src/test/java/subway/ui/LineControllerTest.java diff --git a/src/test/java/subway/ui/LineControllerTest.java b/src/test/java/subway/ui/LineControllerTest.java new file mode 100644 index 000000000..5e151c868 --- /dev/null +++ b/src/test/java/subway/ui/LineControllerTest.java @@ -0,0 +1,120 @@ +package subway.ui; + +import static org.mockito.BDDMockito.any; +import static org.mockito.BDDMockito.anyLong; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import subway.application.LineService; +import subway.dto.LineRequest; +import subway.dto.LineResponse; + +@WebMvcTest(LineController.class) +class LineControllerTest { + + @Autowired + ObjectMapper objectMapper; + + @Autowired + MockMvc mockMvc; + + @MockBean + LineService lineService; + + @DisplayName("노선 정상 생성한다") + @Test + void createLine() throws Exception { + given(lineService.saveLine(any())) + .willReturn(new LineResponse(1L, "2호선", "green", null)); + + mockMvc.perform(post("/lines") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(new LineRequest("2호선", "green")))) + .andExpect(status().isCreated()) + .andExpect(header().string("Location", "/lines/1")) + .andExpect(jsonPath("$.id").value(1L)) + .andExpect(jsonPath("$.name").value("2호선")) + .andExpect(jsonPath("$.color").value("green")) + .andDo(print()); + } + + @DisplayName("전체 노선을 조회한다") + @Test + void findAllLines() throws Exception { + List lines = List.of( + new LineResponse(1L, "2호선", "green", Collections.emptyList()), + new LineResponse(2L, "1호선", "blue", List.of("동인천역", "주안역"))); + given(lineService.findLineResponses()) + .willReturn(lines); + + //todo 처음 배운 정보 기록1 : Matchers.empty() 메서드는 Hamcrest 라이브러리의 Matcher 클래스의 정적 메서드이며, 빈 리스트(empty list)인 경우에 true를 반환합니다. + mockMvc.perform(get("/lines")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value(1L)) + .andExpect(jsonPath("$[0].name").value("2호선")) + .andExpect(jsonPath("$[0].color").value("green")) + .andExpect(jsonPath("$[0].stations").value(Matchers.empty())) + .andExpect(jsonPath("$[1].id").value(2L)) + .andExpect(jsonPath("$[1].name").value("1호선")) + .andExpect(jsonPath("$[1].color").value("blue")) + .andExpect(jsonPath("$[1].stations[0]").value("동인천역")) + .andExpect(jsonPath("$[1].stations[1]").value("주안역")) + .andDo(print()); + } + + @DisplayName("특정 노선을 조회한다") + @Test + void findLineById() throws Exception { + Long lineId = 1L; + given(lineService.findLineResponseById(anyLong())) + .willReturn(new LineResponse(lineId, "1호선", "blue", List.of("동인천역", "주안역"))); + mockMvc.perform(get("/lines/"+lineId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1L)) + .andExpect(jsonPath("$.name").value("1호선")) + .andExpect(jsonPath("$.color").value("blue")) + .andExpect(jsonPath("$.stations[0]").value("동인천역")) + .andExpect(jsonPath("$.stations[1]").value("주안역")) + .andDo(print()); + } + + // todo : 수정에 성공했을 때 이를 클라이언트에게도 반환값을 통해 알릴 것인가? + @DisplayName("특정 노선의 정보를 수정한다") + @Test + void updateLine() throws Exception { + Long lineId = 1L; + mockMvc.perform(put("/lines/" + lineId) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(new LineRequest("2호선", "green")))) + .andExpect(status().isOk()) + .andDo(print()); + } + + @DisplayName("특정 노선을 삭제한다") + @Test + void deleteLine() throws Exception { + Long lineId = 1L; + mockMvc.perform(delete("/lines/"+ lineId)) + .andExpect(status().isNoContent()) + .andDo(print()); + } +} From fe1f1a983d4879eedacec8e610a93facce4ae976 Mon Sep 17 00:00:00 2001 From: zillionme Date: Thu, 11 May 2023 17:28:14 +0900 Subject: [PATCH 03/16] =?UTF-8?q?feat:=20=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/subway/domain/Line.java | 41 +++++++++++--------- src/main/java/subway/domain/Section.java | 46 +++++++++++++++++++++++ src/main/java/subway/domain/Sections.java | 45 ++++++++++++++++++++++ src/main/java/subway/domain/Station.java | 8 ++++ 4 files changed, 123 insertions(+), 17 deletions(-) create mode 100644 src/main/java/subway/domain/Section.java create mode 100644 src/main/java/subway/domain/Sections.java diff --git a/src/main/java/subway/domain/Line.java b/src/main/java/subway/domain/Line.java index 699d0b6df..3614fd688 100644 --- a/src/main/java/subway/domain/Line.java +++ b/src/main/java/subway/domain/Line.java @@ -1,24 +1,40 @@ package subway.domain; -import java.util.Objects; +import java.util.List; +//todo 생각해볼것1: 도메인 객체를 값객체로 감싸야 하는지? +//todo 생각해볼것2: addStation같은 역할을 부여해야 하는지? +//todo 생각해볼것3: 비즈니스로직에서 id가 필요한가? public class Line { - private Long id; - private String name; - private String color; - public Line() { - } + private final Long id; + private final String name; + private final String color; + private final Sections sections; public Line(String name, String color) { + this.id = null; this.name = name; this.color = color; + this.sections = Sections.EMPTY_SECTION; } public Line(Long id, String name, String color) { this.id = id; this.name = name; this.color = color; + this.sections = Sections.EMPTY_SECTION; + } + + public Line(Long id, String name, String color, Sections sections) { + this.id = id; + this.name = name; + this.color = color; + this.sections = sections; + } + + public List findStations() { + return sections.findStations(); } public Long getId() { @@ -33,16 +49,7 @@ public String getColor() { return color; } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Line line = (Line) o; - return Objects.equals(id, line.id) && Objects.equals(name, line.name) && Objects.equals(color, line.color); - } - - @Override - public int hashCode() { - return Objects.hash(id, name, color); + public Sections getSections() { + return sections; } } diff --git a/src/main/java/subway/domain/Section.java b/src/main/java/subway/domain/Section.java new file mode 100644 index 000000000..fac08cbcb --- /dev/null +++ b/src/main/java/subway/domain/Section.java @@ -0,0 +1,46 @@ +package subway.domain; + +import java.util.Objects; + +public class Section { + + private final Station upStation; + private final Station downStation; + private final Integer distance; + + public Section(Station upStation, Station downStation, Integer distance) { + this.upStation = upStation; + this.downStation = downStation; + this.distance = distance; + } + + public Station getUpStation() { + return upStation; + } + + public Station getDownStation() { + return downStation; + } + + public Integer getDistance() { + return distance; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Section section = (Section) o; + return Objects.equals(upStation, section.upStation) && Objects.equals(downStation, + section.downStation) && Objects.equals(distance, section.distance); + } + + @Override + public int hashCode() { + return Objects.hash(upStation, downStation, distance); + } +} diff --git a/src/main/java/subway/domain/Sections.java b/src/main/java/subway/domain/Sections.java new file mode 100644 index 000000000..299184d8d --- /dev/null +++ b/src/main/java/subway/domain/Sections.java @@ -0,0 +1,45 @@ +package subway.domain; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +public class Sections { + + public static final Sections EMPTY_SECTION = new Sections(Collections.EMPTY_LIST); + + private final List
sections; + + public Sections(List
sections) { + this.sections = sections; + } + + public List findStations() { + + List upStations = sections.stream() + .map(Section::getUpStation) + .collect(Collectors.toList()); + + List downStations = sections.stream() + .map(Section::getDownStation) + .collect(Collectors.toList()); + + Station startStation = upStations.stream() + .filter(station -> !downStations.contains(station)) + .findFirst().orElseThrow(()->new IllegalStateException("상행 종점을 찾을 수 없습니다")); + + List stations = new LinkedList<>(List.of(startStation)); + + while (stations.size() <= sections.size()) { + for (Section section : sections) { + if (stations.get(stations.size()-1).equals(section.getUpStation())) { + stations.add(section.getDownStation()); + } + } + } + + return stations; + } + +} diff --git a/src/main/java/subway/domain/Station.java b/src/main/java/subway/domain/Station.java index dbf9d7835..e7d923db7 100644 --- a/src/main/java/subway/domain/Station.java +++ b/src/main/java/subway/domain/Station.java @@ -38,4 +38,12 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(id, name); } + + @Override + public String toString() { + return "Station{" + + "id=" + id + + ", name='" + name + '\'' + + '}'; + } } From eab909fba4713874cfbf60bdcd52b1cdcad8fb24 Mon Sep 17 00:00:00 2001 From: zillionme Date: Thu, 11 May 2023 17:30:12 +0900 Subject: [PATCH 04/16] =?UTF-8?q?feat:=20entity,=20dao=20=EC=98=81?= =?UTF-8?q?=EC=86=8D=EC=84=B1=20=EA=B3=84=EC=B8=B5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/subway/dao/LineDao.java | 54 +++++++-- src/main/java/subway/dao/LineEntity.java | 45 +++++++ src/main/java/subway/dao/SectionDao.java | 120 +++++++++++++++++++ src/main/java/subway/dao/SectionEntity.java | 55 +++++++++ src/main/java/subway/dao/StationDao.java | 10 +- src/test/java/subway/dao/SectionDaoTest.java | 106 ++++++++++++++++ 6 files changed, 375 insertions(+), 15 deletions(-) create mode 100644 src/main/java/subway/dao/LineEntity.java create mode 100644 src/main/java/subway/dao/SectionDao.java create mode 100644 src/main/java/subway/dao/SectionEntity.java create mode 100644 src/test/java/subway/dao/SectionDaoTest.java diff --git a/src/main/java/subway/dao/LineDao.java b/src/main/java/subway/dao/LineDao.java index f644bac29..35a6402a9 100644 --- a/src/main/java/subway/dao/LineDao.java +++ b/src/main/java/subway/dao/LineDao.java @@ -1,23 +1,25 @@ package subway.dao; +import java.util.Optional; +import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.simple.SimpleJdbcInsert; import org.springframework.stereotype.Repository; -import subway.domain.Line; import javax.sql.DataSource; import java.util.HashMap; import java.util.List; import java.util.Map; +import subway.domain.Line; @Repository public class LineDao { private final JdbcTemplate jdbcTemplate; private final SimpleJdbcInsert insertAction; - private RowMapper rowMapper = (rs, rowNum) -> - new Line( + private RowMapper rowMapper = (rs, rowNum) -> + new LineEntity( rs.getLong("id"), rs.getString("name"), rs.getString("color") @@ -30,32 +32,58 @@ public LineDao(JdbcTemplate jdbcTemplate, DataSource dataSource) { .usingGeneratedKeyColumns("id"); } - public Line insert(Line line) { + public LineEntity insert(LineEntity lineEntity) { Map params = new HashMap<>(); - params.put("id", line.getId()); - params.put("name", line.getName()); - params.put("color", line.getColor()); + params.put("id", lineEntity.getId()); + params.put("name", lineEntity.getName()); + params.put("color", lineEntity.getColor()); Long lineId = insertAction.executeAndReturnKey(params).longValue(); - return new Line(lineId, line.getName(), line.getColor()); + return new LineEntity(lineId, lineEntity.getName(), lineEntity.getColor()); } - public List findAll() { + public List findAll() { String sql = "select id, name, color from LINE"; return jdbcTemplate.query(sql, rowMapper); } - public Line findById(Long id) { + //todo 찾아볼 것 : queryForObject이 null을 반환하는 경우가 무엇인지? + public Optional findById(Long id) { String sql = "select id, name, color from LINE WHERE id = ?"; - return jdbcTemplate.queryForObject(sql, rowMapper, id); + try { + return Optional.of(jdbcTemplate.queryForObject(sql, rowMapper, id)); + } + catch (DataAccessException e) { + return Optional.empty(); + } } - public void update(Line newLine) { + public void update(LineEntity newLineEntity) { String sql = "update LINE set name = ?, color = ? where id = ?"; - jdbcTemplate.update(sql, new Object[]{newLine.getName(), newLine.getColor(), newLine.getId()}); + jdbcTemplate.update(sql, new Object[]{newLineEntity.getName(), newLineEntity.getColor(), newLineEntity.getId()}); } public void deleteById(Long id) { jdbcTemplate.update("delete from Line where id = ?", id); } + + public Optional findByName(String name) { + String sql = "select id, name, color from LINE WHERE name = ?"; + try { + return Optional.of(jdbcTemplate.queryForObject(sql, rowMapper, name)); + } + catch (DataAccessException e) { + return Optional.empty(); + } + } + + public Optional findByColor(String color) { + String sql = "select id, name, color from LINE WHERE color = ?"; + try { + return Optional.of(jdbcTemplate.queryForObject(sql, rowMapper, color)); + } + catch (DataAccessException e) { + return Optional.empty(); + } + } } diff --git a/src/main/java/subway/dao/LineEntity.java b/src/main/java/subway/dao/LineEntity.java new file mode 100644 index 000000000..3137c6086 --- /dev/null +++ b/src/main/java/subway/dao/LineEntity.java @@ -0,0 +1,45 @@ +package subway.dao; + +import java.util.Objects; + +public class LineEntity { + private Long id; + private String name; + private String color; + + public LineEntity(String name, String color) { + this.name = name; + this.color = color; + } + + public LineEntity(Long id, String name, String color) { + this.id = id; + this.name = name; + this.color = color; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getColor() { + return color; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LineEntity lineEntity = (LineEntity) o; + return Objects.equals(id, lineEntity.id) && Objects.equals(name, lineEntity.name) && Objects.equals(color, lineEntity.color); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, color); + } +} diff --git a/src/main/java/subway/dao/SectionDao.java b/src/main/java/subway/dao/SectionDao.java new file mode 100644 index 000000000..4a3ada856 --- /dev/null +++ b/src/main/java/subway/dao/SectionDao.java @@ -0,0 +1,120 @@ +package subway.dao; + +import java.util.List; +import java.util.Optional; +import javax.sql.DataSource; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.namedparam.SqlParameterSource; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Repository; +import subway.domain.Section; +import subway.domain.Station; + +@Repository +public class SectionDao { + + private static final RowMapper
sectionRowMapper = (rs, rowNum) -> + new Section( + new Station(rs.getLong("up_station_id"), rs.getString("up_station_name")), + new Station(rs.getLong("down_station_id"), rs.getString("down_station_name")), + rs.getInt("distance") + ); + private static final RowMapper sectionEntityRowMapper = (rs, rowNum) -> + new SectionEntity( + rs.getLong("line_id"), + rs.getLong("up_station_id"), + rs.getLong("down_station_id"), + rs.getInt("distance") + ); + + private final NamedParameterJdbcTemplate jdbcTemplate; + private final SimpleJdbcInsert insertAction; + + public SectionDao(NamedParameterJdbcTemplate jdbcTemplate, DataSource dataSource) { + this.jdbcTemplate = jdbcTemplate; + this.insertAction = new SimpleJdbcInsert(dataSource) + .withTableName("section"); + } + + public List
findSectionsByLineId(Long lineId) { + String sql = "SELECT up.id as up_station_id, up.name as up_station_name, down.id as down_station_id, down.name as down_station_name, s.distance객 " + + "FROM SECTION AS s " + + "JOIN STATION AS up ON s.up_station_id = up.id " + + "JOIN STATION AS down ON s.down_station_id = down.id " + + "WHERE s.line_id = :line_id"; + + SqlParameterSource source = new MapSqlParameterSource() + .addValue("line_id", lineId); + + return jdbcTemplate.query(sql, source, sectionRowMapper); + } + + public Optional> findByLineId(Long lineId) { + String sql = "SELECT line_id, up_station_id, down_station_id, distance FROM SECTION WHERE line_id = :line_id"; + SqlParameterSource source = new MapSqlParameterSource() + .addValue("line_id", lineId); + try { + return Optional.of(jdbcTemplate.query(sql, source, sectionEntityRowMapper)); + } catch (DataAccessException exception) { + return Optional.empty(); + } + } + + public Optional findByUpStationId(Long upStationId, Long lineId) { + String sql = "SELECT line_id, up_station_id, down_station_id, distance FROM SECTION WHERE line_id = :line_id AND up_station_id = :up_station_id"; + SqlParameterSource source = new MapSqlParameterSource() + .addValue("up_station_id", upStationId) + .addValue("line_id", lineId); + try { + return Optional.of(jdbcTemplate.queryForObject(sql, source, sectionEntityRowMapper)); + } catch (DataAccessException exception) { + return Optional.empty(); + } + } + + public Optional findByDownStationId(Long downStationId, Long lineId) { + String sql = "SELECT line_id, up_station_id, down_station_id, distance FROM SECTION WHERE line_id = :line_id AND down_station_id = :down_station_id"; + SqlParameterSource source = new MapSqlParameterSource() + .addValue("down_station_id", downStationId) + .addValue("line_id", lineId); + try { + return Optional.of(jdbcTemplate.queryForObject(sql, source, sectionEntityRowMapper)); + } catch (DataAccessException exception) { + return Optional.empty(); + } + } + + public void insert(SectionEntity sectionEntity) { + SqlParameterSource source = new BeanPropertySqlParameterSource(sectionEntity); + insertAction.execute(source); + } + + public void updateByUpStationId(SectionEntity sectionEntity) { + String sql = "UPDATE SECTION SET down_station_id = :downStationId, distance = :distance " + + "WHERE line_id = :lineId AND up_station_id = :upStationsId"; + + SqlParameterSource source = new BeanPropertySqlParameterSource(sectionEntity); + jdbcTemplate.update(sql, source); + } + + public void updateByDownStationId(SectionEntity sectionEntity) { + String sql = "UPDATE SECTION SET up_station_id = :upStationsId, distance = :distance " + + "WHERE line_id = :lineId AND down_station_id = :downStationId"; + + SqlParameterSource source = new BeanPropertySqlParameterSource(sectionEntity); + jdbcTemplate.update(sql, source); + } + + public void delete(SectionEntity sectionEntity) { + String sql = "DELETE FROM SECTION WHERE up_station_id = :upStationId AND down_station_id = :downStationId"; + + SqlParameterSource source = new BeanPropertySqlParameterSource(sectionEntity); + jdbcTemplate.update(sql, source); + } + +} diff --git a/src/main/java/subway/dao/SectionEntity.java b/src/main/java/subway/dao/SectionEntity.java new file mode 100644 index 000000000..38eec7e16 --- /dev/null +++ b/src/main/java/subway/dao/SectionEntity.java @@ -0,0 +1,55 @@ +package subway.dao; + +import java.util.Objects; + +public class SectionEntity { + + private Long lineId; + private Long upStationId; + private Long downStationId; + private Integer distance; + + public SectionEntity() { + } + + public SectionEntity(Long lineId, Long upStationId, Long downStationId, Integer distance) { + this.lineId = lineId; + this.upStationId = upStationId; + this.downStationId = downStationId; + this.distance = distance; + } + + public Long getLineId() { + return lineId; + } + + public Long getUpStationId() { + return upStationId; + } + + public Long getDownStationId() { + return downStationId; + } + + public Integer getDistance() { + return distance; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SectionEntity sectionEntity = (SectionEntity) o; + return Objects.equals(lineId, sectionEntity.lineId) && Objects.equals(upStationId, + sectionEntity.upStationId) && Objects.equals(downStationId, sectionEntity.downStationId); + } + + @Override + public int hashCode() { + return Objects.hash(lineId, upStationId, downStationId); + } +} diff --git a/src/main/java/subway/dao/StationDao.java b/src/main/java/subway/dao/StationDao.java index 07f7eab30..bc5d96a2b 100644 --- a/src/main/java/subway/dao/StationDao.java +++ b/src/main/java/subway/dao/StationDao.java @@ -1,5 +1,7 @@ package subway.dao; +import java.util.Optional; +import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; @@ -41,9 +43,13 @@ public List findAll() { return jdbcTemplate.query(sql, rowMapper); } - public Station findById(Long id) { + public Optional findById(Long id) { String sql = "select * from STATION where id = ?"; - return jdbcTemplate.queryForObject(sql, rowMapper, id); + try { + return Optional.of(jdbcTemplate.queryForObject(sql, rowMapper, id)); + } catch (DataAccessException exception) { + return Optional.empty(); + } } public void update(Station newStation) { diff --git a/src/test/java/subway/dao/SectionDaoTest.java b/src/test/java/subway/dao/SectionDaoTest.java new file mode 100644 index 000000000..618f60452 --- /dev/null +++ b/src/test/java/subway/dao/SectionDaoTest.java @@ -0,0 +1,106 @@ +package subway.dao; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import javax.sql.DataSource; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.test.context.jdbc.Sql; +import subway.domain.Section; +import subway.domain.Station; + +@JdbcTest +@Sql("classpath:initializeTestDb.sql") +class SectionDaoTest { + + private static final RowMapper sectionEntityRowMapper = (rs, rowNum) -> + new SectionEntity( + rs.getLong("line_id"), + rs.getLong("up_station_id"), + rs.getLong("down_station_id"), + rs.getInt("distance") + ); + @Autowired + private NamedParameterJdbcTemplate jdbcTemplate; + @Autowired + DataSource dataSource; + private SectionDao sectionDao; + + @BeforeEach + void setup() { + sectionDao = new SectionDao(jdbcTemplate, dataSource); + } + + @DisplayName("노선의 id를 입력했을 때, 해당 노선의 구간 정보를 section객체로 반환한다") + @Test + void findSectionsByLineId() { + List
sections = sectionDao.findSectionsByLineId(1L); + assertThat(sections) + .containsExactly( + new Section(new Station(2L, "봉천역"), new Station(1L, "서울대입구역"), 5), + new Section(new Station(1L, "서울대입구역"), new Station(4L, "사당역"), 7)); + } + + @DisplayName("노선의 id를 입력했을 때, 해당 노선의 구간 정보를 SectionEntity 체로 반환한다") + @Test + void findByLineId() { + List sectionEntities = sectionDao.findByLineId(1L).get(); + assertThat(sectionEntities).containsExactly( + new SectionEntity(1L, 2L, 1L, 5), + new SectionEntity(1L, 1L, 4L, 7)); + + } + + @DisplayName("노선의 UpStationId를 입력했을 때, 해당 구간 정보를 SectionEntity 객체로 반환한다") + @Test + void findByUpStationId() { + SectionEntity section = sectionDao.findByUpStationId(1L, 1L).get(); + assertThat(section).isEqualTo( + new SectionEntity(1L, 1L, 4L, 7)); + } + + @DisplayName("노선의 DownStationId를 입력했을 때, 해당 구간 정보를 SectionEntity 객체로 반환한다") + @Test + void findByDownStationId() { + SectionEntity section = sectionDao.findByDownStationId(1L, 1L).get(); + assertThat(section).isEqualTo( + new SectionEntity(1L, 2L, 1L, 5)); + } + + @Test + @DisplayName("특정 노선에 구간을 추가한다") + void insert() { + sectionDao.insert(new SectionEntity(1L, 1L, 3L, 5)); + SectionEntity sectionEntity = jdbcTemplate.queryForObject( + "SELECT * FROM section WHERE line_id = :line_id and up_station_id = :up_station_id and down_station_id = :down_station_id", + new MapSqlParameterSource() + .addValue("line_id", 1L) + .addValue("up_station_id", 1L) + .addValue("down_station_id", 3L), + sectionEntityRowMapper + ); + assertThat(sectionEntity).usingRecursiveComparison() + .comparingOnlyFields() + .isEqualTo(new SectionEntity(1L, 1L, 3L, 5)); + } + + @Test + @DisplayName("특정 노선에 구간을 추가한다") + void delete() { + SectionEntity deleteSection = new SectionEntity(1L, 2L, 1L, 5); + sectionDao.delete(deleteSection); + + List sectionEntities = jdbcTemplate.query( + "SELECT * FROM section", + sectionEntityRowMapper + ); + assertThat(sectionEntities).doesNotContain(deleteSection); + } +} From 3cbf03cca3b388fbd3b0511fe03efde2759cac5a Mon Sep 17 00:00:00 2001 From: zillionme Date: Thu, 11 May 2023 17:31:09 +0900 Subject: [PATCH 05/16] =?UTF-8?q?feat:=20line=20Service=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/subway/application/LineService.java | 73 ++++++++++++++----- src/main/java/subway/dto/LineResponse.java | 22 +++++- 2 files changed, 76 insertions(+), 19 deletions(-) diff --git a/src/main/java/subway/application/LineService.java b/src/main/java/subway/application/LineService.java index bdb006f53..04fff259e 100644 --- a/src/main/java/subway/application/LineService.java +++ b/src/main/java/subway/application/LineService.java @@ -1,53 +1,92 @@ package subway.application; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; import org.springframework.stereotype.Service; import subway.dao.LineDao; +import subway.dao.LineEntity; +import subway.dao.SectionDao; import subway.domain.Line; +import subway.domain.Section; +import subway.domain.Sections; import subway.dto.LineRequest; import subway.dto.LineResponse; - -import java.util.List; -import java.util.stream.Collectors; +import subway.exception.LineDuplicatedException; @Service public class LineService { + private final LineDao lineDao; + private final SectionDao sectionDao; - public LineService(LineDao lineDao) { + + public LineService(LineDao lineDao, SectionDao sectionDao) { this.lineDao = lineDao; + this.sectionDao = sectionDao; } public LineResponse saveLine(LineRequest request) { - Line persistLine = lineDao.insert(new Line(request.getName(), request.getColor())); + validateDuplicateLine(request); + LineEntity lineEntity = new LineEntity(request.getName(), request.getColor()); + LineEntity savedLine = lineDao.insert(lineEntity); + Line line = new Line(savedLine.getId(), savedLine.getName(), savedLine.getColor(), Sections.EMPTY_SECTION); + return LineResponse.of(line); + } + + public LineResponse findLineResponseById(Long id) { + Line persistLine = findLineById(id); return LineResponse.of(persistLine); } + private Line findLineById(Long lineId) { + LineEntity lineEntity = lineDao.findById(lineId).orElseThrow(() -> new NotFoundException("해당 노선이 존재하지 않습니다")); + List
sections = sectionDao.findSectionsByLineId(lineId); + return new Line(lineId, lineEntity.getName(), lineEntity.getColor(), new Sections(sections)); + } + public List findLineResponses() { - List persistLines = findLines(); - return persistLines.stream() + List persistLine = findLines(); + return persistLine.stream() .map(LineResponse::of) .collect(Collectors.toList()); } public List findLines() { - return lineDao.findAll(); + List lineEntities = lineDao.findAll(); + return lineEntities.stream() + .map(lineEntity -> + new Line(lineEntity.getId(), lineEntity.getName(), lineEntity.getColor(), + new Sections(sectionDao.findSectionsByLineId(lineEntity.getId())) + ) + ) + .collect(Collectors.toList()); } - public LineResponse findLineResponseById(Long id) { - Line persistLine = findLineById(id); - return LineResponse.of(persistLine); + public void updateLine(Long id, LineRequest lineUpdateRequest) { + checkIfExistLine(id); + LineEntity lineEntity = new LineEntity(id, lineUpdateRequest.getName(), lineUpdateRequest.getColor()); + lineDao.update(lineEntity); } - public Line findLineById(Long id) { - return lineDao.findById(id); + public void deleteLineById(Long id) { + checkIfExistLine(id); + lineDao.deleteById(id); } - public void updateLine(Long id, LineRequest lineUpdateRequest) { - lineDao.update(new Line(id, lineUpdateRequest.getName(), lineUpdateRequest.getColor())); + private void checkIfExistLine(Long lineId) { + lineDao.findById(lineId).orElseThrow(() -> new NotFoundException("해당 노선이 존재하지 않습니다.")); } - public void deleteLineById(Long id) { - lineDao.deleteById(id); + private void validateDuplicateLine(LineRequest request) { + Optional optionalLineByName = lineDao.findByName(request.getName()); + optionalLineByName.ifPresent(findUser -> { + throw new LineDuplicatedException("이미 존재하는 노선 이름입니다."); + }); + Optional optionalLineByColor = lineDao.findByColor(request.getColor()); + optionalLineByColor.ifPresent(findUser -> { + throw new LineDuplicatedException("이미 존재하는 노선 색입니다."); + }); } } diff --git a/src/main/java/subway/dto/LineResponse.java b/src/main/java/subway/dto/LineResponse.java index c9b668122..999f67ba8 100644 --- a/src/main/java/subway/dto/LineResponse.java +++ b/src/main/java/subway/dto/LineResponse.java @@ -1,20 +1,30 @@ package subway.dto; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; import subway.domain.Line; +import subway.domain.Station; public class LineResponse { private Long id; private String name; private String color; + private List stations; - public LineResponse(Long id, String name, String color) { + public LineResponse(Long id, String name, String color, List stations) { this.id = id; this.name = name; this.color = color; + this.stations = stations; } public static LineResponse of(Line line) { - return new LineResponse(line.getId(), line.getName(), line.getColor()); + List stations = line.findStations().stream() + .map(Station::getName) + .collect(Collectors.toList()); + + return new LineResponse(line.getId(), line.getName(), line.getColor(), stations); } public Long getId() { @@ -28,4 +38,12 @@ public String getName() { public String getColor() { return color; } + + public List getStations() { + return stations; + } + + public static void main(String[] args) { + System.out.println(Collections.EMPTY_LIST); + } } From e3286c296546fd33fa590eceae94abff3603c8e9 Mon Sep 17 00:00:00 2001 From: zillionme Date: Thu, 11 May 2023 17:31:58 +0900 Subject: [PATCH 06/16] =?UTF-8?q?feat:=20Station=20Service=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/subway/application/StationService.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/subway/application/StationService.java b/src/main/java/subway/application/StationService.java index 603d9daa7..cb6f068f1 100644 --- a/src/main/java/subway/application/StationService.java +++ b/src/main/java/subway/application/StationService.java @@ -1,14 +1,13 @@ package subway.application; +import java.util.List; +import java.util.stream.Collectors; import org.springframework.stereotype.Service; import subway.dao.StationDao; import subway.domain.Station; import subway.dto.StationRequest; import subway.dto.StationResponse; -import java.util.List; -import java.util.stream.Collectors; - @Service public class StationService { private final StationDao stationDao; @@ -23,7 +22,8 @@ public StationResponse saveStation(StationRequest stationRequest) { } public StationResponse findStationResponseById(Long id) { - return StationResponse.of(stationDao.findById(id)); + Station station = stationDao.findById(id).orElseThrow(() -> new NotFoundException("해당 역이 존재하지 않습니다.")); + return StationResponse.of(station); } public List findAllStationResponses() { @@ -41,4 +41,4 @@ public void updateStation(Long id, StationRequest stationRequest) { public void deleteStationById(Long id) { stationDao.deleteById(id); } -} \ No newline at end of file +} From 53deea5d9fa4ca4a192e661899c8bcce60659b2d Mon Sep 17 00:00:00 2001 From: zillionme Date: Thu, 11 May 2023 17:32:17 +0900 Subject: [PATCH 07/16] =?UTF-8?q?feat:=20Section=20Service=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../subway/application/SectionService.java | 168 ++++++++++++++++++ src/main/java/subway/dto/SectionRequest.java | 26 +++ 2 files changed, 194 insertions(+) create mode 100644 src/main/java/subway/application/SectionService.java create mode 100644 src/main/java/subway/dto/SectionRequest.java diff --git a/src/main/java/subway/application/SectionService.java b/src/main/java/subway/application/SectionService.java new file mode 100644 index 000000000..86cdbdc74 --- /dev/null +++ b/src/main/java/subway/application/SectionService.java @@ -0,0 +1,168 @@ +package subway.application; + +import java.util.List; +import java.util.Optional; +import org.springframework.stereotype.Service; +import subway.dao.LineDao; +import subway.dao.SectionDao; +import subway.dao.SectionEntity; +import subway.dao.StationDao; +import subway.dto.SectionRequest; +import subway.exception.InvalidSectionException; + +@Service +public class SectionService { + + private final LineDao lineDao; + private final StationDao stationDao; + private final SectionDao sectionDao; + + public SectionService(LineDao lineDao, StationDao stationDao, SectionDao sectionDao) { + this.lineDao = lineDao; + this.stationDao = stationDao; + this.sectionDao = sectionDao; + } + + public void saveSection(Long lineId, SectionRequest sectionRequest) { + checkIfExistLine(lineId); + checkIfExistStation(sectionRequest.getUpStationId()); + checkIfExistStation(sectionRequest.getDownStationId()); + SectionEntity sectionEntity = new SectionEntity(lineId, sectionRequest.getUpStationId(), + sectionRequest.getDownStationId(), sectionRequest.getDistance()); + + if (isEmptyLine(lineId)) { + sectionDao.insert(sectionEntity); + return; + } + saveSectionWhenLineIsNotEmpty(sectionEntity); + } + + private boolean isEmptyLine(Long lineId) { + Optional> byLineId = sectionDao.findByLineId(lineId); + return byLineId.isEmpty(); + } + + private void saveSectionWhenLineIsNotEmpty(SectionEntity sectionEntity) { + checkIfImpossibleSection(sectionEntity); + addStationBetweenStations(sectionEntity); + } + + private void checkIfImpossibleSection(SectionEntity sectionEntity) { + Long lineId = sectionEntity.getLineId(); + Long upStationId = sectionEntity.getUpStationId(); + Long downStationId = sectionEntity.getDownStationId(); + boolean hasUpStationInLine = hasStationInLine(upStationId, lineId); + boolean hasDownStationInLine = hasStationInLine(downStationId, lineId); + + if (hasUpStationInLine && hasDownStationInLine) { + throw new InvalidSectionException("이미 존재하는 구간입니다."); + } + } + + private boolean hasStationInLine(Long stationId, Long lineId) { + return sectionDao.findByUpStationId(stationId, lineId).isPresent() + || sectionDao.findByDownStationId(stationId, lineId).isPresent(); + } + + private void addStationBetweenStations(SectionEntity sectionEntity) { + if (hasStationInLine(sectionEntity.getUpStationId(), sectionEntity.getLineId())) { + addSectionBasedOnUpStation(sectionEntity.getUpStationId(), sectionEntity); + return; + } + if (hasStationInLine(sectionEntity.getDownStationId(), sectionEntity.getLineId())) { + addSectionBasedOnDownStation(sectionEntity.getDownStationId(), sectionEntity); + return; + } + throw new InvalidSectionException("한 역은 기존의 노선에 존재해야 합니다."); + } + + public void addSectionBasedOnUpStation(Long upStationId, SectionEntity sectionToAdd) { + Long lineId = sectionToAdd.getLineId(); + Optional originalSectionEntity = sectionDao.findByUpStationId(upStationId, lineId); + if (originalSectionEntity.isPresent()) { + SectionEntity originalSection = originalSectionEntity.get(); + Long downStationIdOfOrigin = originalSection.getDownStationId(); + Long downStationIdOfToAdd = sectionToAdd.getDownStationId(); + int revisedDistance = findRevisedDistance(sectionToAdd, originalSection); + SectionEntity revisedSection = new SectionEntity(lineId, downStationIdOfToAdd, downStationIdOfOrigin, + revisedDistance); + sectionDao.updateByDownStationId(revisedSection); + sectionDao.insert(sectionToAdd); + } + + if (sectionDao.findByDownStationId(upStationId, lineId).isPresent()) { + sectionDao.insert(sectionToAdd); + } + } + + public void addSectionBasedOnDownStation(Long downStationId, SectionEntity sectionToAdd) { + Long lineId = sectionToAdd.getLineId(); + if (sectionDao.findByUpStationId(downStationId, lineId).isPresent()) { + sectionDao.insert(sectionToAdd); + } + Optional originalSectionEntity = sectionDao.findByDownStationId(downStationId, lineId); + if (originalSectionEntity.isPresent()) { + SectionEntity originalSection = originalSectionEntity.get(); + Long upStationIdOfOrigin = originalSection.getUpStationId(); + Long upStationIdOfToAdd = sectionToAdd.getUpStationId(); + int revisedDistance = findRevisedDistance(sectionToAdd, originalSection); + + SectionEntity revisedSection = new SectionEntity(lineId, upStationIdOfOrigin, upStationIdOfToAdd, + revisedDistance); + sectionDao.updateByUpStationId(revisedSection); + sectionDao.insert(sectionToAdd); + } + } + + private static int findRevisedDistance(SectionEntity sectionToAdd, SectionEntity originalSection) { + int revisedDistance = originalSection.getDistance() - sectionToAdd.getDistance(); + if (revisedDistance <= 0) { + throw new InvalidSectionException("현재 구간보다 큰 구간은 입력할 수 없습니다."); + } + return revisedDistance; + } + + public void removeStationFromLine(Long lineId, Long stationIdToRemove) { + checkIfExistLine(lineId); + checkIfExistStation(stationIdToRemove); + if (hasStationInLine(stationIdToRemove, lineId)) { + deleteStationBetweenStations(lineId, stationIdToRemove); + return; + } + Optional upStationEntity = sectionDao.findByUpStationId(stationIdToRemove, lineId); + if (upStationEntity.isPresent()) { + SectionEntity upSectionOfOrigin = upStationEntity.get(); + sectionDao.delete(upSectionOfOrigin); + return; + } + Optional downStationEntity = sectionDao.findByDownStationId(stationIdToRemove, lineId); + if (downStationEntity.isPresent()) { + SectionEntity downSectionOfOrigin = downStationEntity.get(); + sectionDao.delete(downSectionOfOrigin); + return; + } + throw new NotFoundException("노선에 역이 존재하지 않습니다."); + } + + private void deleteStationBetweenStations(Long lineId, Long stationIdToRemove) { + SectionEntity upSectionOfOrigin = sectionDao.findByUpStationId(stationIdToRemove, lineId).get(); + SectionEntity downSectionOfOrigin = sectionDao.findByDownStationId(stationIdToRemove, lineId).get(); + int revisedDistance = upSectionOfOrigin.getDistance() + downSectionOfOrigin.getDistance(); + + SectionEntity revisedSection = new SectionEntity(lineId, upSectionOfOrigin.getUpStationId(), + downSectionOfOrigin.getDownStationId(), revisedDistance); + + sectionDao.updateByUpStationId(revisedSection); + sectionDao.delete(downSectionOfOrigin); + } + + + private void checkIfExistLine(Long lineId) { + lineDao.findById(lineId).orElseThrow(() -> new NotFoundException("해당 노선이 존재하지 않습니다.")); + } + + private void checkIfExistStation(Long stationId) { + stationDao.findById(stationId).orElseThrow(() -> new NotFoundException("해당 역이 존재하지 않습니다.")); + } + +} diff --git a/src/main/java/subway/dto/SectionRequest.java b/src/main/java/subway/dto/SectionRequest.java new file mode 100644 index 000000000..8ebae6757 --- /dev/null +++ b/src/main/java/subway/dto/SectionRequest.java @@ -0,0 +1,26 @@ +package subway.dto; + +public class SectionRequest { + + private final Long upStationId; + private final Long downStationId; + private final Integer distance; + + public SectionRequest(Long upStationId, Long downStationId, Integer distance) { + this.upStationId = upStationId; + this.downStationId = downStationId; + this.distance = distance; + } + + public Long getUpStationId() { + return upStationId; + } + + public Long getDownStationId() { + return downStationId; + } + + public Integer getDistance() { + return distance; + } +} From ae48de26bcd5a7281cccf0c4e01bd9929d56f34b Mon Sep 17 00:00:00 2001 From: zillionme Date: Thu, 11 May 2023 17:32:37 +0900 Subject: [PATCH 08/16] =?UTF-8?q?feat:=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EB=B2=A0=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/schema.sql | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index fc6de5f5c..2f950f09e 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -9,6 +9,16 @@ create table if not exists LINE ( id bigint auto_increment not null, name varchar(255) not null unique, - color varchar(20) not null, + color varchar(20) not null unique, primary key(id) ); + +--//todo 물어볼 것 : 기본 키 id를 없애도 되는지? +create table if not exists SECTION +( + line_id bigint not null references line(id) on delete cascade, + up_station_id bigint not null references station(id) on delete cascade, + down_station_id bigint not null references station(id) on delete cascade, + distance int not null, + primary key(up_station_id, down_station_id) +); From 1ec59d70c247c326f96529f84fefc2e753ab891e Mon Sep 17 00:00:00 2001 From: zillionme Date: Thu, 11 May 2023 17:33:13 +0900 Subject: [PATCH 09/16] =?UTF-8?q?feat:=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/subway/ui/LineController.java | 17 ++++++--- .../java/subway/ui/SectionController.java | 35 +++++++++++++++++++ .../java/subway/ui/StationController.java | 22 ++++++------ 3 files changed, 58 insertions(+), 16 deletions(-) create mode 100644 src/main/java/subway/ui/SectionController.java diff --git a/src/main/java/subway/ui/LineController.java b/src/main/java/subway/ui/LineController.java index 3a335ee14..4bc7aa399 100644 --- a/src/main/java/subway/ui/LineController.java +++ b/src/main/java/subway/ui/LineController.java @@ -1,15 +1,22 @@ package subway.ui; +import java.net.URI; +import java.sql.SQLException; +import java.util.List; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import subway.application.LineService; import subway.dto.LineRequest; import subway.dto.LineResponse; -import java.net.URI; -import java.sql.SQLException; -import java.util.List; - @RestController @RequestMapping("/lines") public class LineController { diff --git a/src/main/java/subway/ui/SectionController.java b/src/main/java/subway/ui/SectionController.java new file mode 100644 index 000000000..8fcb1afbe --- /dev/null +++ b/src/main/java/subway/ui/SectionController.java @@ -0,0 +1,35 @@ +package subway.ui; + +import java.net.URI; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import subway.application.SectionService; +import subway.dto.SectionRequest; + +@RestController +@RequestMapping("/sections") +public class SectionController { + + private final SectionService sectionService; + + public SectionController(SectionService sectionService) { + this.sectionService = sectionService; + } + + @PostMapping("/{lineId}") + public ResponseEntity createSection(@PathVariable Long lineId, @RequestBody SectionRequest sectionRequest) { + sectionService.saveSection(lineId, sectionRequest); + return ResponseEntity.created(URI.create("/lines/" + lineId)).build(); + } + + @DeleteMapping("/{lineId}") + public ResponseEntity deleteSection(@PathVariable Long lineId, @RequestBody Long stationId) { + sectionService.removeStationFromLine(lineId, stationId); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/subway/ui/StationController.java b/src/main/java/subway/ui/StationController.java index 5bf52a9a9..4f2914f7d 100644 --- a/src/main/java/subway/ui/StationController.java +++ b/src/main/java/subway/ui/StationController.java @@ -1,14 +1,19 @@ package subway.ui; +import java.net.URI; +import java.util.List; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import subway.application.StationService; import subway.dto.StationRequest; import subway.dto.StationResponse; -import subway.application.StationService; - -import java.net.URI; -import java.sql.SQLException; -import java.util.List; @RestController @RequestMapping("/stations") @@ -46,9 +51,4 @@ public ResponseEntity deleteStation(@PathVariable Long id) { stationService.deleteStationById(id); return ResponseEntity.noContent().build(); } - - @ExceptionHandler(SQLException.class) - public ResponseEntity handleSQLException() { - return ResponseEntity.badRequest().build(); - } } From 05745046d1dec2043f5b56248530e9d293a1461e Mon Sep 17 00:00:00 2001 From: zillionme Date: Thu, 11 May 2023 17:33:46 +0900 Subject: [PATCH 10/16] =?UTF-8?q?feat:=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../subway/application/NotFoundException.java | 10 +++++++ .../java/subway/dto/ExceptionResponse.java | 16 ++++++++++ .../exception/InvalidSectionException.java | 11 +++++++ .../exception/LineDuplicatedException.java | 11 +++++++ .../subway/ui/GlobalExceptionHandler.java | 29 +++++++++++++++++++ 5 files changed, 77 insertions(+) create mode 100644 src/main/java/subway/application/NotFoundException.java create mode 100644 src/main/java/subway/dto/ExceptionResponse.java create mode 100644 src/main/java/subway/exception/InvalidSectionException.java create mode 100644 src/main/java/subway/exception/LineDuplicatedException.java create mode 100644 src/main/java/subway/ui/GlobalExceptionHandler.java diff --git a/src/main/java/subway/application/NotFoundException.java b/src/main/java/subway/application/NotFoundException.java new file mode 100644 index 000000000..67b0a37a2 --- /dev/null +++ b/src/main/java/subway/application/NotFoundException.java @@ -0,0 +1,10 @@ +package subway.application; + +public class NotFoundException extends RuntimeException { + + public NotFoundException(){}; + + public NotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/subway/dto/ExceptionResponse.java b/src/main/java/subway/dto/ExceptionResponse.java new file mode 100644 index 000000000..f44164103 --- /dev/null +++ b/src/main/java/subway/dto/ExceptionResponse.java @@ -0,0 +1,16 @@ +package subway.dto; + +import java.util.List; + +public class ExceptionResponse { + + private final List message; + + public ExceptionResponse(List message) { + this.message = message; + } + + public List getMessage() { + return message; + } +} diff --git a/src/main/java/subway/exception/InvalidSectionException.java b/src/main/java/subway/exception/InvalidSectionException.java new file mode 100644 index 000000000..b21695872 --- /dev/null +++ b/src/main/java/subway/exception/InvalidSectionException.java @@ -0,0 +1,11 @@ +package subway.exception; + +public class InvalidSectionException extends RuntimeException { + + public InvalidSectionException() { + } + + public InvalidSectionException(String message) { + super(message); + } +} diff --git a/src/main/java/subway/exception/LineDuplicatedException.java b/src/main/java/subway/exception/LineDuplicatedException.java new file mode 100644 index 000000000..d4fd1bf6f --- /dev/null +++ b/src/main/java/subway/exception/LineDuplicatedException.java @@ -0,0 +1,11 @@ +package subway.exception; + +public class LineDuplicatedException extends RuntimeException { + + public LineDuplicatedException() { + } + + public LineDuplicatedException(String message) { + super(message); + } +} diff --git a/src/main/java/subway/ui/GlobalExceptionHandler.java b/src/main/java/subway/ui/GlobalExceptionHandler.java new file mode 100644 index 000000000..26171d66e --- /dev/null +++ b/src/main/java/subway/ui/GlobalExceptionHandler.java @@ -0,0 +1,29 @@ +package subway.ui; + +import java.util.List; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import subway.application.NotFoundException; +import subway.dto.ExceptionResponse; +import subway.exception.InvalidSectionException; +import subway.exception.LineDuplicatedException; + +public class GlobalExceptionHandler { + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgumentsException(final IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ExceptionResponse(List.of(e.getMessage()))); + } + + @ExceptionHandler({NotFoundException.class, LineDuplicatedException.class, InvalidSectionException.class}) + public ResponseEntity handleRuntimeException(final RuntimeException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ExceptionResponse(List.of(e.getMessage()))); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(final Exception e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ExceptionResponse(List.of(e.getMessage()))); + } + +} From 8fc2448359fd35a68fccf9e3f5133af6c1ffae0b Mon Sep 17 00:00:00 2001 From: zillionme Date: Thu, 11 May 2023 17:35:55 +0900 Subject: [PATCH 11/16] =?UTF-8?q?test:=20=ED=86=B5=ED=95=A9=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.properties | 3 + .../java/subway/SubwayApplicationTests.java | 79 ++++++++++++++++++- .../java/subway/ui/LineControllerTest.java | 20 ++++- src/test/resources/initializeTestDb.sql | 58 ++++++++++++++ 4 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 src/main/resources/application.properties create mode 100644 src/test/resources/initializeTestDb.sql diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 000000000..6cbf0d611 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,3 @@ +spring.h2.console.enabled=true +spring.datasource.url=jdbc:h2:mem:product_db;MODE=MySQL +spring.datasource.driver-class-name=org.h2.Driver diff --git a/src/test/java/subway/SubwayApplicationTests.java b/src/test/java/subway/SubwayApplicationTests.java index cdf84476f..28db7af9c 100644 --- a/src/test/java/subway/SubwayApplicationTests.java +++ b/src/test/java/subway/SubwayApplicationTests.java @@ -1,13 +1,88 @@ package subway; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.restassured.RestAssured; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.context.jdbc.Sql; +import subway.dto.SectionRequest; -@SpringBootTest +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@Sql("classpath:initializeTestDb.sql") class SubwayApplicationTests { + @LocalServerPort + private int port; + + @BeforeEach + void setUp() { + RestAssured.port = this.port; + } + + @Autowired + ObjectMapper objectMapper; + + @DisplayName("특정 빈 노선에 역 추가하기") @Test - void contextLoads() { + void createSectionEmptyLine() throws JsonProcessingException { + RestAssured.given() + .body(objectMapper.writeValueAsString(new SectionRequest(7L, 8L, 6))) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when() + .post("/sections/2") + .then() + .statusCode(HttpStatus.CREATED.value()) + .header("Location", "/lines/2"); } + @DisplayName("특정 비지 않은 노선에 역 추가하기") + @Test + void createSectionInLine() throws JsonProcessingException { + RestAssured.given() + .body(objectMapper.writeValueAsString(new SectionRequest(1L, 3L, 5))) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when() + .post("/sections/1") + .then() + .statusCode(HttpStatus.CREATED.value()) + .header("Location", "/lines/1"); + } + + @DisplayName("특정 비지 않은 노선에 역 추가하기 - 거리에 의한 예외 발생") + @ParameterizedTest + @ValueSource(ints = {7, 8}) + void createSectionInLineException(int distance) throws Exception { +// RestAssured.given() +// .body(objectMapper.writeValueAsString(new SectionRequest(1L, 3L, distance))) +// .contentType(MediaType.APPLICATION_JSON_VALUE) +// .when() +// .post("/sections/1") +// .then() +// .statusCode(HttpStatus.BAD_REQUEST.value()); + + } +// +// @DisplayName("특정 노선 조회하기") +// @Test +// void contextLoads() { +// } +// +// @DisplayName("모든 노선 조회하기") +// @Test +// void contextLoads() { +// } +// +// @DisplayName("역 삭제하기") +// @Test +// void contextLoads() { +// } } diff --git a/src/test/java/subway/ui/LineControllerTest.java b/src/test/java/subway/ui/LineControllerTest.java index 5e151c868..cfa252690 100644 --- a/src/test/java/subway/ui/LineControllerTest.java +++ b/src/test/java/subway/ui/LineControllerTest.java @@ -13,7 +13,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; -import java.sql.SQLException; import java.util.Collections; import java.util.List; import org.hamcrest.Matchers; @@ -117,4 +116,23 @@ void deleteLine() throws Exception { .andExpect(status().isNoContent()) .andDo(print()); } + + // 써비스에서 할 것 + // 생성예외 + // 수정, 삭제, 특정 노선 조회 예외 : 존재하지 않는 노선(id)로 노선 조회하려고 하면 예외 발생한다 + // 수정 예외 : 이미 존재하는 이름 혹은 색으로 변경하려고하면 예외가 발생한다 +// @DisplayName("이미 존재하는 이름 혹은 색의 지하철을 생성하려고 하면 예외가 발생한다") +// @Test +// void HandleSQLException() throws Exception { +// given(lineService.saveLine(any())) +//// .willThrow(new SQLException("이미 존재하는 이름입니다.")); +// .willAnswer(invocation -> new SQLException("이미 존재하는 이름입니다.")); +// +// mockMvc.perform(post("/lines") +// .contentType(MediaType.APPLICATION_JSON) +// .content(objectMapper.writeValueAsString(new LineRequest("2호선", "green")))) +// .andExpect(status().isBadRequest()) +// .andExpect(jsonPath("$.message").value("이미 존재하는 이름입니다.")) +// .andDo(print()); +// } } diff --git a/src/test/resources/initializeTestDb.sql b/src/test/resources/initializeTestDb.sql new file mode 100644 index 000000000..53d17a4cf --- /dev/null +++ b/src/test/resources/initializeTestDb.sql @@ -0,0 +1,58 @@ +DROP TABLE section; +DROP TABLE station; +DROP TABLE line; + +create table if not exists STATION +( + id bigint auto_increment not null, + name varchar(255) not null unique, + primary key(id) +); + +create table if not exists LINE +( + id bigint auto_increment not null, + name varchar(255) not null unique, + color varchar(20) not null unique, + primary key(id) +); + +--//todo 물어볼 것 : 기본 키 id를 없애도 되는지? +create table if not exists SECTION +( + line_id bigint not null references line(id) on delete cascade, + up_station_id bigint not null references station(id) on delete cascade, + down_station_id bigint not null references station(id) on delete cascade, + distance int not null, + primary key(up_station_id, down_station_id) +); + + + +INSERT INTO line (name, color) +values ('2호선', '초록색');--1 +INSERT INTO line (name, color) +values ('1호선', '파랑색');--2 + + +INSERT INTO station (name) +values ('서울대입구역');--1 +INSERT INTO station (name) +values ('봉천역');--2 +INSERT INTO station (name) +values ('낙성대역');--3 +INSERT INTO station (name) +values ('사당역');--4 +INSERT INTO station (name) +values ('방배역');--5 +INSERT INTO station (name) +values ('교대역');--6 +INSERT INTO station (name) +values ('인천역');--7 +INSERT INTO station (name) +values ('동인천역');--8 + +INSERT INTO section (line_id, up_station_id, down_station_id, distance) +values (1, 2, 1, 5); +INSERT INTO section (line_id, up_station_id, down_station_id, distance) +values (1, 1, 4, 7); From 4d415dedcd88e7440e3ed5633679e9b21156deca Mon Sep 17 00:00:00 2001 From: zillionme Date: Thu, 11 May 2023 17:36:24 +0900 Subject: [PATCH 12/16] =?UTF-8?q?docs:=20=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EC=84=A4=EA=B3=84=20=EB=AA=A9=EB=A1=9D=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d43260dea..b80bf4626 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,7 @@ ``` GET /lines ``` - - + 4. 테스트 하기 - 노선에 역이 하나도 등록되지 않은 상황에서 최초 등록 시 두 역을 동시에 등록해야 합니다. - 하나의 역은 여러 노선에 등록이 될 수 있습니다. @@ -67,3 +66,32 @@ A-B가 2km, B-C가 3km, C-D가 4km인 경우 C역이 제거되면 B-D 거리가 7km가 되어야 합니다. - 노선에 등록된 역이 2개 인 경우 하나의 역을 제거할 때 두 역이 모두 제거되어야 합니다. A-B 노선에서 B를 제거할 때 거리 정보를 포함할 수 없기 때문에 두 역 모두 제거되어야 합니다. + + +## 도메인 설계하기 +### Line +- 필드 + - Long id + - String name => LineName name (-선으로 끝나야함) endwith + - String color => Color color + - Sections +- 기능 + - 역 목록 조회하기 + +### Sections +- 필드 + - List
+- 기능 + - 역 상행-하행 순으로 정렬하기 + - 역 목록 조회하기 + +### Section +- 필드 + - Station upStation + - Station downStation + - Integer distance => Distance distance + +### Station +- 필드 + - Long id + - String name => StaionName name (-역으로 끝나야함) endwith From e9192d59eac8329a0c75252b397c8e13faeaa2eb Mon Sep 17 00:00:00 2001 From: zillionme Date: Thu, 11 May 2023 17:49:35 +0900 Subject: [PATCH 13/16] =?UTF-8?q?fix:=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20@controllerAdvice=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../subway/application/StationService.java | 8 +++++ src/main/java/subway/dao/StationDao.java | 14 +++++++-- .../subway/ui/GlobalExceptionHandler.java | 2 ++ .../java/subway/SubwayApplicationTests.java | 30 ------------------- 4 files changed, 21 insertions(+), 33 deletions(-) diff --git a/src/main/java/subway/application/StationService.java b/src/main/java/subway/application/StationService.java index cb6f068f1..21aaf7037 100644 --- a/src/main/java/subway/application/StationService.java +++ b/src/main/java/subway/application/StationService.java @@ -10,6 +10,7 @@ @Service public class StationService { + private final StationDao stationDao; public StationService(StationDao stationDao) { @@ -17,6 +18,7 @@ public StationService(StationDao stationDao) { } public StationResponse saveStation(StationRequest stationRequest) { + checkDuplicatedStationName(stationRequest); Station station = stationDao.insert(new Station(stationRequest.getName())); return StationResponse.of(station); } @@ -41,4 +43,10 @@ public void updateStation(Long id, StationRequest stationRequest) { public void deleteStationById(Long id) { stationDao.deleteById(id); } + + private void checkDuplicatedStationName(StationRequest stationRequest) { + if (stationDao.findByName(stationRequest.getName()).isPresent()) { + throw new IllegalArgumentException("이미 존재하는 역 이름 입니다"); + } + } } diff --git a/src/main/java/subway/dao/StationDao.java b/src/main/java/subway/dao/StationDao.java index bc5d96a2b..a5a61dfdf 100644 --- a/src/main/java/subway/dao/StationDao.java +++ b/src/main/java/subway/dao/StationDao.java @@ -1,6 +1,8 @@ package subway.dao; +import java.util.List; import java.util.Optional; +import javax.sql.DataSource; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; @@ -10,9 +12,6 @@ import org.springframework.stereotype.Repository; import subway.domain.Station; -import javax.sql.DataSource; -import java.util.List; - @Repository public class StationDao { private final JdbcTemplate jdbcTemplate; @@ -61,4 +60,13 @@ public void deleteById(Long id) { String sql = "delete from STATION where id = ?"; jdbcTemplate.update(sql, id); } + + public Optional findByName(String name) { + String sql = "select * from STATION where name = ?"; + try { + return Optional.of(jdbcTemplate.queryForObject(sql, rowMapper, name)); + } catch (DataAccessException exception) { + return Optional.empty(); + } + } } diff --git a/src/main/java/subway/ui/GlobalExceptionHandler.java b/src/main/java/subway/ui/GlobalExceptionHandler.java index 26171d66e..b24ef5caf 100644 --- a/src/main/java/subway/ui/GlobalExceptionHandler.java +++ b/src/main/java/subway/ui/GlobalExceptionHandler.java @@ -3,12 +3,14 @@ import java.util.List; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import subway.application.NotFoundException; import subway.dto.ExceptionResponse; import subway.exception.InvalidSectionException; import subway.exception.LineDuplicatedException; +@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(IllegalArgumentException.class) diff --git a/src/test/java/subway/SubwayApplicationTests.java b/src/test/java/subway/SubwayApplicationTests.java index 28db7af9c..f80857477 100644 --- a/src/test/java/subway/SubwayApplicationTests.java +++ b/src/test/java/subway/SubwayApplicationTests.java @@ -6,8 +6,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; @@ -57,32 +55,4 @@ void createSectionInLine() throws JsonProcessingException { .header("Location", "/lines/1"); } - @DisplayName("특정 비지 않은 노선에 역 추가하기 - 거리에 의한 예외 발생") - @ParameterizedTest - @ValueSource(ints = {7, 8}) - void createSectionInLineException(int distance) throws Exception { -// RestAssured.given() -// .body(objectMapper.writeValueAsString(new SectionRequest(1L, 3L, distance))) -// .contentType(MediaType.APPLICATION_JSON_VALUE) -// .when() -// .post("/sections/1") -// .then() -// .statusCode(HttpStatus.BAD_REQUEST.value()); - - } -// -// @DisplayName("특정 노선 조회하기") -// @Test -// void contextLoads() { -// } -// -// @DisplayName("모든 노선 조회하기") -// @Test -// void contextLoads() { -// } -// -// @DisplayName("역 삭제하기") -// @Test -// void contextLoads() { -// } } From 18fc5d767cb9e68a31a7168200865ba3074739b3 Mon Sep 17 00:00:00 2001 From: zillionme Date: Thu, 11 May 2023 17:50:25 +0900 Subject: [PATCH 14/16] =?UTF-8?q?fix:=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20@controllerAdvice=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/subway/integration/LineIntegrationTest.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/test/java/subway/integration/LineIntegrationTest.java b/src/test/java/subway/integration/LineIntegrationTest.java index ad4170205..59dc0bb50 100644 --- a/src/test/java/subway/integration/LineIntegrationTest.java +++ b/src/test/java/subway/integration/LineIntegrationTest.java @@ -1,8 +1,13 @@ package subway.integration; +import static org.assertj.core.api.Assertions.assertThat; + import io.restassured.RestAssured; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -11,12 +16,6 @@ import subway.dto.LineRequest; import subway.dto.LineResponse; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static org.assertj.core.api.Assertions.assertThat; - @DisplayName("지하철 노선 관련 기능") public class LineIntegrationTest extends IntegrationTest { private LineRequest lineRequest1; From 933a0402827d2c4381ee4a87e67766122bdc7700 Mon Sep 17 00:00:00 2001 From: zillionme Date: Thu, 11 May 2023 17:59:08 +0900 Subject: [PATCH 15/16] =?UTF-8?q?fix:=20=EC=BF=BC=EB=A6=AC=20=EC=98=A4?= =?UTF-8?q?=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/subway/dao/SectionDao.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/subway/dao/SectionDao.java b/src/main/java/subway/dao/SectionDao.java index 4a3ada856..622931e08 100644 --- a/src/main/java/subway/dao/SectionDao.java +++ b/src/main/java/subway/dao/SectionDao.java @@ -10,7 +10,6 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.SqlParameterSource; import org.springframework.jdbc.core.simple.SimpleJdbcInsert; -import org.springframework.stereotype.Component; import org.springframework.stereotype.Repository; import subway.domain.Section; import subway.domain.Station; @@ -42,7 +41,7 @@ public SectionDao(NamedParameterJdbcTemplate jdbcTemplate, DataSource dataSource } public List
findSectionsByLineId(Long lineId) { - String sql = "SELECT up.id as up_station_id, up.name as up_station_name, down.id as down_station_id, down.name as down_station_name, s.distance객 " + String sql = "SELECT up.id as up_station_id, up.name as up_station_name, down.id as down_station_id, down.name as down_station_name, s.distance " + "FROM SECTION AS s " + "JOIN STATION AS up ON s.up_station_id = up.id " + "JOIN STATION AS down ON s.down_station_id = down.id " From 9dc8f180c6c5e397877caa225869b6744cad1d62 Mon Sep 17 00:00:00 2001 From: zillionme Date: Thu, 11 May 2023 18:12:05 +0900 Subject: [PATCH 16/16] =?UTF-8?q?fix:=20SectionDao=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/subway/application/SectionService.java | 4 +++- src/main/java/subway/dao/SectionDao.java | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/subway/application/SectionService.java b/src/main/java/subway/application/SectionService.java index 86cdbdc74..b0d825a8f 100644 --- a/src/main/java/subway/application/SectionService.java +++ b/src/main/java/subway/application/SectionService.java @@ -39,7 +39,7 @@ public void saveSection(Long lineId, SectionRequest sectionRequest) { private boolean isEmptyLine(Long lineId) { Optional> byLineId = sectionDao.findByLineId(lineId); - return byLineId.isEmpty(); + return byLineId.get().isEmpty(); } private void saveSectionWhenLineIsNotEmpty(SectionEntity sectionEntity) { @@ -88,6 +88,7 @@ public void addSectionBasedOnUpStation(Long upStationId, SectionEntity sectionTo revisedDistance); sectionDao.updateByDownStationId(revisedSection); sectionDao.insert(sectionToAdd); + return; } if (sectionDao.findByDownStationId(upStationId, lineId).isPresent()) { @@ -99,6 +100,7 @@ public void addSectionBasedOnDownStation(Long downStationId, SectionEntity secti Long lineId = sectionToAdd.getLineId(); if (sectionDao.findByUpStationId(downStationId, lineId).isPresent()) { sectionDao.insert(sectionToAdd); + return; } Optional originalSectionEntity = sectionDao.findByDownStationId(downStationId, lineId); if (originalSectionEntity.isPresent()) { diff --git a/src/main/java/subway/dao/SectionDao.java b/src/main/java/subway/dao/SectionDao.java index 622931e08..5ff059360 100644 --- a/src/main/java/subway/dao/SectionDao.java +++ b/src/main/java/subway/dao/SectionDao.java @@ -95,14 +95,14 @@ public void insert(SectionEntity sectionEntity) { public void updateByUpStationId(SectionEntity sectionEntity) { String sql = "UPDATE SECTION SET down_station_id = :downStationId, distance = :distance " - + "WHERE line_id = :lineId AND up_station_id = :upStationsId"; + + "WHERE line_id = :lineId AND up_station_id = :upStationId"; SqlParameterSource source = new BeanPropertySqlParameterSource(sectionEntity); jdbcTemplate.update(sql, source); } public void updateByDownStationId(SectionEntity sectionEntity) { - String sql = "UPDATE SECTION SET up_station_id = :upStationsId, distance = :distance " + String sql = "UPDATE SECTION SET up_station_id = :upStationId, distance = :distance " + "WHERE line_id = :lineId AND down_station_id = :downStationId"; SqlParameterSource source = new BeanPropertySqlParameterSource(sectionEntity);