728x90
이전 파일 업로드 글과 연관지어 게시글 작성의 구현을 마무리하고 이를 정리하겠습니다.
파일첨부에 대해서는 언급하지 않겠습니다!
이전 글 : https://black-mint.tistory.com/62?category=982467
1. Mysql - 테이블
CREATE TABLE `tb_board` (
`id` int NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`content` text NOT NULL,
`writer_id` int NOT NULL,
`writer` varchar(20) NOT NULL,
`delete_yn` varchar(1) DEFAULT 'N',
`create_date` datetime DEFAULT NULL,
`views` int DEFAULT '0',
`type` varchar(20) DEFAULT 'board',
PRIMARY KEY (`id`),
KEY `writer_id` (`writer_id`),
CONSTRAINT `tb_board_ibfk_1` FOREIGN KEY (`writer_id`) REFERENCES `tb_userinfo` (`id`) ON DELETE CASCADE
)
2. BoardDTO
@Data
public class Board {
private Long id;
private String title;
private String content;
private Long writerId;
private String writer;
private String deleteYN;
private Timestamp createDate;
private Long views;
private String type;
}
3. 게시글 작성 Controller
// 게시글 신규 작성 폼 진입 & 기존 게시글 불러오기
@GetMapping("/form")
public String form(Model model, @RequestParam(required = false) Long boardId) {
if (boardId == null) {
model.addAttribute("board", new Board());
} else {
Board board = boardService.contentLoad(boardId, "board");
model.addAttribute("board", board);
}
return "board/form";
}
- 게시글 작성, 수정 화면으로 이동하는 GetMapping
- 기존 게시글을 수정하는 경우 기존 게시글 내용을 추가해주기 위해 Service를 통해 정보를 얻어와 addAttribute를 진행
// 게시글 작성 & 수정
@PostMapping("/form")
public String boardSubmit(Board board, Principal principal,
@RequestParam(value = "files", required = false) List<MultipartFile> files,
@RequestParam(value = "boardId", required = false) Long boardId) throws IOException, SQLException {
if (board.getTitle().length() < 1 || board.getContent().length() < 1 || (!CollectionUtils.isEmpty(files) && files.size() > 7)) {
// 잘못된 입력값이 들어왔을 때 다시 해당 페이지로 로딩
if(boardId != null) {
return "redirect:/board/form?boardId=" + boardId;
}
return "redirect:/board/form";
}
String loginUsername = principal.getName();
String type = "board";
if (boardId == null) { // 새 글 작성
Long newBoardId = boardService.save(board, loginUsername, type); // Insert
// 첨부파일 있을 때
if (!files.get(0).getOriginalFilename().isEmpty()) {
for (int i = 0; i < files.size(); i++) {
if (files.get(i).getContentType().contains("image/")) {
fileService.saveFile(files.get(i), newBoardId);
} else {
System.out.println("이미지 타입이 아닙니다");
}
}
}
} else { // 기존 글 수정
boardService.update(board, boardId, type); // Update
}
return "redirect:/board/list";
}
- 이 전글에선 @Valid를 사용했지만 이번 글에서는 Javascript를 이용해 View에서 제목과 내용의 유무를 체크할 예정입니다. 물론 첫 줄의 검증 코드로 어느정도 검증을 진행! (Valid를 통해서 값을 검증하니 여러 오류가 발생했어요.. 이에 대한 대체 방안입니다,,,)
- 작성(create), 수정(update)를 하나의 Controller와 View에서 처리하기 때문에 boardId(게시판 id)가 null인 상태와 null이 아닌 상태를 나눠서 처리했습니다.
- type : 향후 다른 종류 게시판을 만들거나 공지사항 추가 등 서비스의 확장을 고려해 추가했습니다.
4. Service
// id를 이용해서 해당 글 수정
public Board contentLoad(Long id, String type) {
Board board = new Board();
board.setId(id);
board.setType(type);
try {
board = boardRepository.findById(board);
} catch (Exception e) {
System.out.println("boardService.contentLoad() .. error : " + e.getMessage());
} finally {
return board;
}
}
- GetMapping에서 사용되는 contentLoad입니다.
// 글 등록
public Long save(Board board, String loginUsername, String type) {
board.setCreateDate(Timestamp.valueOf(LocalDateTime.now()));
try {
Member member = memberService.getMember(loginUsername);
board.setWriterId(member.getId());
board.setWriter(member.getUsername());
board.setType(type);
boardRepository.insertBoard(board);
} catch (Exception e) {
System.out.println("boardService.save() .. error : " + e.getMessage());
}
return board.getId();
}
// 글 수정
public Long update(Board board, Long boardId, String type) {
board.setId(boardId);
board.setType(type);
try {
boardRepository.updateBoard(board);
} catch (Exception e) {
System.out.println("boardService.update() .. error : " + e.getMessage());
}
return board.getId();
}
5. Mapper.xml
<!--특정 아이디 게시글 조회-->
<select id="findById" resultType="Board">
SELECT
<include refid="boardColumns"/>
FROM
tb_board
WHERE
id = #{id} AND type = #{type}
</select>
- type : board, notice 등으로 조회합니다. (해당 포스트 같은 경우 board입니다.)
- 만약 공지사항을 조회한다면 notice가 들어갈 것입니다.
<!--게시글 작성-->
<insert id="insertBoard" parameterType="Board" useGeneratedKeys="true" keyProperty="id">
INSERT INTO
tb_board (title, content, writer_id, writer, create_date, type)
VALUES
(#{title}, #{content}, #{writerId}, #{writer}, #{createDate}, #{type})
</insert>
<!--게시글 업데이트-->
<update id="updateBoard" parameterType="Board" useGeneratedKeys="true" keyProperty="id">
UPDATE
tb_board
SET
title = #{title}
,content = #{content}
WHERE
id = #{id} AND type = #{type}
</update>
- useGenratedKeys, keyProperty : insert와 update 시 결과값으로 완료된 row의 pk(id)를 반환받습니다.
6. View
<form action="#" th:action="@{/board/form}" th:object="${board}" method="post" enctype="multipart/form-data"
onsubmit="return checkForm(this);">
<input type="hidden" name="boardId" th:value="*{id}">
<input type="hidden" th:field="*{writerId}">
<div class="mb-3">
<label for="title" class="form-label">제목</label>
<input type="text" class="form-control" id="title" th:field="*{title}">
</div>
<div class="mb-3">
<label for="content" class="form-label">내용</label>
<textarea class="form-control" id="content" rows="13" th:field="*{content}"></textarea>
</div>
<div id="imageThumbnail">
</div>
<!-- button -->
<th:block th:if="${param.boardId} == null">
<div id="uploadForm">
<div id="uploadElement">
<input id="uploadInput" type="file" class="btn btn-outline-primary" name="files"
accept="image/*" onchange="setThumbnail(event);" multiple /> <span
style="font-size: small;"> * jpeg / png 타입의
이미지는
최대 7개까지
등록해주세요.</span>
</div>
<a id="reset" class="mt-3 btn btn-danger" onclick="resetImg()">초기화</a>
</div>
</th:block>
<div class="nav justify-content-end mb-5">
<button id="submit" type="submit" class="me-2 btn btn-primary">작성</button>
<a type="button" class="btn btn-primary" th:href="@{/board/list}">나가기</a>
</div>
</form>
- th:object - form을 제출할 때 설정된 객체로 리턴됩니다. (이 경우 컨트롤러의 Board)
- th:field - 해당 값(name과 value)을 th:object에서 설정된 객체의 내부 값으로 설정됩니다. (Board.setId, Board.setTitle...등)
- onsubmit : form 태그의 값을 서버로 전송하기 전에 실행하여 유효성을 체크합니다.
7. form검증을 위한 script (파일 처리부분은 이 전글 참고)
// form submit 검증
function checkForm(form) {
if (form.title.value == "") {
alert('제목을 입력하세요.');
form.title.classList.add('is-invalid');
form.title.focus();
return false;
}
if (form.content.value == "") {
alert('내용을 입력하세요.');
form.title.classList.remove('is-invalid');
form.content.classList.add('is-invalid');
form.content.focus();
return false;
}
form.content.classList.remove('is-invalid');
return true;
}
- onsubmit에서 this를 통해 받았기 때문에 form.[name]으로 element를 지정가능!
- 제목이나 내용이 없을 경우 경고 팝업을 띄워주고, 해당 input 또는 textarea의 class에 is-invalid를 추가해줍니다. (bootstrap을 이용해 빨간 input칸으로 변화)
- false를 return 받는 경우는 서버로 form 값이 제출 안됩니다.
8. 시연
'Web > SpringBoot' 카테고리의 다른 글
[Spring Boot] 첨부 이미지 보여주기 (0) | 2022.01.11 |
---|---|
[Spring Boot] 파일 첨부 (게시글 이미지 첨부) (0) | 2022.01.11 |
[Spring Boot] Caused by: java.lang.NumberFormatException: For input string: "컬럼 데이터" (오류 해결) (0) | 2022.01.10 |
[Spring Boot] Mybatis insert, update 시 PK값 얻기 (0) | 2022.01.09 |
[Spring Boot] ajax 리스트 값 서버로 보내기 (휴지통 복원, 삭제) (0) | 2022.01.06 |