@ModelAttribute가 하는 역할이 다양한데 
-. Command 객체의 이름을 변경하여 View에 넘기기 (해당 포스트는 여기를 참조)
-. Controller 클래스에 있는 데이터를 View로 넘기기 (해당 포스트는 여기를 참조)
-. @SessionAttribute가 세션에 저장한 데이터를 @ModelAttribute가 가져다 사용하기

이번 글에서는 이들 중에서 세번째에 대해서 다룬다.

@SessionAttribute("myData")의 의미
-. 특정 컨트롤러 클래스에 @SessionAttribute가 선언되어 있으면 
-. 해당 Controller 클래스의 특정 메소드에서 "myData"라는 이름으로 Model 객체에 저장되는 데이터가 있다면 그 데이터를 세션(HttpSession)에도 자동으로 저장하라는 어노테이션이다.
-. ex)

@Controller
@SessionAttributes("myData")
public class BoardController {
	
	@Autowired
	private BoardService boardService;

... 중 략 ...

	//글 상세 조회
	@RequestMapping("/getBoard.do")
	public String getBoard(BoardVO vo, Model model) throws Exception
	{
		System.out.println("GetBoardController 글 상세 조회 처리~");
		
		//아래와 같이 Model 객체에 myData라는 이름으로 저장하면
		//HttpSession에도 boardService.getBoard(vo)의 반환 데이터가
		//자동으로 저장이 된다.
		model.addAttribute("myData", boardService.getBoard(vo)); 
		
		return "getBoard.jsp";
	}

... 후 략 ...

} //class BoardController

위 코드에서와 같이 컨트롤러 클래스인 BoardController 클래스에 @SessionAttributes("myData")어노테이션이 적용되어 있으면 이 컨트롤러 클래스의 어느 메소드에서  Model에 "myData"라는 이름으로 데이터를 저장하면(여기서는 boardService.getBoard(vo)를 저장) 그 데이터를 세션(HttpSession)에도 자동으로 저장하게된다.
그러면 이렇게 세션에 저장된 데이터를 누가 어디서 어떤식으로 꺼내서 사용하는가?
그 역할 하는 것이 다름아닌 @ModelAttribute이다. 아래 코드와 같이

	//글 수정
	@RequestMapping("/updateBoard.do")
	public String updateBoard(@ModelAttribute("myData") BoardVO vo) throws Exception
	{
		System.out.println("UpdateBoardController 글 수정 처리~");
		System.out.println("▶ vo.getSeq(): "+vo.getSeq());
		System.out.println("▶ vo.getTitle(): "+vo.getTitle());
		System.out.println("▶ vo.getWriter(): "+vo.getWriter());
		System.out.println("▶ vo.getContent(): "+vo.getContent());
		System.out.println("▶ vo.getRegDate(): "+vo.getRegDate());
		System.out.println("▶ vo.getCnt(): "+vo.getCnt());
		
		boardService.updateBoard(vo);
		return "getBoardList.do";
	}

위의 updateBoard()메소드에 @ModelAttribute("myData") BoardVO vo가 내부적으로 동작하는 내용은
① 세션에 myData라는 이름으로 저장된 데이터가 있는지 확인하여(세션에는 key-value 형태로 저장된다), 데이터가 있으면 그 데이터를 세션에서 꺼내서 @ModelAttribute가 붙은 매개변수에(여기서는 BoardVO 객체인 vo에) 자동으로 저장한다. 세션에 해당 데이터가 없다면 당연히 이 과정을 일어나지 않을 것이다.

그런데 여기서 위의 updateBoard(@ModelAttribute("myData") BoardVO vo) 메소드는 게시글 수정시 호출되는 메소드이다.
따라서 게시글 수정은 기본적으로 form 형태로 이전 글 내용이 보여지고 여기서 수정하고자 하는 항목을 수정후 해당 form을 commit하는 방식이 될것이다(아래 form).
이러한 경우 Command 객체 개념이 BoardVO vo에 적용이된다. 이때 updateBoard() 메소드에는 세션에 저장된 데이터가 BoardVO vo에 저장이 되면서 동시에 form으로부터 넘어온 데이터가 Command 객체인 BoardVO vo에 또한 저장이된다. 이런 상황에서 세션 데이터와 form에서 넘어온 데이터가 어떤 식으로 Command 객체인 BoardVO vo에 저장이 되는가 하는 것이다.

Command 객체란
Controller 클래스(위에서는 BoardController)의 메소드에 매개변수로 VO 객체가 선언되어 있을때(위에서는 BoardVO vo) 이를 Command 객체라고 한다. 위에서는 updateBoard() 메소드의 BoardVO vo가 Command 객체이다. Spring 컨테이너는 이러한 Command 객체에 대해서는 내부적으로 다음과 같은 특별한 처리를 자동으로 해준다. 
-. Command 객체인 BoardVO vo 객체를 자동으로 생성하고
-. form 태그에서 입력한 값들을 추출하여 BoardVO 객체인 vo에 저장한다. 이때 BoardVO 클래스의 setter 메소드들이 호출된다.
여기서 중요한 것은 form 태그의 name 속성에 지정한 이름과 BoardVO의 멤버 변수 이름이 동일해야 한다. 
아래의 form의 
name="title"
name="content"과 VO의 멤버 변수 이름이 동일해야 한다.

	<form action="updateBoard.do" method="post">
		<table border="1" cellpadding="0" cellspacing="0">
			<tr>
				<td bgcolor="orange" width="70">제목</td>
				<td align="left"><input type="text" name="title" value="${ joe_Board.title}" /></td>
			</tr>
			<tr>
				<td bgcolor="orange">작성자</td>
				<td align="left">${ boardModel.writer}</td>
			</tr>
			<tr>
				<td bgcolor="orange">내용</td>
				<td align="left"><textarea name="content" cols="40" rows="10">${joe_Board.content}</textarea></td>
			</tr>
			... 후 략 ...
		</table>
	</form>

VO의 멤버 변수 이름이 form 태그의 name 속성에서 지정한 이름과 동일해야 한다. 아래와 같이

public class BoardVO {
	private String title;
	private String content;

	public String getTitle() { return title; }
	public void setTitle(String title) { this.title = title; }
	public String getContent() { return content; }
	public void setContent(String content) { this.content = content; }
... 후 략 ...
}

② 따라서 세션에 myData라는 이름으로 저장된 데이터를 BoardVO 객체에 할당 이후에 사용자가 게시글 수정한 정보(form 태그 안에 있는 데이터)가 Command 객체인 BoardVO 객체에 덮어쓰기로 할당이 된다.

이상이 @ModelAttribute가 @SessionAttribute와 연동할때 내부적으로 데이터가 어떻게 처리되는지를 살펴보았다.
스프링은 많은 것을 내부에서 처리하는 자동화 메커니즘을 가지고 있기 때문에 개발자는 이러한 자동화 메커니즘을 잘 숙지하고 익숙해 있어야 한다.

@ModelAttribute가 Controller 메소드의 매개변수로 선언된 Command 객체의 긴 이름을 짦은 이름으로 변경할때도 사용되지만(해당 포스트는 여기를 클릭),
Controller 클래스에 있는 특정 데이터를 View(.jsp 페이지)에서 사용할수 있도록 View로 넘기는 용도로도 사용되는 별스런 역할도 할수 있다.
일단 개념부터 정리를 해 보면...

어떤 컨트롤러 클래스 안에있는 특정 메소드에 @ModelAttribute 어노테이션이 붙어 있으면 해당 컨트롤러 클래스의 모든 @RequestMapping 어노테이션이 붙은 메소드가 호출될 때마다 그 메소드 호출 전에 @ModelAttribute가 붙은 메소드가 일단 먼저 호출되고 그 이후 @RequestMapping이 붙은 메소드가 호출되는데 이때 @ModelAttribute 메소드 실행 결과로 리턴되는 객체(데이터)는 자동으로 @RequestMapping 어노테이션이 붙은 메소드의 Model에 저장이되고 그 이후에 .jsp(View)에서 @ModelAttribute 메소드가 반환한 데이터를 사용할수 있다. 
놀라운 @ModelAttribute의 능력이랄까?

일단 코드에서 확인해 보자. 아래와 같은 컨트롤러 클래스가 있다.

@Controller
public class BoardController {
	... 전 략 ...
	
	//글 수정
	@RequestMapping("/updateBoard.do")
	public String updateBoard(BoardVO vo, BoardDAO bdDao) throws Exception {
		System.out.println("UpdateBoardController 글 수정 처리~");
		
		bdDao.updateBoard(vo);
		return "getBoardList.do";
	}
	
	//글 상세 조회
	@RequestMapping("/getBoard.do")
	public String getBoard(BoardVO vo, BoardDAO bdDao, Model model) throws Exception {
		System.out.println("GetBoardController 글 상세 조회 처리~");
		
		model.addAttribute("boardModel", bdDao.getBoard(vo)); 
		return "getBoard.jsp";
	}
	
	//글 목록 검색
	@RequestMapping("/getBoardList.do")
	public String getBoardList(@RequestParam(value="searchCondition", defaultValue="TITLE", required=true) String condition,
								@RequestParam(value="searchKeyword", defaultValue="", required=false) String keyWord,
								BoardVO vo, 
								BoardDAO bdDao, 
								Model model) throws Exception {
		System.out.println("$$$$$$$ GetBoardListController 글 목록 검색 처리~");
		
		model.addAttribute("boardListModel", bdDao.getBoardList(vo));
		return "getBoardList.jsp";
	}
	
	@ModelAttribute("myModelAttribute")
	public Map<String, String> joe() {
		System.out.println("▶▶▶▶▶▶▶ 여기는 joe()~~~ ▶▶▶▶▶▶▶");
		
		Map<String, String> infoMap = new HashMap<String, String>();
		
		infoMap.put("joe", "Web Developer");
		infoMap.put("kim", "Designer");
		infoMap.put("nana", "CEO of M&P");
		
		return infoMap;
	}
}

위의 BoardController 클래스안에는 @RequestMapping 어노테이션이 붙은 메소드가 3개가 있는데
-. public String updateBoard()
-. public String getBoard()
-. public String getBoardList()

이들 메소드가 호출될때마다 @ModelAttribute("myModelAttribute") 어노테이션이 붙은 아래 메소드가 먼저 호출된다.
public Map<String, String> joe()
그리고 joe() 메소드에서 반환하는 데이터(infoMap)가 클라이언트 요청으로 실행될 @RequestMapping이 붙은 메소드의 Model 객체에 자동으로 저장이 된다. 이렇게 저장된 데이터는 .jsp 페이지에서 사용할수 있게 되는데 이때 @ModelAttribute("myModelAttribute") 안에 지정한 문자열인 myModelAttribute가 객체 이름이 된다. 
역시 .jsp에서 어떻게 데이터에 접근하는지 코드에서 확인해 보자.

예를들어 http://xxx.xxx.xx/getBoard.do로 요청이 들어오면 먼저 
public Map<String, String> joe()가 먼저 호출이 되고 그 이후
public String getBoard(BoardVO vo, BoardDAO bdDao, Model model)가 호출이 되는데 이때 joe() 메소드에서 생성된 infoMap 데이터가 getBoard()의 model 객체에 자동으로 저장이 되고 getBoard.jsp에서는 다음과 같이 데이테어 접근할수 있게 된다.

... 전 략 ...
	<h1>글 상세보기</h1>
	<a href="logout.do">로그아웃</a>
	<hr>
	
	구성원1 : ${ myModelAttribute.joe }<br/>
	구성원2 : ${ myModelAttribute.nana }<br/>
	<hr/>
... 후 략 ...

위 .jsp 페이지의 출력 결과는 다음과 같이 될 것이다.

구성원1 : Web Devloper 
구성원2 : CEO of  M&P 

혹은 다음과 같이도 할수 있다.

	<c:forEach items="${ myModelAttribute }" var="item">
		item.key : ${item.key }<br/>
		item.value : ${item.value }<br/><br/>
	</c:forEach>
	<hr/>


그러면 다음과 같은 결과가 나올 것이다.

item.key : joe
item.value : Web Developer

item.key : nana
item.value : CEO of M&P

item.key : kim
item.value : Designer

@ModelAttribute 어노테이션이 이런 용도로도 사용될수 있다는 점이다. 
그런데 @ModelAttribute는 이것 외에 또 있으니 @SessionAttribute와 연결되면 또 요술을 부린다.

+ Recent posts