Day 93

ChangWoo·2023년 1월 13일
0

중앙 HTA

목록 보기
37/51

태그 기능

  • PostRegisterForm에 모든 정보가 저장된다.
  • 저장된 것을 필요에 따라 Post / AttachedFile / Tag로 옮긴다.
  • 각 정보가 각각의 테이블에 저장되게 하였다.
  • PostRegisterForm은 요청에 최적화되어 있다.
  • 입력폼의 값이 여러 곳에 있을 때, 한 군데로 정보를 모아서 각각의 테이블로 전달된다.
PostService
// 게시글 등록 서비스
	public void insertPost(String userId, PostRegisterForm form) {
		Post post = new Post();
		post.setUserId(userId);
		BeanUtils.copyProperties(form, post);
		
		postMapper.insertPost(post);
		// SPRING_POST_ATTACHED_FILES 테이블에 게시글 첨부파일 정보 저장
		if(form.getFilename() !=null ) {
			AttachedFile attachedFile = new AttachedFile();
			attachedFile.setPostNo(post.getNo());
			attachedFile.setFilename(form.getFilename());
			
			postMapper.insertAttachedFile(attachedFile);
		}
        }

첨부파일, 태그가 없는 경우

등록폼의 정보 PostRegisterForm [title=첨부파일, 태그가 없는 경우, content=내용입니다., upfile=MultipartFile[field="upfile", filename=, contentType=application/octet-stream, size=0], tags=null, filename=null]
  • 첨부파일이 없어도 multipartfile은 null이 아니다.
  • tag는 없으면 null이다.

첨부파일은 있고 태그는 없는 경우

등록폼의 정보 PostRegisterForm [title=첨부파일은 있고 태그는 없는 경우, content=내용, upfile=MultipartFile[field="upfile", filename=1.hwp, contentType=application/haansofthwp, size=13312], tags=null, filename=null]

첨부파일도 있고 태그도 있는 경우

등록폼의 정보 PostRegisterForm [title=첨부파일도 있고 태그도 있는 경우, content=내용, upfile=MultipartFile[field="upfile", filename=1.hwp, contentType=application/haansofthwp, size=13312], tags=[태그1, 태그2, 태그3], filename=null]
  • title과 content, upfile은 입력을 하지 않아도 hidden필드가 아니므로 null이 아니다.
  • tag는 hidden이고 동적으로 추가되기 때문에 아무것도 없으면 null이 나올 수 있다.

테이블 생성

CREATE TABLE SPRING_POST_TAGS (
    POST_NO NUMBER(5) NOT NULL CONSTRAINT SPRING_TAG_POST_NO_FK REFERENCES SPRING_POSTS (POST_NO),
    TAG_CONTENT VARCHAR2(100) NOT NULL
);
  • 테이블의 생성 순서는 부모 -> 자식 -> 손자 순으로
  • 테이블의 삭제 순서는 손자 -> 자식 -> 부모 순으로 한다.
Tag.java
package com.sample.vo;

import org.apache.ibatis.type.Alias;

@Alias("Tag")
public class Tag {

	private int postNo;
	private String content;
	
	public Tag() {}
	public Tag(int postNo, String content) {
		this.postNo = postNo;
		this.content = content;
	}
	public int getPostNo() {
		return postNo;
	}
	public void setPostNo(int postNo) {
		this.postNo = postNo;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
}
PostMapper.java
void insertAttachedFile(AttachedFile attachedFile);
	// 태그 저장
	void insertTag(Tag tag);
	
	// 첨부파일 가지고 오는 것(우리는 하나이기 때문에 List를 붙이지 않지만, 원래는 첨부파일을 많이 넣기 때문에 List를 붙여야 한다.)
	AttachedFile getAttachedFilesByPostNo(int postNo);
	// 태그 가지고 오는 것
	List<Tag> getTagsByPostNo(int postNo);
posts.xml
<!-- 
		void insertTag(Tag tag);
	 -->
	 <insert id="insertTag" parameterType="Tag">
	 	insert into spring_post_tags
	 	(post_no, tag_content)
	 	values
	 	(#{postNo}, #{content})
	 </insert>
     
 <!-- 
		List<AttachedFile> getAttachedFilesByPostNo(int postNo);
	 -->
	 <select id="getAttachedFilesByPostNo" parameterType="int" resultType="AttachedFile">
	 	select
	 		post_no as postNo,
	 		file_name as filename
	 	from
	 		spring_post_attached_files
	 	where
	 		post_no = #{value}
	 </select>
	 
	 <!-- 
	 	List<Tag> getTagsByPostNo(int postNo)
	  -->
	  <select id="getTagsByPostNo" parameterType="int" resultType="Tag">
	  	select
	  		post_no as postNo,
	  		tag_content as content
	  	from
	  		spring_post_tags
	  	where
	  		post_no = #{value}
	  </select>    
PostService.java
// SPRING_POST_TAGS 테이블에 게시글 태그정보 저장
		if (form.getTags() != null) {
			List<String> tags = form.getTags();
			for (String tagContent : tags) {
				Tag tag = new Tag(post.getNo(), tagContent);
				postMapper.insertTag(tag);
			}
		}

실행결과

상세화면에 태그와 첨부파일 출력시키기

PostService의 두가지 역할

  • 입력과 반대로 각각의 테이블에서 정보를 가지고 와서 Dto에 담는다.
  • Dto는 여러 테이블의 정보를 하나로 담을 수 있게 해준다.
  • 출력작업 : Dto에 정보를 모두 담아서 저장하는 작업을 한다.
  • 입력작업 : Form객체를 정의하고, 각 테이블에 저장할 값을 VO 객체로 복사해서 테이블에 저장시키는 작업을 한다.
PostDetailDto.java
// 게시글 정보
	private int no;
	private String title;
	private String userId;
	private String userName;
	private int readCount;
	private int commentCount;
	private Date createdDate;
	private Date updatedDate;
	private String content;
	// 댓글정보
	private List<PostCommentListDto> comments;
	// 첨부파일 정보
	private List<AttachedFile> attahcedFiles;
	// 태그 정보
	private List<Tag> tags;
PostService.java
// 댓글 정보 조회
		List<PostCommentListDto> postCommentListDtos = postCommentMapper.getPostCommentsByPostNo(postNo);
		postDetailDto.setComments(postCommentListDtos);
		
		
// 첨부파일 정보 조회
		List<AttachedFile> attachedFiles = postMapper.getAttachedFileByPostNo(postNo);
		postDetailDto.setAttachedFiles(attachedFiles);
				
// 태그 정보 조회
		List<Tag> tags = postMapper.getTagsByPostNo(postNo);
		postDetailDto.setTags(tags);
		
		return postDetailDto;

detail.jsp
<tr>
						<th>첨부파일</th>
						<td colspan="3">
							<c:choose>
								<c:when test="${empty post.attachedFiles }">
									등록된 첨부파일이 없습니다.
								</c:when>
								<c:otherwise>
									<!-- list라 반복처리 해야 한다. -->
									<c:forEach var="file" items="${post.attachedFiles }">
										<a href="" class="btn btn-outline-dark btn-sm">${file.filename }<i class="bi bi-download ms-2"></i></a>
									</c:forEach>
								</c:otherwise>
							</c:choose>
						</td>
					</tr>
					<tr>
						<th>태그</th>
						<td colspan="3">
							<c:choose>
								<c:when test="${empty post.tags }">
									등록된 태그가 없습니다.
								</c:when>
								<c:otherwise>
									<c:forEach var="tag" items="${post.tags }">
										<span class="badge text-bg-secondary">#${tag.content }</span>
									</c:forEach>
								</c:otherwise>
							</c:choose>
						</td>
					</tr>

실행결과

view

  • RedirectView와 JstlView는 spring에서 제공해준다.
  • DispatcherServlet은 view의 render만 보여준다.
  • 실질적인 응답을 제공하는 것은 view다.

다운로드

PostController
@GetMapping("/download")
	public ModelAndView fileDownload(@RequestParam("filename") String filename) {
		// 지정된 파일정보를 표현하는 File객체 생성, 파일이 존재하지 않더라도 File객체는 생성할 수 있다.(파일이 존재하지 않더라도 null은 아니다.)
		File file = new File(directory, filename);
		// 파일이 존재하지 않으면 예외를 던진다. 
		if(!file.exists()) {
			throw new ApplicationException("["+filename+"] 파일이 존재하지 않습니다.");
		}
		
		ModelAndView mav = new ModelAndView();
		
		// ModelAndView의 Model에 값 저장
		mav.addObject("file", file);
		
		// ModelAndView의 View에 DownloadView객체 저장
		mav.setView(fileDownloadView);
		
		return mav;
	}
detail.jsp
<tr>
	<th>첨부파일</th>
			<td colspan="3">
					<c:choose>
						<c:when test="${empty post.attachedFiles }">
									등록된 첨부파일이 없습니다.
						</c:when>
						<c:otherwise>
						<!-- list라 반복처리 해야 한다. -->
							<c:forEach var="file" items="${post.attachedFiles }">
						<a href="download?filename=${file.filename }" class="btn btn-outline-dark btn-sm">${file.filename }<i class="bi bi-download ms-2"></i></a>
							</c:forEach>
						</c:otherwise>
						</c:choose>
			  </td>
</tr>
FileDownloadView
package com.sample.web.view;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.AbstractView;

import com.sample.exception.ApplicationException;

@Component
// 보통 view를 직접 구현하지 않고 AbstractView를 통해 구현한다.
public class FileDownloadView extends AbstractView {

	@Override
	protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		
		File file = (File) model.get("file");
		
		// application/octet-stream - 일반적인 바이너리 데이터에 대한 컨텐츠 타입이다.(메모장으로 읽을 수 없는 타입)
		setContentType("application/octet-stream");
		// 응답메세지의 헤더부에 다운로드되는 첨부파일을 이름으로 설정한다.
		// attachment;는 브라우저에서 파일을 열지 않고, 항상 다운로드되게 한다.
		// URLEncoder.encode(text, encoding)은 텍스트를 지정된 인코딩 방식으로 변환시킨다.
		// 텍스트에 한글이 포함되어 있는 경우 utf-8방식으로 인코딩(변환)하지 않으면 한글이 전부 깨진다.
		response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(file.getName(), "utf-8"));
		
		// 파일을 읽어오는 스트림 객체를 생성한다.
		InputStream in = new FileInputStream(file);
		// 브라우저와 연결된 출력스트림을 획득한다.
		OutputStream out = response.getOutputStream();
		
		// 입력스트림으로 읽은 데이터를 출력스트림으로 복사해서 출력시킨다.
		IOUtils.copy(in, out);
	}
}

실행결과

profile
한 걸음 한 걸음 나아가는 개발자

0개의 댓글