💙 들어가며
list 페이지와 detail 페이지를 구성할 때는
어떤 점을 주의해야 할까?
언제 View가 필요한 지에 대해서 알아보자.
✏️ 학습내용 정리
#언제 View를 별도로 만들까?
💡 목록은 순수하게 entity만 가지고 표현하는 일이 많지 않다.
메인(테이블1)에서 곁다리(테이블2)가 들어가 있는 녀석은
곁다리를 포함해서 만들어야 하고, 이것을 View로 처리한다.
만약 페이지를 출력할 때,
좋아요나 댓글수와 같이
다른 테이블의 값을 집계한 결과가
지속적으로 필요하다면
어떻게 하는 것이 효율적일까?
DB 테이블에 집계된 데이터를
따로 컬럼으로 가지고 있지 않다면,
댓글을 따로 쿼리해서 가져와야 한다.
때문에 지속적으로 출력이 필요한 집계된 값들을
미리 속성으로 가지고 있는 것이 효율적이다.
이때, 테이블을 조인해서 논리적 구조인
View를 만들어서 사용하게 된다.
# list 페이지 vs detail 페이지
th:each로 반복시켜야 할 list와 같은 것들이라면
즉, 반복적으로 요청되는 값이라면
한 번에 속성들을 다 가지고 있어야 하다보니
join한 테이블을 View로 만들어서 유지하는 것이 더 바람직하다.
하지만 detail과 같은 페이지는
페이지 내에서 필요한 부분을
따로따로 출력할 수 있는 페이지이기 때문에
즉, 반복이 없는 값들이기 때문에
join을 해서 복잡도를 높이지 않고
별도로 서비스 함수들을 불러와서
View(HTML)에 출력하는 식으로 처리한다.
💡 목록에 있다면 상세 페이지에도 있어야 한다.
View(HTML)에 출력할 때 list에만 있고 detail에는 없으면 안된다.
1. 리스트는 반복이 필요한 페이지이다.
(반복 목록에 좋아요와 같은 다른 테이블의 값이 필요하다.)
반복에 포함되어야 하는 데이터라면 join이 필요하다.
2. detail은 반복이 필요 없는 페이지이다.
필요한 곳에 서비스 함수를 이용해서 출력하면 된다.
join을 해서 복잡도를 높일 필요가 없다.
#참조관계가 아닌 테이블의 값이 필요한 경우
select문에 서브쿼리로 가지고 온다.
#DAO 재사용 vs Service 재사용
detail 페이지에 추천과 관련된 View가 있다.
Menu에서 처리해야할까? 추천에서 처리해야 할까?
데이터함수(DAO)를 재사용해서
서비스함수를 따로따로 만들어서 가지는 것이 옳다.
주인공(Menu)에 맞게 서비스함수를 따로 가져야 한다.
MenuService에서 MenuRepository, RecommendRepository처럼
여러 Repository를 사용하는 것은 가능하지만,
Service를 재사용할 수는 없다.
(같은 계층끼리는 사용할 수 없다!)
따라서, 페이지를 담당하는 서비스 단에서
각각의 서비스 함수를 새로 만들어주어야 한다.
(중복이라고 생각하면 안됨!!)
이때 서비스 함수의 이름을 구분지어서 만든다.
💡 Menu에서 좋아요 수와 추천목록을 위한 Service는 이름을 구분지어 짓는다.
count (X) → getLikeCountByMenuId (O)
getList (X) → getRecommendList (O)
#Bootstrap (프레임워크)
부트스트랩 프레임워크를 이용해서
미리 만들어진 ui를 사용할 수도 있다.
(관리자페이지는 부트스트랩을 이용해서 만들어도 될듯..?)
사용할 내용을 다운로드 받거나
CDN으로 링크를 걸어줄 수 있다.
단, 링크로 달 경우에는
서비스 종료 후에 큰 사달이 날 수 있으므로..
사용하는 버전에 대한 다운로드를
미리 받아놓는 것이 좋겠다.
admin페이지의 layout부분에 코드로 구현해보자.
💡 admin layout에 BootStrap 이용해보기
<!DOCTYPE html>
<!-- ★★★ 1. 가장 먼저 지시자부터 붙이기 th랑 layout 2개임!! ★★★ -->
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- BootStrap css/js파일 첨부 -->
<!--<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9" crossorigin="anonymous">-->
<!--<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm" crossorigin="anonymous"></script>-->
</head>
<body>
<!-- ★★★ 2. th:replace는 header, aside, footer부분, 완전히 대체되는 것 ★★★ -->
<div th:replace="~{admin/inc/header :: .header}" />
<div th:replace="~{admin/inc/aside :: .aside}" />
<!-- ★★★ 3. layout:fragment는 main부분, 완전히 대체되는 것 / th가 아니다 ★★★ -->
<div layout:fragment="main">
이 내용이 나오면 망한거임... 나오지 마라...
</div>
<!-- ★★★ 2. th:replace는 header, aside, footer부분, 완전히 대체되는 것 ★★★ -->
<div th:replace="~{admin/inc/footer :: .footer}" />
</body>
</html>
💙 마치며
1.
언제 View를 만드는 것이
효율적이고 바람직한지에 대해서
많이 경험해 볼 필요가 있겠다.
2.
전혀 상관없는 테이블의 값이 필요한 경우
select문 안에 scala 서브쿼리로
입력한다고 하셨는데,
아직 감이 안오는 것 같다.
연습해봐야겠다.