[Spring Boot] 웹 페이지에서 데이터 조회하기
웹 페이지에서 데이터를 조회할 수 있는 기본적인 로직을 알아보자.
1. 클라이언트가 브라우저를 통해 데이터 요청
2. 요청 url을 Controller가 받음
3. 받아진 url에서 찾고자 하는 정보를 Repository에 전달
4. Repository는 DB에 요청
5. 해당 데이터를 Entity로 반환
6. Entity는 모델을 통해 View 템플릿으로 전달
7. 결과 페이지가 클라이언트에게 보임
실습에서는 articles/new를 통해 데이터를 등록한 데이터를 articles/id url에서 id 별로 데이터를 확인하는 방법이다.
url에서 데이터 찾기
url의 요청을 받기 위해서는 웹 요청(Request)을 구현한 Controller가 필요하다. @GetMapping 어노테이션을 통해 url을 등록하자. id를 중괄호로 감싸면 변수값이 된다.
url 요청을 매개변수로 받아올 때는 @PathVariable을 사용한다.
@PathVariable
@PathVariable은 RESTful 웹 서비스에서 URL 경로의 일부 값을 추출하여 컨트롤러 메서드에서 사용할 수 있도록 한다. 예를 들어 /users/{id}와 같은 URL 경로에서 id 부분을 추출하여 컨트롤러 메서드에서 사용할 수 있다.
Repository에서 데이터 조회하기
Repository에서 id값을 통해 데이터 조회 해보자. (데이터가 없는 경우도 처리해줘야 함)
findById() 메서드는 Optional 타입을 반환하도록 정의되어 있다. Optional 타입은 null을 허용하지 않는다. 따라서 null을 반환하는 위의 코드는 NullPointerException가 발생할 수 있다. 실습에서는 orElse를 사용했지만 더 안전성 있는 코드를 살펴보자.
Optional은 null 값을 처리하는 데 있어 안전성과 가독성을 높일 수 있는 좋은 방법이다. Optional 대신 orElse(null)을 사용하면 null 값이 발생할 가능성이 있는 경우에도 예외 처리를 할 수 없기 때문에 Optional을 사용하자.
view 템플릿
중괄호를 통해 변수명을 지정하고 #과 /을 통해 처음과 끝을 지정해 주자.
앞서 단일 데이터를 조회했다면 이번에는 DB속 여러 데이터를 조회해 보자.
Repository가 반환하는 값이 Entity가 아니라 List <Entity>이다
기본적인 로직은 위의 단일 데이터를 받아올 때와 동일 하지만 여러 데이터를 가져올 때는 Repository가 반환하는 값이 Entity가 아니라 List<Entity> 형태로 만들어주어야 한다. 또 findAll() 메서드를 사용한다.
CrudRepository를 상속받는 articleRepository의 findAll()은 Iterable 형태를 리턴한다. 따라서 List를 구현한 ArrayList로 오버라이딩 해주자.
왜 List가 아니라 ArrayList로 오버라이딩 할까?
CrudRepository에서 findAll() 메서드의 반환 타입은 Iterable이다.
따라서 findAll() 메서드를 오버라이딩하면서, 반환 타입을 List로 지정할 수도 있었다.
하지만 왜 굳이 List가 아니라 ArrayList로 오버라이딩했을까?
이 경우에는 구체적인 구현 클래스를 명시하지 않기 때문에 반환 타입이 List일 때 발생할 수 있는 문제점을 방지하기 위해 ArrayList를 사용했다.
List 인터페이스는 구현체가 여러 가지이며, 이들 구현체들은 서로 다른 특징을 가지고 있다.
예를 들어 LinkedList는 인덱스 접근이 느리지만 삽입과 삭제가 빠르고, ArrayList는 인덱스 접근이 빠르지만 삽입과 삭제가 느리다.
따라서 반환 타입을 List로 지정하는 것은 이들 구현체의 특징을 미리 알지 못할 때 의도하지 않은 성능 저하나 예기치 않은 버그가 발생할 가능성이 있다.
반면에 ArrayList는 List 인터페이스를 구현한 구현체 중에서 가장 기본적이고 일반적인 구현체이기 때문에 Iterable 인터페이스를 구현한 findAll() 메서드에서는 ArrayList를 반환 타입으로 지정하는 것이 안전하다.
오버라이딩은 ArrayList로 하고 구현은 List로 한다고?
일반적으로는 List 인터페이스를 구현한 구현체를 반환하는 메소드를 작성할 때,
메서드 시그니처( List<Article> articleEntityList )에서는 List 인터페이스를 사용하고 실제 구현체는 ArrayList 등으로 오버라이딩하여 구현하는 것이 일반적이다.
따라서 위 코드에서 findAll() 메서드는 Iterable<Article>을 반환하는 CrudRepository 인터페이스의 기본 메서드를 ArrayList<Article>을 반환하도록 오버라이딩한 것이다.
이후 ArrayList를 반환하는 findAll() 메소드를 호출하여 반환되는 결과를 List<Article> 변수에 대입한다.
이렇게 하면 코드의 유연성이 높아지며 나중에 구현체를 변경하더라도 코드 수정이 쉬워진다.