[ Spring ] 게시판 글쓰기
💎 Console
★★ 템플릿 리터럴( template literal ) ★★
- js내에 '역따옴표(백틱)' 을 사용하면 ${} 표현식을 쓸수있고 결과는 문자열로 인식한다
장점 : 기존 문자열 내부에 표현식을 추가하려면, 문자열을 따옴표로 분리한후 + 연산자로 하나씩 연결해줘야했지만
템플릿리터럴 즉 백틱을 사용하면 편리하고 가독성이 좋게 작성할수있다.
💎 Jsp
💎 JS
💎 Spring
🔎 BoardController2
// 게시글 작성 화면 전환
@GetMapping("/{boardCode:[0-9]+}/insert")
public String boardInsert(@PathVariable("boardCode") int boardCode) {
// @PathVariable : 주소 값 가져오기 + request scope에 값 올리기
return "board/boardWrite";
}
// 게시글 작성
@PostMapping("/{boardCode:[0-9]+}/insert")
public String boardInsert(
@PathVariable("boardCode") int boardCode,
Board board, // 커맨드 객체(필드에 파라미터 담겨있음!)
@RequestParam(value="images", required=false) List<MultipartFile> images,
@SessionAttribute("loginMember") Member loginMember,
RedirectAttributes ra,
HttpSession session
) throws IllegalStateException, IOException {
// 파라미터 : 제목, 내용, 파일(0~5개)
// 파일 저장 경로 : HttpSession
// 세션 : 로그인한 회원의 번호
// 리다이렉트 시 데이터 전달 : RedirectAttributes
// 작성 성공 시 이동할 게시판 코드 : @PathVariable("boardCode")
/* List<MultipartFile>
* - 업로드된 이미지가 없어도 List에 요소 MultipartFile 객체가 추가됨
*
* - 단, 업로드된 이미지가 없는 MultipartFile 객체는
* 파일크기(size)가 0 또는 파일명(getOriginalFileName())이 ""
*
* */
// 1. 로그인한 회원 번호를 얻어와 board에 세팅
board.setMemberNo( loginMember.getMemberNo() );
// 2. boardCode도 board에 세팅
board.setBoardCode(boardCode);
// 3. 업로드된 이미지 서버에 실제로 저장되는 경로
// + 웹에서 요청 시 이미지를 볼 수 있는 경로(웹 접근경로)
String webPath = "/resources/images/board/";
String filePath = session.getServletContext().getRealPath(webPath);
// 게시글 삽입 서비스 호출 후 삽입된 게시글 번호 반환 받기
int boardNo = service.boardInsert(board, images, webPath, filePath);
// 게시글 삽입 성공 시
// -> 방금 삽입한 게시글의 상세 조회 페이지 리다이렉트
// -> /board/{boardCode}/{boardNo}
String message = null;
String path = "redirect:";
if(boardNo > 0) { // 성공 시
message = "게시글이 등록되었습니다.";
path += "/board/" + boardCode + "/" + boardNo;
} else {
message = "게시글 등록 실패ㅠㅠ.";
path += "insert";
}
ra.addFlashAttribute("message", message);
return path;
}
🔎 BoardService2
🔎 BoardServiceImpl2
// 게시글 삽입
@Transactional(rollbackFor = Exception.class)
@Override
public int boardInsert(Board board, List<MultipartFile> images, String webPath, String filePath) throws IllegalStateException, IOException {
// 0. XSS 방지 처리
board.setBoardTitle( Util.XSSHandling( board.getBoardTitle() ) );
board.setBoardContent( Util.XSSHandling( board.getBoardContent() ) );
// 1. BOARD 테이블 INSERT 하기 (제목, 내용, 작성자, 게시판코드)
// -> boardNo(시퀀스로 생성한 번호) 반환 받기
int boardNo = dao.boardInsert(board);
// 2. 게시글 삽입 성공 시
// 업로드된 이미지가 있다면 BOARD_IMG 테이블에 삽입하는 DAO 호출
if(boardNo > 0) { // 게시글 삽입 성공 시
// List<MultipartFile> images
// -> 업로드된 파일이 담긴 객체 MultipartFile이 5개 존재
// -> 단, 업로드된 파일이 없어도 MultipartFile 객체는 존재
// 실제 업로드된 파일의 정보를 기록할 List
List<BoardImage> uploadList = new ArrayList<>();
// images에 담겨있는 파일 중 실제 업로드된 파일만 분류
for(int i = 0; i < images.size(); i++) {
if(images.get(i).getSize() > 0) {
BoardImage img = new BoardImage();
// img에 파일 정보를 담아서 uploadList에 추가
img.setImagePath(webPath); // 웹 접근 경로
img.setBoardNo(boardNo); // 게시글 번호
img.setImageOrder(i); // 이미지 순서
// 파일 원본명0
String fileName = images.get(i).getOriginalFilename();
img.setImageOriginal(fileName); // 원본명
img.setImageReName(Util.fileRename(fileName)); // 변경명
uploadList.add(img);
}
}// 분류 for문 종료
// 분류 작업 후 uploadList가 비어있지 않은 경우
// == 업로드한 파일이 있다
if(!uploadList.isEmpty()) {
// BOARD_IMG 테이블에 INSERT하는 DAO 호출
int result = dao.insertImageList(uploadList);
// result == 삽입된 행의 개수 == uploadList.size()
// 삽입된 행의 개수와 uploadList의 개수가 같다면
// == 전체 insert 성공
if(result == uploadList.size()) {
// 서버에 파일을 저장(transferTo())
// images : 실제 파일이 담긴 객체 리스트
// (업로드 안된 인덱스 빈칸)
// uploadList : 업로드된 파일의 정보 리스트
// (원본명, 변경명, 순서, 경로, 게시글 번호)
// 순서 == images 업로드된 인덱스
for(int i = 0; i < uploadList.size(); i++) {
int index = uploadList.get(i).getImageOrder();
// 파일로 변환
String rename = uploadList.get(i).getImageReName();
images.get(index).transferTo(new File(filePath + rename));
}
} else { // 일부 또는 전체 insert 실패
// ** 웹 서비스 수행 중 1개라도 실패하면 전체 실패 **
// -> rollback 필요
// @Transactional(rollbackFor = Exception.class)
// -> 예외가 발생해야지만 롤백
// [결론]
// 예외를 강제 발생 시켜서 rollback 해야된다
// -> 사용자 정의 예외 생성
throw new FileUploadException(); // 예외 강제 발생
}
}
}
return boardNo;
}
🔎 BoardDAO2
🔎 board-mapper.xml
💥 useGeneratedKeys 속성 : DB 내부적으로 생성한 키 (시퀀스)를 전달된 파라미터의 필드로 대입 가능 여부
*************** 동적 SQL*************** : 프로그램 수행중 SQL을 변경하는 기능( mybatis의 가장 강력기능!!)
<selectKey> 태그 : INSERT/UPDATE 시 사용할 키(시퀀스)를 조회해서 파라미터의 지정된 필드에 대입
order 속성 : 메인 SQL이 수행되기 전/후에 selectKey 가 수행되도록 지정 전 : BEFORE 후 : AFTER
keyProperty 속성 : selectKey 조회 결과를 저장할 파라미터의 필드
<insert id="boardInsert" parameterType="Board"
useGeneratedKeys="true">
<selectKey order="BEFORE" resultType="_int"
keyProperty="boardNo">
SELECT SEQ_BOARD_NO.NEXTVAL FROM DUAL
</selectKey>
INSERT INTO BOARD
VALUES( #{boardNo},
#{boardTitle},
#{boardContent},
DEFAULT, DEFAULT, DEFAULT, DEFAULT,
#{memberNo},
#{boardCode})
</insert>
<!-- 이미지 리스트(여러 개) 삽입 -->
<insert id="insertImageList" parameterType="list">
INSERT INTO BOARD_IMG
SELECT SEQ_IMG_NO.NEXTVAL, A.*
FROM (
<foreach collection="list" item="img" separator="UNION ALL ">
SELECT #{img.imagePath} IMG_PATH,
#{img.imageReName} IMG_RENAME,
#{img.imageOriginal} IMG_ORIGINAL,
#{img.imageOrder} IMG_ORDER,
#{img.boardNo} BOARD_NO
FROM DUAL
</foreach>
) A
</insert>
💥 동적 SQL 중 <foreach> - 특정 SQL 구문을 반복할 때 사용 - 반복되는 사이에 구분자(separator)를 추가할 수 있음.
collection : 반복할 객체의 타입 작성(list, set, map...)
item : collection에서 순차적으로 꺼낸 하나의 요소를 저장하는 변수
index : 현재 반복 접근중인 인덱스 (0,1,2,3,4 ..)
open : 반복 전에 출력할 sql
close : 반복 종료 후에 출력한 sql
separator : 반복 사이사이 구분자
UNION ALL : 각 쿼리의 모든 결과를 포함한 합집합 (중복제거 안함) 반복할떄마다 맨밑에 붙어줘서
SQL에 INSERT시 반복문 돌았던만큼에 결과값이 한번에 INSERT 된다.