분류 전체보기 1341

Ch03. 요구사항 추가(type, 대출현황) - N+1 문제와 SQL 조인을 통한 문제 해결(fetch join)

N+1 문제 @Transactional(readOnly = true) fun getUserLoanHistories(): List { return userRepository.findAll().map { user -> UserLoanHistoryResponse( name = user.name, books = user.userLoanHistories.map { history -> BookHistoryResponse( name = history.bookName, isReturn = history.status == UserLoanStatus.RETURNED ) } ) } } Service 코드를 보게 되면 왜 N+1 문제가 발생하는지 알 수 있다. 최초 User를 조회하는 findAll에서 1번의 쿼리가 발생하고..

Ch03. 요구사항 추가(type, 대출현황) - 유저 대출 현황 - 테스트 코드 작성

검증 조건 사용자가 지금까지 한 번도 책을 빌리지 않은 경우 APi 응답에 잘 포함되어 있어야 한다. 사용자가 책을 빌리고 아직 반납하지 않은 경우 isReturn 값이 false로 잘 들어 있어야 한다. 사용자가 책을 빌리고 반납한 경우 isReturn 값이 true로 잘 들어 있어야 한다. 사용자가 책 여러 권을 빌렸는데, 반납을 한 책도 있고 하지 않은 책도 있는 경우 중첩된 리스트에 여러 권이 정상적으로 들어가 있어야 한다. 테스트 코드 @Test @DisplayName("대출 기록이 없는 유저도 응답에 포함된다.") fun getUserLoanHistoriesTest1() { //given userRepository.save(User("A", null)) //when val result = us..

Ch03. 요구사항 추가(type, 대출현황) - 도서 대출 현황 요구사항 추가

과거 대출했던 기록, 현재 대출 중인 기록을 보여준다. 아무런 기록이 없는 유저도 화면에 보여야 한다. 위의 요구사항을 추가하기 위해 Dto, Controller,Service 를 만들어 보자 UserLoanHistoryReponse(Dto) data class UserLoanHistoryResponse( val name: String, //유저 이름 val books: List ) data class BookHistoryResponse( val name: String, //책 이름 val isReturn: Boolean, ) 하나의 User에 책이름, 반납여부 List를 가지고 있는 Dtodlek. Controller Controller를 나누는 기준이 여러 가지이다 화면에서 사용되는 API끼리 모아둔..

Ch03. 요구사항 추가(type, 대출현황) - UserLoanHistory의 isReturn을 Enum으로 변경하기

Boolean 필드의 단점 현재 UserLoanHistory 엔티티 같은 경우 isReturn 필드가 Boolean으로 되어있다. -> Enum으로 사용하는 것이 더 좋아 보인다. 만약 Boolean 필드를 사용하는 필드가 많이 발생하게 된다면? -> 2^n 을 경우에 수가 발생하고, 개발자가 고려해야 할 로직이 많아진다. Enum 필드로 관심사가 같은 필드끼리는 묶어서 개발하면, 관리하는 필드가 줄고, 경우의 수도 줄어든다. 또한 Boolean 필드를 많이 가지게 되면, 비즈니스 적으로 불가능한 경우(DB에 존재할 수 없는 조합)가 발생할 수도 있다. EnumType을 사용하면, 필드 1개로 여러 상태를 표현할 수 있기에 코드의 이해가 쉬워지고, 정확하게 유의미한 상태만 나타낼 수 있기 때문에 코드의 ..

Ch03. 요구사항 추가(type, 대출현황) - Enum Class 를 활용해 type 리펙토링 하기

String 클래스 단점 클라이언트에서 들어오는 type 필드에 무엇이 들어올지 모른다(서버에서 검증 로직을 작성해 줄 수 있지만, 코드를 추가 작성해야 하는 부담이 있다) 코드만 보았을 때, Book 테이블의 type 필드에 어떤 값들이 있는지 알 수 없다. type과 관련한 새로운 로직을 작성해야 할 때, 분기 로직이 들어간다. 예를 들어 type에 따라 점수를 주는 로직을 작성해야 한다면 아래와 같은 로직이 들어갈 것이다. fun getEventScore(): Int { return when (type) { "COMPUTER" -> 10 "ECONOMY" -> 8 "SOCIETY", "LANGUAGE", "SCIENCE" -> 5 else -> throw IllegalArgumentException..

Ch03. 요구사항 추가(type, 대출현황) - Java 서버를 Kotlin 서버로 리팩토링하자 - 책의 분야 추가하기

추가적인 요구사항이 생겼다 -> 책을 등록할 때, '분야'를 선택해야 한다(분야에는 5가지 존재한다 - 컴퓨터/ 경제/ 사회/ 언어/ 과학) Book 도메인에 type 필드를 추가 null이 불가능하므로, String 타입으로 선언하자. 한번 분야가 정해진 후, 업데이트 요구사항이 없으므로, 불변(val)으로 선언하자 로직 변경 book @Entity class Book( val name: String, val type: String, //책 타입 추가 @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long? = null, ) { //기본 생성자가 없는 오류가 발생(플러그인을 받으면 오류 해결) init { // 초기화 블록 if (na..

테스트 코드 리펙토링

JavaCOde @ExtendWith(SpringExtension.class) public class TodoServiceTests { @MockBean private TodoRepository repository; private TodoService service; private Todo stub; @BeforeEach public void setUp() { service = new TodoService(repository); stub = todoStub(); } @Test public void 한개의_TODO를_반환해야한다() { // Given given(repository.findById(1L)).willReturn(Optional.of(stub)); // When Todo actual = ser..

도메인(Domain, Repository) 리펙토링

Todo 도메인 JavaCode @NoArgsConstructor @AllArgsConstructor @Getter @Builder @Entity @Table(name = "todos") public class Todo { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "title") private String title; @Lob @Column(name = "description") private String description; @Column(name = "done") private Boolean done; @Column(name = "created_at") private LocalDate..

Service 리펙토링

JavaCode @Service public class TodoService { private final TodoRepository todoRepository; public TodoService(TodoRepository todoRepository) { this.todoRepository = todoRepository; } @Transactional(readOnly = true) public List findAll() { return todoRepository.findAll(Sort.by(Direction.DESC, "id")); } @Transactional(readOnly = true) public Todo findById(Long id) { return todoRepository.findById(i..

Controller, Dto 리펙토링

Controller JAVA 컨트롤러 @RestController @RequestMapping("/api/todos") public class TodoController { private final TodoService todoService; public TodoController(TodoService todoService) { this.todoService = todoService; } @GetMapping public ResponseEntity getAll() { List todos = todoService.findAll(); return ResponseEntity.ok(TodoListResponse.of(todos)); } @GetMapping("/{id}") public ResponseEntity..