<!-- 상세 조회 주소에 cp가 포함된 이유 : 상세 -> 목록으로 돌아올 때 사용 -->
<a th:href="@{/board/{boardCode}/{boardNo}
(boardCode=${boardCode}, boardNo=*{boardNo}, cp=${pagination.currentPage})}"
th:text="*{boardTitle}">게시글 제목</a>
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Builder
public class BoardImg {
private int imgNo;
private String imgPath;
private String imgOriginalName;
private String imgRename;
private int imgOrder;
private int boardNo;
// 게시글 이미지 삽입/수정 때 사용
private MultipartFile uploadFile;
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Builder
public class Comment {
private int commentNo;
private String commentContent;
private String commentWriteDate;
private String commentDelFl;
private int boardNo;
private int memberNo;
private int parentCommentNo;
// 댓글 조회 시 회원 프로필, 닉네임
private String memberNickname;
private String profileImg;
}
// 게시판 이미지
@Value("${my.board.resource-handler}")
private String boardResourceHandler; // 요청 주소
@Value("${my.board.resource-location}")
private String boardResourceLocation; // 연결될 서버 폴더 경로
// 요청 주소에 따라 서버 컴퓨터의 어떤 경로에 접근할지 설정
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// ========= 이전 내용 삭제 =============
// ** 게시글 이미지 요청 - 서버 폴더 연결 추가
registry
.addResourceHandler(boardResourceHandler)
.addResourceLocations(boardResourceLocation);
}
// 상세 조회 요청 주소
// board/1/1990?cp=1
// board/2/1990?cp=2
@GetMapping("{boardCode:[0-9]+}/{boardNo:[0-9]+}")
public String boardDetail(
@PathVariable("boardCode") int boardCode,
@PathVariable("boardNo") int boardNo,
Model model,
RedirectAttributes ra) {
// 게시글 상세 조회 서비스 호출
// 1) Map으로 전달할 파라미터 묶기
Map<String, Integer> map = new HashMap<>();
map.put("boardCode", boardCode);
map.put("boardNo", boardNo);
// 2) 서비스 호출
Board board = service.selectOne(map);
String path = null;
// 조회 결과가 없는 경우
if(board == null) {
path = "redirect:/board/" + boardCode; // 목록 재요청
ra.addFlashAttribute("message", "게시글이 존재하지 않습니다.");
// 조회 결과가 있는 경우
} else {
path = "board/boardDetail"; // board/boardDetail.html로 forward
// board - 게시글 일반 내용 + imageList + commentList
model.addAttribute("board", board);
// 조회된 이미지 목록(imageList)가 있을 경우
if( !board.getImageList().isEmpty() ) {
BoardImg thumbnail = null;
// imageList의 0번 인덱스 == 가장 빠른 순서(imgOrder)
// 이미지 목록의 첫번째 행이 순서 0 == 썸네일인 경우
if(board.getImageList().get(0).getImgOrder() == 0) {
thumbnail = board.getImageList().get(0);
}
model.addAttribute("thumbnail", thumbnail);
model.addAttribute("start", thumbnail != null ? 1: 0);
}
}
return path;
}
// * 게시글 상세 조회
@Override
public Board selectOne(Map<String, Integer> map) {
// 여러 SQL을 실행하는 방법
// 1. 하나의 Service 메서드에서 여러 Mapper 메서드를 호출하는 방법
// 2. 수행하려는 SQL이
// 1) 모두 SELECT 이면서
// 2) 먼저 조회된 결과 중 일부를 이용해서
// 나중에 수행되는 SQL의 조건으로 삼을 수 있을 때
// --> MyBatis의 <resultMap>, <collection> 태그를 이용해서
// Mapper 메서드 1회 호출로 여러 SELECT 한 번에 수행 가능
return mapper.selectOne(map);
}
💡 resultMap은 네임스페이스 아래 최상단에 위치 권장
<!--
* resultMap 태그
- 1) 조회된 컬럼명과 DTO의 필드명이 일치하지 않을 때
매핑(연결) 시켜주는 역할
* 이거 사용 *
- 2) <collection> 태그를 추가 작성하여
여러 행 결과가 조회되는 다른 SELECT를 수행한 후
그 결과를 지정된 DTO의 필드에 대입
* type 속성 : 연결할 DTO 경로 또는 별칭
* id 속성 : 해당 태그를 식별할 값(이름 지정)
-->
<resultMap type="Board" id="board_rm">
<!-- id 태그 : PK 역할을 하는 컬럼, 필드를 작성하는 태그 -->
<id property="boardNo" column="BOARD_NO"/>
<!-- * collection 태그
select로 조회된 결과를 컬렉션(List)에 담아
지정된 필드에 세팅
property : List를 담을 DTO의 필드명
select : 실행할 select의 id
column : 조회 결과 중 지정된 컬럼의 값을 파라미터로 전달
javaType : List(컬렉션)의 타입을 지정
ofType : List(컬렉션)의 제네릭(타입 제한) 지정
-->
<!-- * 해당 게시글 이미지 목록 조회 후 필드에 저장 -->
<collection
property="imageList"
select ="selectImageList"
column="BOARD_NO"
javaType="java.util.ArrayList"
ofType="BoardImg"
/>
<!-- * 해당 게시글 댓글 목록 조회 후 필드에 저장 -->
<collection
property="commentList"
select = "selectCommentList"
column="BOARD_NO"
javaType="java.util.ArrayList"
ofType="Comment"
/>
</resultMap>
<!-- 게시글 상세 조회 -->
<select id="selectOne" resultMap="board_rm">
SELECT BOARD_NO, BOARD_TITLE, BOARD_CONTENT, BOARD_CODE, READ_COUNT,
MEMBER_NO, MEMBER_NICKNAME, PROFILE_IMG,
TO_CHAR(BOARD_WRITE_DATE, 'YYYY"년" MM"월" DD"일" HH24:MI:SS') BOARD_WRITE_DATE,
TO_CHAR(BOARD_UPDATE_DATE, 'YYYY"년" MM"월" DD"일" HH24:MI:SS') BOARD_UPDATE_DATE,
(SELECT COUNT(*)
FROM "BOARD_LIKE"
WHERE BOARD_NO = #{boardNo}) LIKE_COUNT,
(SELECT IMG_PATH || IMG_RENAME
FROM "BOARD_IMG"
WHERE BOARD_NO = #{boardNo}
AND IMG_ORDER = 0) THUMBNAIL
FROM "BOARD"
JOIN "MEMBER" USING(MEMBER_NO)
WHERE BOARD_DEL_FL = 'N'
AND BOARD_CODE = #{boardCode}
AND BOARD_NO = #{boardNo}
</select>
<!-- 상세 조회한 게시글의 이미지 목록 조회 -->
<select id="selectImageList" resultType="BoardImg">
SELECT *
FROM "BOARD_IMG"
WHERE BOARD_NO = #{boardNo}
ORDER BY IMG_ORDER
</select>
<!-- 상세 조회한 게시글의 댓글 목록 조회 -->
<select id="selectCommentList" resultType="Comment">
SELECT LEVEL, C.* FROM
(SELECT COMMENT_NO, COMMENT_CONTENT,
TO_CHAR(COMMENT_WRITE_DATE, 'YYYY"년" MM"월" DD"일" HH24"시" MI"분" SS"초"') COMMENT_WRITE_DATE,
BOARD_NO, MEMBER_NO, MEMBER_NICKNAME, PROFILE_IMG, PARENT_COMMENT_NO, COMMENT_DEL_FL
FROM "COMMENT"
JOIN MEMBER USING(MEMBER_NO)
WHERE BOARD_NO = #{boardNo}) C
WHERE COMMENT_DEL_FL = 'N'
OR 0 != (SELECT COUNT(*) FROM "COMMENT" SUB
WHERE SUB.PARENT_COMMENT_NO = C.COMMENT_NO
AND COMMENT_DEL_FL = 'N')
START WITH PARENT_COMMENT_NO IS NULL
CONNECT BY PRIOR COMMENT_NO = PARENT_COMMENT_NO
ORDER SIBLINGS BY COMMENT_NO
</select>
