Spring

[Spring] NULL 업데이트 이슈 (@SessionAttributes, @ModelAttribute)

웹코린이 2023. 8. 16. 20:41
728x90

오늘은 앞의 이미지 업로드 실습에 이어 글 수정 시 파일을 추가하지 않았을 경우 파일이 디폴트 이미지로 들어가지는 경우에 대해서 포스팅 하려고 한다. 이미지 업로드 실습은 아래의 링크로 이동하면 된다.

 

https://junwons.tistory.com/124

 

[Spring] MultipartFile을 활용한 파일 업로드

오늘은 Spring에서 제공하는 방법을 통해 파일 업로드에 대해 알아보려고 한다. 먼저 파일 업로드를 하기 위해서는 [.jar] 파일이 필요하다. 따라서 pom.xml 파일에 아래의 코드를 추가한다. pom.xml comm

junwons.tistory.com

 

 

NULL 업데이트 이슈란?

NULL 데이터가 업데이트에 들어갔다라는 뜻

 

 

 글 수정 시에 별도의 이미지를 추가하지 않으면, 해당 name 파라미터에 NULL이 들어간다. 그래서 NULL 값이 그대로 Controller --> Service --> DAO --> DB 까지 전달되게 되어, 해당 글을 다시 보려고 하면, 내가 업로드한 파일이 아닌 디폴트 파일로 설정되어 'NULL 업데이트 이슈'가 발생한다. 따라서 NULL 업데이트가 되지 않도록 @(어노테이션) 설정을 해야한다.

 

 이 때의 로직은 이러하다.

 

 

혹시 NULL 이니?

그럼 이전에 불렀던 내용으로 세팅해줄게

 

↓    ↓    ↓

 

라고 설정해주는 @(어노테이션)이 @SessionAttributes 이다. 

 

@SessionAttributes

별도로 설정하지 않아도 브라우저가 종료되기 전까지 모든 데이터를 기억할 수 있는 힘이 있는 어노테이션이다.
이 어노테이션은 Controller에서 설정하는 것 !

 

 

따라서, View에서 이전에 불렀던 내용을 알려줘야하고, 그러기 위해서는 같이 설정해야하는 @(어노테이션)이 존재한다.

그 @(어노테이션)은 @ModelAttribute이다.

 

  • @SessionAttributes : View에서 넘어온 데이터들의 이름을 불러올 수 있는 어노테이션
    • Controller 상단에 위치
  • @ModelAttribute : View에서 넘어온 데이터들의 이름이 '설정한 이름 / 예시: data' 라고 알려주는 어노테이션
    • Command 객체와 같이 위치

 

현재는 글 수정을 기준으로 설명할 예정이며, 코드는 아래와 같다.

 

 

먼저, View에서 기존의 데이터가 넘어오는지를 확인한다.

 

 

board.jsp

<c:if test="${empty data}">
	<h1>해당 게시글은 없거나 이용이 불가능한 게시글입니다!</h1>
</c:if>
<c:if test="${not empty data}">
<form action="updateBoard.do" method="post">
	
	<input type="hidden" name="bid" value="${data.bid}">
	<input type="text" name="writer" value="${data.writer}" disabled> <br>
	<input type="text" name="title" required value="${data.title}"> <br>
	<input type="text" name="content" required value="${data.content}"> <br>
	<c:if test="${ data.fileName eq null }">
		<img alt="" src="./images/paper.png">
	</c:if>
	<c:if test="${ data.fileName ne null }">
		<img alt="" src="./images/${ data.fileName }"> <br>
	</c:if>
	<c:if test="${data.writer eq member}">
		<input type="submit" value="글 변경">&nbsp;&nbsp;&nbsp;<input type="button" onclick="del(${data.bid})" value="글 삭제">
	</c:if>
</form>

<form> 태그를 이용해 값을 넘겨주기 때문에 문제가 없다. 따라서 Controller에서 설정만 해주면 끝이 난다.

 

 

 

BoardController.java

@Controller
@SessionAttributes("data")
public class BoardController {

	...
    
    @RequestMapping(value="/insertBoard.do", method=RequestMethod.POST)
	public String insertBoard(@ModelAttribute("data")BoardVO bVO) throws IllegalStateException, IOException {

		MultipartFile fileUpload = bVO.getFileUpload();

		if(!fileUpload.isEmpty()) { // 파일 업로드 했니 ?
			String fileName = fileUpload.getOriginalFilename();
			System.out.println("파일명: " + fileName);
			// 복사본을 생성해서 전용 폴더에 넣어줘
			fileUpload.transferTo(new File("/Users/lyujun-won/Desktop/Ryu/SpringWorkspace/day69/src/main/webapp/images/" + fileName));// 그럼 복사본 생성해주세요
			bVO.setFileName(fileName);
		}

		if(boardService.insert(bVO)){
			return "redirect:main.do";
		}
		else{
			return "redirect:insertBoard.jsp";
		}
	}
}

현재 필요한 데이터는 BoardVO에 담겨있기 때문에 Command 객체인 BoardVO 앞에 '@ModelAttribute("data")' 를 선언하고 data라는 이름으로 선언하여 클래스 최상단의 '@SessionAttributes' 를 선언할 때 똑같이 ("data") 를 넣어 같은 값임을 알려준다. 이렇게 작성하게 되면 끝이 난다 !

 

 

 

그러면 이렇게 생각할 수도 있다. '그럼 막 갖다 붙혀도 되는 거 아닌가?' 라고 할 수도 있는데 정답은 '그럴 수 없다' 이다.

 

그 이유는

 

 - 등록할 데이터가 많아질수록 요청이 무거워진다.

       --> 따라서, 하나의 클래스에 Select와 Update만 남겨두고 다른 클래스로 옮기는 것이 좋다. (현재의 경우)

       --> 현재의 코드로는 검색어 + main.do / @SessionAttributes + selectOne + update 와 같이 묶는 것이 좋다.

 

 

BoardController.java

@Controller
@SessionAttributes("data")
public class BoardController {

	@Autowired
	private BoardService boardService;

	@ModelAttribute("searchMap")
	public Map<String,String> searchMap(){
		Map<String,String> map=new HashMap<String,String>();
		map.put("제목", "TITLE");
		map.put("작성자", "WRITER");
		return map;
	}

	@RequestMapping(value="/main.do")
	public String main(@ModelAttribute("mem")MemberVO mVO, BoardVO bVO, Model model) {
		System.out.println("searchCondition: "+bVO.getSearchCondition());
		System.out.println("searchContent: "+bVO.getSearchContent());
		if(bVO.getSearchCondition() == null) {
			bVO.setSearchCondition("TITLE");
			bVO.setSearchContent("");
		} // 검색 로직에서 종종 활용되는 기법

		mVO.setMid("test");
		mVO.setMpw("1234");

		// model.addAttribute("mem", mVO);
		model.addAttribute("datas", boardService.selectAll(bVO));
		return "main.jsp";
	}

	@RequestMapping(value="/board.do")
	public String selectBoard(BoardVO bVO, Model model) {
		model.addAttribute("data", boardService.selectOne(bVO));
		boardService.update(bVO);
		return "board.jsp";
	}

	@RequestMapping(value="/updateBoard.do")
	public String updateBoard(BoardVO bVO) {
		boardService.update(bVO);
		return "board.do";
	}

	@RequestMapping(value="/deleteBoard.do")
	public String deleteBoard(BoardVO bVO) {
		if(boardService.delete(bVO)){
			return "redirect:main.do";
		}
		else{
			return "board.do";
		}
	}

	@RequestMapping(value="/insertBoard.do", method=RequestMethod.GET)
	public String insertBoardPage(BoardVO bVO) {
		return "redirect:insertBoard.jsp";
	}

	@RequestMapping(value="/insertBoard.do", method=RequestMethod.POST)
	public String insertBoard(@ModelAttribute("data")BoardVO bVO) throws IllegalStateException, IOException {

		MultipartFile fileUpload = bVO.getFileUpload();

		if(!fileUpload.isEmpty()) { // 파일 업로드 했니 ?
			String fileName = fileUpload.getOriginalFilename();
			System.out.println("파일명: " + fileName);
			// 복사본을 생성해서 전용 폴더에 넣어줘
			fileUpload.transferTo(new File("/Users/lyujun-won/Desktop/Ryu/SpringWorkspace/day69/src/main/webapp/images/" + fileName));// 그럼 복사본 생성해주세요
			bVO.setFileName(fileName);
		}

		if(boardService.insert(bVO)){
			return "redirect:main.do";
		}
		else{
			return "redirect:insertBoard.jsp";
		}
	}

}

 

- 두번째로는 검색 데이터 저장 문제가 발생한다. 

   --> 예를 들어 설명하면 사용자가 검색에 'apple'이라고 검색을 한번 한 후에, 그 다음 번에 검색을 하지 않고 전체 데이터를 보려고 할 때 @SessionAttribute 입장에서는 ' 어? 검색어가 없네? apple로 검색해줄게! ' 라고 인식하기 때문에 NULL을 만들 수가 없다는 단점이 있다.

 

 

728x90