3주차 레퍼런스 문서와 멤버분들이 추가 공부한 내용을 스크랩했습니다.
✅ SSR, CSR
이번 챕터의 주제가 화면 구성인 만큼, 템플릿 엔진에 대해서 자세하게 다루었는데요. 우리가 웹 페이지를 위한 서버를 만들고 있기 때문에 템플릿 엔진이 필요합니다. (모바일 앱을 위한 서버라면 HTML을 내려주는 것이 의미가 없으므로 템플릿 엔진이 필요하지 않겠죠?)
서버 템플릿 엔진을 사용하면 SSR(Server Side Rendering) 방식을, 클라이언트 템플릿 엔진을 사용하면 CSR(Client Side Rendering) 방식을 채택하는 것입니다. 추가 실습에서 CSR을 위한 REST 컨트롤러를 만들어봤는데요, 여기서 반환하는 JSON 데이터들을 클라이언트가 받아서 미리 만들어둔 HTML과 조립해 사용자에게 제공합니다.
SSR과 CSR이 무엇이 다른지는 알겠는데, 각각 어떤 장단점이 있을까요? 어떠한 기준으로 HTML 파일을 내려줄 방식을 채택해야할까요? 또 웹 프론트에 관심 있으신 분들은 Next.js를 들어보셨을 것 같은데요, Next.js는 SSR과 CSR의 장점을 취하고 단점을 극복하기 위해 나온 프레임워크라고 합니다. Next.js의 전략이 무엇인지 살펴보시는 것도 재밌을 것 같아요.
SSR(서버사이드 렌더링)과 CSR(클라이언트 사이드 렌더링)
👩💻 SSR vs CSR 비교 설명, Next.js가 탄생하게 된 이유
✅ View Resolver
IndexController의 index() 메소드를 보면 코드는 단순히 문자열을 반환하고 있는데, 지정된 경로에 있는 문자열 이름의 파일을 찾아서 클라이언트에 반환해줍니다. 이 작업은 View Resolver가 해주는데요, 뷰 리졸버는 단순히 경로 찾기만 해주는 것이 아닙니다. 뷰 리졸버는 controller가 반환한 데이터 타입에 따라 결과를 지정해주는 역할을 합니다. 템플릿 엔진을 썼다면 고정된 경로에서 파일을 찾고, RestController에서 객체를 반환했다면 JSON 타입으로 내려줍니다. 스프링 프레임워크의 코드에서 뷰 리졸버는 인터페이스이고, 타입별로 구현된 구현체 클래스가 동적으로 선택되어 실행되겠죠? 무슨 말인지 이해가 가지 않으신다면 인터페이스 다형성에 대해 찾아보시면 좋을 것 같습니다.
[Spring] 뷰 리졸버(View Resolver) 개념 이해하기
뷰가 무엇인지, 뷰에는 어떠한 종류가 있는지, 또 어떠한 종류의 뷰 리졸버의 구현체가 있는지 궁금하신 분들은 아래 링크를 한번 읽어보세요.
✅ PRG 패턴
index.js 파일을 보면 등록 후 인덱스 페이지로 리다이렉트하는 코드를 볼 수 있습니다.
save : function () {
var data = {
title: $('#title').val(),
author: $('#author').val(),
content: $('#content').val()
};
$.ajax({
type: 'POST',
url: '/api/v1/posts',
dataType: 'json',
contentType:'application/json; charset=utf-8',
data: JSON.stringify(data)
}).done(function() {
alert('글이 등록되었습니다.');
**window.location.href = '/';**
}).fail(function (error) {
alert(JSON.stringify(error));
});
}
왜 등록 후에 인덱스 페이지로 바로 리다이렉트하게 만든걸까요? 이는 게시글이 중복 등록되는 것을 방지하기 위함인데요. 네트워크 상황이 나빠서 유저가 게시글 등록 이후 새로고침을 여러번 한다고 생각해봅시다. 내용이 같은 게시글이 여러개 등록되겠죠? 만약 서비스가 게시글 등록이 아니라 결제라면, 여러번 결제가 되는 대참사가 벌어질 것입니다. 이런 상황을 막기 위해 Post 요청이 성공적으로 처리된 이후 바로 Get 요청을 합니다. 이러한 패턴을 PRG 패턴이라고 하는데요, 웹 개발에서 많이 사용되는 패턴이므로 아래 링크를 읽어보면서 어떤 느낌인지 파악해보세요.
✅ JPQL, QueryDSL
글의 전체 목록을 아이디 기준 내림차순으로 정렬하여 가져오는 쿼리를 다음과 같이 PostsRepository에서 정의하여 사용했습니다.
@Query("SELECT p FROM Posts p ORDER BY p.id DESC")
List<Posts> findAllDesc();
어라, 근데 우리가 알고 있는 SQL과 조금 다르지 않나요? 동일한 질의를 h2에 하려면 다음과 같은 쿼리를 실행해야 합니다.
SELECT * FROM POSTS ORDER BY ID DESC;
키워드는 동일한데, 형태가 조금 다릅니다. Posts p
, p.id
와 같은 코드를 보면 마치 객체를 선언하고 참조 연산자로 필드 값을 얻어오는 것 같지 않나요? 눈치채신 분들도 있을 것 같은데요, 쿼리 애노테이션의 값으로 사용한 쿼리문은 JPQL이라는 언어입니다. JPA가 제공하는 쿼리 언어로, 객체 지향 쿼리라고 불려요. Spring Data Jpa가 메소드 이름만으로 제공하는 쿼리에는 한계가 있기 때문에 @Query 애노테이션을 사용해서 JPQL을 실행합니다.
JPQL은 객체를 탐색하므로, 일반 SQL이 테이블을 대상으로 탐색한다는 점과 다릅니다. 또 SQL을 추상화했기 때문에 특정 벤더에 종속적이지 않아요. JPA는 JPQL을 분석하여 SQL을 따로 생성한 후 DB에 쿼리를 날립니다.
[JPA] JPQL(Java Persistence Query Language)이란?
그러나 JPQL을 문자열로 적는 것에는 여전히 한계가 있습니다. 프로젝트의 규모가 커지면, 데이터를 조회할 때 여러 테이블 조인하고, 여러 조건을 붙여 조회를 해야하는데요. 이런 복잡한 쿼리를 문자열로 작성하면 오타 나기도 쉽고 유지 보수가 너무 힘들겠죠. 그래서 조회용 프레임워크로 많이 사용하는 것이 QueryDSL입니다. 메소드를 기반으로 쿼리를 생성해주기 때문에, 오타나 유효하지 않은 컬럼명을 작성하면 IDE에서 검출이 가능합니다. 다음과 같이 사용할 수 있어요.
@Repository
public class AcademyRepositorySupport extends QuerydslRepositorySupport {
private final JPAQueryFactory queryFactory;
public AcademyRepositorySupport(JPAQueryFactory queryFactory) {
super(Academy.class);
this.queryFactory = queryFactory;
}
public List<Academy> findByName(String name) {
return **queryFactory
.selectFrom(academy)
.where(academy.name.eq(name))
.fetch();**
}
}
Spring Boot Data Jpa 프로젝트에 Querydsl 적용하기
✅ stream, lambda
Service의 findAllDesc()은 정렬한 글 목록을 DB에서 조회하여 객체 리스트로 받고, 이를 Dto 리스트로 변환하여 반환하고 있습니다. 아니 근데 이 희안한 문법은 무엇일까요? 그냥 for문을 사용해서 하나씩 객체를 Dto로 바꾸면 안되는 걸까요?
@Transactional(readOnly = true)
public List<PostsListResponseDto> findAllDesc(){
return postsRepository.findAllDesc()**.stream()
.map(PostsListResponseDto::new)
.collect(Collectors.toList());**
}
코드를 보면 stream, map, ::, collect 등의 키워드가 보이는데요. 이들은 모두 자바8에서 새로 등장한 문법입니다. 하나씩 살펴볼게요.
stream
은 데이터 집합을 읽는 객체입니다. 위의 예제에서는 postsRepository.findAllDesc()가 반환한 List 데이터 집합을 읽는데 사용되었습니다. 함께 사용된 map
은 대표적인 stream 연산으로, stream의 요소들을 새로운 타입으로 변환하는데 주로 사용합니다. 예제에서 stream의 각각의 요소는 Posts가 될 것이고, 각 Posts 객체에 대해 map 메소드에 파라미터로 넘긴 작업을 수행할 것입니다. collect
도 map과 마찬가지로 stream 연산입니다. stream 요소들을 List, Set, Map 등 다른 종류의 결과로 수집하고 싶은 경우 사용합니다. 예제에서는 map의 연산 결과로 받은 stream을 List로 반환하였습니다.
map 메소드에 파라미터로 넘긴 작업은 무엇일까요?( PostsListResponseDto::new
) PostsListResponseDto의 생성자를 호출하고 있다는 느낌이 팍팍 드는데요. 이 문법은 자바8에서 도입된 람다식의 축약 표현인 메소드 레퍼런스입니다.
// 1. 람다 표현식
numList.forEach(x -> System.out.println(x));
// 2. 메서드 레퍼런스 (클래스::메소드)
numList.forEach(System.out::println);
메소드를 참조해서 매개변수의 정보 및 리턴 타입을 알아내어, 람다식에 불필요한 매개 변수를 제거하는 것이 목적입니다. 위의 예제에서 람다식은 단순히 x를 System.out.println()로 전달하는 역할만 하기 때문에, 코드가 불필요하게 길어졌습니다. 메서드 레퍼런스를 사용하면 이러한 매개변수 전달 코드를 줄일 수 있습니다.
다시 교재 코드를 보면서 정리해보겠습니다. 이 코드는 JpaRepository로 찾아온 List를 List로 변환하는 코드입니다. for문을 사용하는 대신, 자바8의 Stream API와 람다의 추상 표현을 사용했습니다. stream으로 List를 읽었고, map으로 Posts를 PostsListResponseDto로 변환했고, collect로 map의 연산 결과인 stream을 List로 변환했습니다.
그런데 조금 이상하지 않나요? 왜 for문을 사용하지 않고 stream을 사용했을까요? stream이 무언가 특별한 기능을 제공하는걸까요? 그리고 Posts를 PostsListResponseDto로 변환할 때, 그냥 PostsListResponseDto의 생성자에 바로 Posts를 넣어서 List에 추가하지, 왜 람다식을 사용한걸까요? 람다식은 함수형 프로그래밍 언어에서 등장하는 문법인데, 왜 객체 지향 언어인 자바에서 함수식을 사용하는 걸까요?
Java 8과 함수형 프로그래밍: Lambda, Stream, Functional Interface
Java 8 에서 왜 함수형 프로그래밍이 도입되었을까?
✅ Optional
Spring Data Jpa가 기본으로 제공하는 findById로 가져온 객체를 사용할 때, 다음과 같은 요상한 코드를 사용했습니다.
@Transactional(readOnly = true)
public PostsResponseDto findById(Long id) {
Posts entity = postsRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("해당 사용자가 없습니다. id=" + id));
return new PostsResponseDto(entity);
}
사실 findById()는 Optional<엔티티>라는 객체를 반환합니다. Optional 역시 Java8에 나온 문법인데요, Optional은 어떠한 필요에 의해 나온 것일까요? Optional은 어떻게 사용할까요? Optional은 null을 안전하게 사용하려면 다뤄야할 타입입니다. 다른 사람들의 코드를 보면서 그 역할을 깨우쳐보세요!
[Java] Optional이란? Optional 개념 및 사용법 - (1/2)
[Java] 언제 Optional을 사용해야 하는가? 올바른 Optional 사용법 가이드 - (2/2)
❄️ 멤버들 추가공부 자료
- @22-23_김한비(M) 님(추가 예정) : https://www.notion.so/3-426740d2db8841969cdd6d23a0accae2
- @22-23_류현지(M) 님 : https://defiant-coat-2e2.notion.site/Chap4-c1af28b13dd744b2b52e8b6404ef162b
- @22-23_윤다빈(M) 님 : https://pickle-fireplant-fa1.notion.site/3-133eebe10a094e0d94fa051869060674
- @22-23_한채연(M) 님 : https://natural-need-ea3.notion.site/3-2f946d1ee6384a09b90105856b131af9
바쁘신 시험 기간에도 성실히 스터디에 참여해주신 모든 분들께 감사의 말씀 올립니다 넘넘 자랑스러워요🫶
'Group Study (2022-2023) > Spring 입문' 카테고리의 다른 글
5주차 레퍼런스 (0) | 2023.01.12 |
---|---|
4주차 레퍼런스 (0) | 2023.01.12 |
2주차 레퍼런스 (0) | 2023.01.12 |
1주차 레퍼런스 (0) | 2023.01.12 |
[spring 입문] 토이프로젝트 - TwoYeon (1) | 2022.12.20 |