일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- MSI
- 공부
- 설정
- Ajax
- 자바
- spring
- 티스토리 블로그
- 오라클
- 무결성 제약조건
- 깃허브 블로그
- 스프링
- 전화번호부
- ORA-01407
- 별 찍기
- ORA-02292
- 이클립스단축기
- 파워서플라이
- for문
- 오류
- Oracle
- 백준문제
- 환경설정
- jsp
- 오류모음
- 인터페이스
- while
- 이클립스
- 백준
- 백준문제풀이
- 순환문
- Today
- Total
danDevlog
Spring - 16 (첨부파일 - 2) 본문
이제 데이터 베이스에 저장할 수 있도록 구현할 차례이다.
register.jsp의 전송 클릭시 첨부파일 정보를 함께 보낼 수 있게한다. (document.readt 바로 아랫부분에 추가하였다.)
BoardController 에서 첨부파일 정보 전송 확인한다.
@PostMapping("/register")
public String register(BoardVO board, RedirectAttributes rttr) {
// @Controller 어노테이션이 붙고,
// 컴포넌트 스캔에 패키지가 지정되어 있다면,
// 매개변수 인자들은 스프링이 자동으로 생성 할당 함.
log.info("register : " + board);
service.register(board);
// Controller에서는 첨부파일을 처리하지 않고,
// 서비스 계층에서 처리할 예정 이다.
// 그러므로, 첨부파일 정보에 bno가 널인 것은 상관 없음.
if(board.getAttachList() != null) {
// 첨부파일이 있다면,
board.getAttachList().forEach(attach -> log.info(attach));
// 첨부파일의 각 요소를 로그로 출력.
}
rttr.addFlashAttribute("result",board.getBno());
// 리다이렉트 시키면서 1회용 값을 전달.
return "redirect:/board/list";
로그만 확인하고싶다면 service.register 부분을 주석처리하면된다.
BoardServiceImp에 코드 추가
@Log4j // lombok 로그 이용.
@Service // 이 클래스가 서비스 계층을 맡는다고 알림.
@AllArgsConstructor // 모든 매개변수에 대한 생성자 생성.
public class BoardServiceImp implements BoardService {
@Setter(onMethod_ = @Autowired)
private BoardMapper mapper;
@Setter(onMethod_ = @Autowired)
private BoardAttachMapper attachMapper;
@Transactional
@Override
public void register(BoardVO board) {
log.info("register......" + board);
mapper.insertSelectKey(board);
// 게시물은 부모, 첨부파일은 자식의 개념
if(board.getAttachList() == null ||
board.getAttachList().size() <= 0) {
// 첨부파일 객체가 널이거나 첨부파일 객체의 크기가 0 이라하면 메소드 중지.
return;
}
board.getAttachList().forEach(attach->{
attach.setBno(board.getBno()); // 게시물 번호를 할당하고
attachMapper.insert(attach); // 첨부파일 정보 디비에 등록.
});
}
게시물 등록시 첨부파일이 등록되고, tbl_attach, tbl_board 에 등록 내용 확인.
BoardService에서 게시물 읽기시 첨부파일의 정보가 보이도록한다.
public List<BoardAttachVO> getAttachList(Long bno);
// 게시물의 정보를 가지고 오면서, 첨부파일의 정보도 포함.
// 게시물 읽기 시에 첨부파일 목록을 표시하여 다운로드 가능하도록 처리.
BoardServiceImp에 메소드를 구현한다.
@Override
public List<BoardAttachVO> getAttachList(Long bno) {
log.info("get Attach list by bno : " + bno);
return attachMapper.findByBno(bno);
// 게시물 번호를 전달하고,
// 게시물 번호와 일치하는 첨부파일을 모두 리턴.
}
BoardController에 첨부파일 목록을 호출할 메소드를 추가한다.
// 글 내용을 읽으면서 ajax 호출로 정부파일 목록 표서드.
@GetMapping(value = "/getAttachList", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResponseEntity<List<BoardAttachVO>> getAttachList(Long bno) {
log.info("getAttachList: " + bno);
return new ResponseEntity<>(service.getAttachList(bno), HttpStatus.OK);
}
get.jsp에 화면과 스크립트를 구현한다.
<!-- 첨부파일 시작 -->
<br/>
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">첨부파일</div>
<div class="panel-body">
<div class="uploadResult">
<ul></ul>
</div>
</div>
</div>
</div>
</div>
<!-- 첨부파일 끝 -->
// 첨부파일 목록 표시.(익명 즉시 실행 함수)
(function(){
var bno='<c:out value="${board.bno}"/>';
// 화면상에 공유된 bno 값 가져와 사용 준비.
$.getJSON("/board/getAttachList"
,{bno:bno}, function(arr){
console.log(arr);
var str="";
$(arr).each(function(i,attach){
str+="<li data-path='";
str+=attach.uploadPath+"' data-uuid='";
str+=attach.uuid+"' data-filename='";
str+=attach.fileName+"' data-type='";
str+=attach.fileType+"'><div>";
str+="<img src='/resources/img/dan.jpg' width='20' height='20'> ";
str+="<span>"+attach.fileName+"</span><br/> ";
str+="</div></li>";
});
$(".uploadResult ul").html(str);
});
})();// 첨부파일 목록 표시.(익명 즉시 실행 함수) 끝
UploadController에 파일 다운로드 메소드를 구현한다.
import org.springframework.core.io.Resource;
// 파일 다운로드에 대한 메소드 처리 시작.
// 처음에 작성하지만, 차후에는 복붙 / 일종의 상용구 형태.
@GetMapping(value = "/download",
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
@ResponseBody
public ResponseEntity<Resource> downloadFile(
@RequestHeader("User-Agent") String userAgent, String fileName){
// 서버에 접속한 브라우저의 정보는
// 헤더의 User-Agent를 보면 알 수 있음.
Resource resource = new FileSystemResource("c:\\upload\\" + fileName);
// 파일을 리소스(자원: 가공 처리를 위한 중간 단계]로 변경.
log.info("resource: " + resource);
if(resource.exists() == false) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
String resourceName = resource.getFilename();
// 리소스에서 파일명을 찾아서 할당.
String resourceOriginalName
= resourceName.substring(resourceName.indexOf("_") + 1);
// uuid를 제외한 파일명 만들기.
HttpHeaders headers = new HttpHeaders();
// 웹 브라우저별 특성 처리
try {
String downloadName = null;
if (userAgent.contains("Trident")) {
log.info("IE browser");
downloadName = URLEncoder.encode(resourceOriginalName
, "UTF-8").replaceAll("\\", " ");
} else if (userAgent.contains("Edge")) {
log.info("Edge browser");
downloadName = URLEncoder.encode(resourceOriginalName, "UTF-8");
} else {
log.info("chrome browser");
downloadName = new String(resourceOriginalName.getBytes("UTF-8")
, "ISO-8859-1");
}
log.info("downloadName: " + downloadName);
headers.add("Content-disposition", "attachment; filename=" + downloadName);
// 헤더에 파일 다운로드 정보 추가.
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return new ResponseEntity<Resource>(resource, headers, HttpStatus.OK);
}
get.jsp에 다운로드 스크립트를 작성한다.
// 첨부파일 다운로드 이벤트 시작
$(".uploadResult").on("click", "li", function(e){
console.log("download file");
var liObj = $(this);
var path = encodeURIComponent(liObj.data("path")
+"/"+liObj.data("uuid")+"_"
+liObj.data("filename"));
self.location="/download?fileName="+path;
});
// 다운 끝
encodeURIComponent() 함수는 URI의 특정한 문자를 UTF-8로 인코딩해 하나, 둘, 셋, 혹은 네 개의 연속된 이스케이프 문자로 나타낸다. (두 개의 대리 문자로 이루어진 문자만 이스케이프 문자 네 개로 변환된다.)
게시물 삭제시 첨부파일도 함께 삭제 처리하도록 수정
BoardAttachMapper.xml에 deleteAll을 추가한다.
<delete id="deleteAll">
delete tbl_attach where bno=#{bno}
</delete>
BoardServiceImp 수정
@Transactional
@Override
public boolean remove(Long bno) {
log.info("remove......" + bno);
replyMapper.deleteAll(bno);
attachMapper.deleteAll(bno); // 게시물 삭제시 해당 게시물의 첨부파일 모두 삭제.
return (mapper.delete(bno)) ==1 ;
}
만약 deleteAll 메서드가 안만들어져있다면 모두 만들어준다.
ReplyMapper 인터페이스에 deleteAll 메소드 선언부를 추가한다.
public int deleteAll(Long bno);
ReplyMapper.xml 에 덧글 모두 삭제에 대한 쿼리문을 구현한다
<delete id="deleteAll">
delete from tbl_reply where bno=#{bno}
</delete>
BoardServiceImpl에 replyMapper 인스턴스 만들어준다
@Setter(onMethod_ = @Autowired)
private ReplyMapper replyMapper; // 덧글
Criteria에 메소드 추가 (향후 작업을 쉽게 하도록 메소드 변형
import org.springframework.web.util.UriComponentsBuilder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class Criteria {
// Criteria : 페이징 처리 기준을 갖는 밸류 오브젝트
private int pageNum; // 현재 페이지 번호.
private int amount; // 페이지당 게시물수
private String keyword; // 검색 키워드
private String type; // 검색 타입
private String[] typeArr;
public Criteria() {
this(1, 10); // 아래쪽 전달값 2개 생성자 호출.
}
public Criteria(int pageNum, int amount) {
this.pageNum = pageNum;
this.amount = amount;
}
public void setType(String type) {
this.type = type;
this.typeArr = type.split("");
}
public String[] getTypeArr() {
// 검색 타입 배열 가져오기.
return type == null ? new String[] {} : type.split("");
// 검색타입이 널이라면 비여있는 문자열 배열을 만들고,
// 그렇지 않다면, 검색타입을 한글자씩 잘라서 문자열 배열로 만듦.
// t(제목), w(작성자), c(내용)
}
public String getListLink() {
UriComponentsBuilder builder = UriComponentsBuilder.fromPath("").queryParam("pageNum", this.pageNum)
.queryParam("amount", this.getAmount()).queryParam("type", this.getType())
.queryParam("keyword", this.getKeyword());
return builder.toUriString();
// 기존의 get 방식으로 전달하던 page,amount,type,keyword
// 즉, 현재 페이지, 페이지당 게시물수, 검색타입, 검색어
// 를 주소창에 get 방식으로 붙여서 보냈는데,
// 일일이 값을 호출해서 처리하는 것이 아니라,
// getListLink 메소드로 한꺼번에 처리하도록 변경.
}
}
UriComponentsBuilder는 여러 개의 파라미터들을 연결하여 URL 형태로 만들어 주는 기능을 가지고 있다.
즉 Controller 단에서 addAttrivute로 하나 하나 속성을 지정해주지 않아도 이 class를 이용하면 손쉽고 간단하게
파라미터를 전달할 수 있다.
BoardController 수정
@PostMapping("/modify")
public String modify(BoardVO board, RedirectAttributes rttr,
@ModelAttribute("cri") Criteria cri) {
log.info("modify:" + board);
if(service.modify(board)) {
rttr.addFlashAttribute("result", "성공");
}
// rttr.addAttribute("pageNum",cri.getPageNum());
// rttr.addAttribute("amount",cri.getAmount());
// rttr.addAttribute("keyword",cri.getKeyword());
// rttr.addAttribute("type",cri.getType());
return "redirect:/board/list" + cri.getListLink();
}
@PostMapping("/remove")
public String remove(@RequestParam("bno") Long bno, RedirectAttributes rttr,
@ModelAttribute("cri") Criteria cri) {
log.info("삭제" + bno);
if (service.remove(bno)) {
rttr.addFlashAttribute("result", "성공");
}
// rttr.addAttribute("pageNum",cri.getPageNum());
// rttr.addAttribute("amount",cri.getAmount());
// rttr.addAttribute("keyword",cri.getKeyword());
// rttr.addAttribute("type",cri.getType());
return "redirect:/board/list" + cri.getListLink();
}
기존에 addAttribute로 일일이 속성을 지정해줘야했던것을 주석처리하고,
return에 cri.getListLink() 메서드로 한번에 보내줄 수 있다.
또한 @ModelAttrivbute어노테이션을 이용하여 값을 알아서 "바인딩" OR "주입" 시켜준다.
BoardController 에서 게시물당 첨부된 파일을 찾아서 디스크에서 삭제해준다.
// 첨부파일 삭제 처리 메소드.
private void deleteFiles(List<BoardAttachVO> attachList) {
if (attachList == null || attachList.size() == 0) {
return;
}
log.info("delete attach file....");
log.info(attachList);
attachList.forEach(attach -> {
try {
Path file = Paths.get(
"c:\\upload\\"+attach.getUploadPath()+"\\"
+ attach.getUuid() + "_" + attach.getFileName());
Files.deleteIfExists(file);
// 해당 파일이 존재한다면 삭제 처리.
} catch (Exception e) {
e.printStackTrace();
}
});
}
BoardServiceImp 에 삭제 메서드를 추가해준다.
@Transactional
@Override
public boolean remove(Long bno) {
log.info("remove......" + bno);
replyMapper.deleteAll(bno);
attachMapper.deleteAll(bno); // 게시물 삭제시 해당 게시물의 첨부파일 모두 삭제.
return (mapper.delete(bno)) ==1 ;
}
게시믈 삭제시 화면에서 게시물이 사라지고, 첨부파일 저장 공간에 게시물과 관련된 파일이 삭제되고,
tbl_attach, tbl_reply에 방금 삭제한 데이터가 사라진 것을 확인할 수 있다.
'Spring 게시판 만들기' 카테고리의 다른 글
Spring - 18 (스프링 시큐리티) (0) | 2022.04.29 |
---|---|
Spring - 17 (첨부파일-3) (0) | 2022.04.29 |
Spring - 15 (첨부파일) (0) | 2022.04.26 |
Spring - 14 (댓글 페이징) (0) | 2022.04.26 |
Spring - 13 (댓글 기능 - 페이징) (0) | 2022.04.25 |