(1) Model 객체 이용하기
- requestScope 영역에 데이터를 맵형식(key-value)로 담을 수 있는 객체
- 단 setAttribute가 아닌 addAttribute 메소드를 사용한다.
- 포워딩 처리가 되면 알아서 ~
(2) ModelAndView 객체 이용하기
- Model과 View가 합쳐져 있는 객체.
- Model은 데이터를 담는 객체, View는 응답 뷰에 대한 정보를 담는 객체
- ModelAndView 객체에 데이터와 응답 뷰에 대한 정보를 담고 해당 객체를 반환한다
========================================================================
(1) Model 객체 이용하기
ㅁ main.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="${ contextPath }/resources/js/sample.js"></script>
<script src="${ contextPath }/assets/js/jquery-3.7.1.min.js"></script>
</head>
<body>
<!-- / 또는 /main.do라는 url mapping으로 요청시 해당 /WEB-INF/views/main.jsp가 보여지도록 한다. -->
<h1>메인페이지입니다</h1>
<h3>1. 정적 자원 확인</h3>
<img src="${ contextPath }/resources/images/1.jpeg" width="100" onclick="test();">
<img src="${ contextPath }/assets/images/2.jpg" width="100" id="img">
<script>
$(function(){
$('#img').on("click", () => {
alert("어서오세요");
})
})
</script>
<hr>
<h3>2. 응답페이지 보여지게 하는 연습 (포워딩, redirect)</h3>
<!-- <a href="${contextPath}/list.bk">도서목록페이지로 이동</a> -->
<a href="${contextPath}/book/list.do">도서목록페이지로 이동</a> <br>
<a href="${contextPath}/book/enrollForm.do">도서등록페이지로 이동</a>
<hr>
<h3>3. 요청시 전달되는 파라미터 처리하는 연습 (request의 parameter)</h3>
<!-- <a href="${contextPath}/detail.mem?no=1">회원상세조회</a> -->
<a href="${contextPath}/member/detail.do?no=1">회원상세조회</a> <br><br><br>
<!-- (1) HttpServletRequest 방법 -->
<form action="${ contextPath }/member/enroll1.do" method="post">
이름 : <input type="text" name="name"> <br>
나이 : <input type="text" name="age"> <br>
주소 : <input type="text" name="address"> <br>
<button>등록</button>
</form>
<br><br>
<!-- (2) @RequestParam 방법 -->
<form action="${ contextPath }/member/enroll2.do" method="post">
이름 : <input type="text" name="name"> <br>
나이 : <input type="text" name="age"> <br>
주소 : <input type="text" name="address"> <br>
<button>등록</button>
</form>
<!-- <a href="${contextPath}/detail2.mem?no=1">회원상세조회</a> -->
<a href="${contextPath}/member/detail2.do?no=1">회원상세조회</a> <br><br><br>
<br><br>
<!-- (3) 커맨드 객체 방법 -->
<form action="${ contextPath }/member/enroll3.do" method="post">
이름 : <input type="text" name="name"> <br>
나이 : <input type="text" name="age"> <br>
주소 : <input type="text" name="addr"> <br>
<button>등록</button>
</form>
<br><br>
<h3>4. 응답페이지로 포워딩시 필요한 데이터를 담는 방법</h3>
<a href="${ contextPath }/notice/list.do">공지사항 목록페이지로 이동</a>
</body>
</html>
- "4. 응답페이지로 포워딩시 필요한 데이터를 담는 방법" 부분을 추가했다.
ㅁ com.br.mvc.dto 패키지에 NoticeDto 클래스를 만든다.
package com.br.mvc.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class NoticeDto {
private int no;
private String title;
private String content;
}
- 사실 이제 매개변수 생성자로 생성할 일이 없다. 그래도 혹시 모르니 매개변수 생성자 어노테이션도 적어놓는다.
- lobok 라이브러리를 사용해서 생성자, getter, setter, toString 메소드를 만든다.
ㅁ com.br.mvc.service 패키지에 NoticeService 인터페이스를 만든다.
package com.br.mvc.service;
import java.util.List;
import org.springframework.stereotype.Service;
import com.br.mvc.dto.NoticeDto;
public interface NoticeService {
// 목록조회
List<NoticeDto> selectNoticeList();
// 상세조회
NoticeDto selectNoticeByNo(int noticeNo);
}
- 인터페이스에서 선언하는 모든 메서드는 기본적으로 public과 abstract가 붙은 것으로 간주된다.
ㅁ com.br.mvc.service 패키지에 NoticeServiceImpl 클래스를 만든다.
package com.br.mvc.service;
import java.util.List;
import org.springframework.stereotype.Service;
import com.br.mvc.dao.NoticeDao;
import com.br.mvc.dto.NoticeDto;
import lombok.RequiredArgsConstructor;
@Service // Service 역할의 클래스에 부여하는 Component 어노테이션 (빈 스캐닝에 의해 자동으로 빈 등록됨)
@RequiredArgsConstructor
public class NoticeServiceImpl implements NoticeService {
private final NoticeDao noticeDao;
@Override
public List<NoticeDto> selectNoticeList() {
return noticeDao.selectNoticeList();
}
@Override
public NoticeDto selectNoticeByNo(int noticeNo) {
return noticeDao.selectNoticeByNo(noticeNo);
}
}
- NoticeServiceImpl도 NoticeDao 객체가 필요하다. 직접 new로 생성하진 않는다.
선언만 하고 주입받아서 사용한다.
그러기 위해서는 NoticeDao를 빈으로 등록해야 한다.
- dao 역할을 하는 클래스에 부여하는 component 어노테이션은 @Repository다.
- @Service
- @RequiredArgsConstructor
ㅁ com.br.mvc.dao 패키지에 NoticeDao 클래스를 만든다.
package com.br.mvc.dao;
import java.util.Arrays;
import java.util.List;
import org.springframework.stereotype.Repository;
import com.br.mvc.dto.NoticeDto;
@Repository // dao 역할의 클래스에 부여하는 Componenet 어노테이션(빈 스캐닝에 의해 빈 등록됨)
public class NoticeDao {
// db에서 조회되었다고 가정하고 샘플데이터 만들기
// db에 담겨있는 공지사항 데이터 재사용
// list를 생성하고 add로 담지 않고, 한줄로 끝냄.
private List<NoticeDto> dbList = Arrays.asList(
new NoticeDto(1, "제목1", "내용1")
, new NoticeDto(2, "제목2", "내용2")
, new NoticeDto(3, "제목3", "내용3") );
public List<NoticeDto> selectNoticeList(){
return dbList;
}
public NoticeDto selectNoticeByNo(/*SqlSession sqlSession,*/int noticeNo) {
// return 쿼리결과;
// 그냥 3개의 NoticeDto 객체 중에서 하나를 찝어도 되고,
for(NoticeDto n : dbList) {
if(n.getNo() == noticeNo) {
return n;
}
}
return null; // db 연동해도 조회결과 없으면 null 반환이다. for문이 끝나기 전까지 조회결과를 못찾으면 null 반환.
}
}
- 회사마다 어떤 회사는 service 단계가 없을 수도 있고, dao도 인터페이스를 두는 회사도 있다.
- 일단은 SqlSession 객체는 매개변수에 두지 않았다.
- @Repository 추가.
ㅁ NoticeController
package com.br.mvc.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.br.mvc.service.NoticeService;
import lombok.AllArgsConstructor;
@AllArgsConstructor // lombok
@RequestMapping("/notice")
@Controller
public class NoticeController {
// 직접 생성하면 결합도가 높아지는 문제가 있다. 매번 생성해서 메모리도 많이 사용한다.
// private NoticeService noticeService = new NoticeServiceImpl();
// @Autowired로 스프링에게서 주입받아 사용한다.
// 그러려면 NoticeServiceImpl을 빈으로 등록해야 한다.
// xml 파일에 가서 빈 등록을 해도 되지만 NoticeServiceImpl 클래스 위에 @Service 어노테이션을 작성하면 빈 스캐닝에 의해 빈 등록이 된다.
/*
(1) 필드 주입 방식
@Autowired
private NoticeService noticeService;
*/
/*
(2) 메소드 주입 방식
private NoticeService noticeService;
@Autowired
public void setNoticeService(NoticeService noticeService) { // 자동실행
this.noticeService = noticeService;
}
*/
//(3) 생성자 주입 방식 (생성자는 자동으로 실행되기 때문에 @Autowired 생략 가능)
private int no;
private final NoticeService noticeSerivce;
/*
public NoticeController(NoticeService noticeService) { <- 롬복
this.noticeService = noticeService;
}
*/
// =========== 포워딩할 응답페이지에 필요한 데이터를 담는 방법 ===========
@GetMapping("/list.do")
public String noticeList() {
List<NoticeDto> list = noticeService.selectNoticeList();
}
}
- 서비스 객체의 메소드를 쓰고 싶다면 서비스 객체가 있어야 한다.
- service 객체를 전역 필드로 두고 쓴다.
- @AllArgsConstructor를 줬다.
- (1) 필드 주입 방식은 등록된 빈 중 타입으로 찾는다. 10개의 필드가 있다면 10개의 @Autowired 어노테이션을 작성해야 한다.
- (2) 메소드 주입 방식은 ~ 메소드가 자동으로 실행된다.
※ (3) 생성자 주입 방식을 자주 쓰는 이유.
- @Autowired 어노테이션을 쓰지 않고 생성자만 쓰면 된다.
- 롬복 라이브러리를 사용할 경우 매개변수 생성자를 만들어주는 어노테이션으로 쉽게 대체 가능하다.
왠만한 회사는 스프링을 쓸때 lombok기능을 쓴다.
lombok 기능으로 필드만 작성하면 ~된다.
- @AllArgsConstructor는 모든 필드들에 매개변수 생성자를 만들어 준다. (주입한다)
- @RequiredArgsConstructor는 final 필드만 매개변수 생성자를 만들어 준다. (주입한다)
- 필드만 두면 lombok으로 매개변수 생성자가 만들어져있다.
- 그런데 @AllArgsConstructor 어노테이션을 사용하진 않는다. 일반 필드들이 있을 때는 문제가 된다.
ㅁ NoticeController 수정
package com.br.mvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.br.mvc.service.NoticeService;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor // lombok
//@AllArgsConstructor lombok
@RequestMapping("/notice")
@Controller
public class NoticeController {
// 직접 생성하면 결합도가 높아지는 문제가 있다. 매번 생성해서 메모리도 많이 사용한다.
// private NoticeService noticeService = new NoticeServiceImpl();
// @Autowired로 스프링에게서 주입받아 사용한다.
// 그러려면 NoticeServiceImpl을 빈으로 등록해야 한다.
// xml 파일에 가서 빈 등록을 해도 되지만 NoticeServiceImpl 클래스 위에 @Service 어노테이션을 작성하면 빈 스캐닝에 의해 빈 등록이 된다.
/*
(1) 필드 주입 방식
@Autowired
private NoticeService noticeService;
*/
/*
(2) 메소드 주입 방식
private NoticeService noticeService;
@Autowired
public void setNoticeService(NoticeService noticeService) { // 자동실행
this.noticeService = noticeService;
}
*/
//(3) 생성자 주입 방식 (생성자는 자동으로 실행되기 때문에 @Autowired 생략 가능)
private int no;
private final NoticeService noticeSerivce;
/*
public NoticeController(NoticeService noticeService) { <- 롬복
this.noticeService = noticeService;
}
*/
// =========== 포워딩할 응답페이지에 필요한 데이터를 담는 방법 ===========
@GetMapping("/list.do")
public String noticeList() {
List<NoticeDto> list = noticeService.selectNoticeList();
}
}
- controller 클래스 상단의 @AllArgsConstructor 어노테이션을 주석처리하고 @RequiredArgsConstructor를 사용한다.
- 앞으로 사용할 DI 방식은 (final + @RequiredArgsConstructor)다.
일반 필드들이 추가될 수도 있기 때문에 모든 필드들에 주입이 되어서는 안된다.
ㅁ NoticeController 수정
package com.br.mvc.controller;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.br.mvc.dto.NoticeDto;
import com.br.mvc.service.NoticeService;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor // lombok
//@AllArgsConstructor lombok
@RequestMapping("/notice")
@Controller
public class NoticeController {
// 직접 생성하면 결합도가 높아지는 문제가 있다. 매번 생성해서 메모리도 많이 사용한다.
// private NoticeService noticeService = new NoticeServiceImpl();
// @Autowired로 스프링에게서 주입받아 사용한다.
// 그러려면 NoticeServiceImpl을 빈으로 등록해야 한다.
// xml 파일에 가서 빈 등록을 해도 되지만 NoticeServiceImpl 클래스 위에 @Service 어노테이션을 작성하면 빈 스캐닝에 의해 빈 등록이 된다.
/*
(1) 필드 주입 방식
@Autowired
private NoticeService noticeService;
*/
/*
(2) 메소드 주입 방식
private NoticeService noticeService;
@Autowired
public void setNoticeService(NoticeService noticeService) { // 자동실행
this.noticeService = noticeService;
}
*/
//(3) 생성자 주입 방식 (생성자는 자동으로 실행되기 때문에 @Autowired 생략 가능)
private int no;
private final NoticeService noticeSerivce;
/*
public NoticeController(NoticeService noticeService) { <- 롬복
this.noticeService = noticeService;
}
*/
// =========== 포워딩할 응답페이지에 필요한 데이터를 담는 방법 ===========
@GetMapping("/list.do")
public String noticeList(Model model) {
List<NoticeDto> list = noticeService.selectNoticeList(); // 응답페이지에 필요한 데이터
model.addAttribute("list", list);
// /WEB-INF/views/notice/list.jsp로 포워딩
return "notice/list";
}
}
※ Model 객체 이용하기
- requestScope 영역에 데이터를 맵형식(key-value)로 담을 수 있는 객체
- 단 setAttribute가 아닌 addAttribute 메소드를 사용한다.
- 포워딩 처리가 되면 알아서 ~
ㅁ /WEB-INF/views에 notice 폴더를 만들고 list.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>공지사항 목록 페이지</h3>
<table border="1">
<thead>
<tr>
<th>번호</th>
<th>제목</th>
<th>내용</th>
</tr>
</thead>
<tbody>
<c:forEach var="n" items="${ list }">
<tr>
<td>${ n.no }</td>
<td>${ n.title }</td>
<td>${ n.content }</td>
</tr>
</c:forEach>
</tbody>
</table>
</body>
</html>
ㅁ 서버 실행해서 이용해보기
=============================================================================
(2) ModelAndView 객체 이용하기
- Model과 View가 합쳐져 있는 객체.
- Model은 데이터를 담는 객체, View는 응답 뷰에 대한 정보를 담는 객체
- ModelAndView 객체에 데이터와 응답 뷰에 대한 정보를 담고 해당 객체를 반환한다
ㅁ list.jsp 수정
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>공지사항 목록 페이지</h3>
<table border="1">
<thead>
<tr>
<th>번호</th>
<th>제목</th>
<th>내용</th>
</tr>
</thead>
<tbody>
<c:forEach var="n" items="${ list }">
<tr>
<td>${ n.no }</td>
<td><a href="${contextPath}/notice/detail.do?no=${ n.no }">${ n.title }</a></td>
<td>${ n.content }</td>
</tr>
</c:forEach>
</tbody>
</table>
</body>
</html>
- 공지사항 목록 페이지의 각 제목행에 각 게시글 상세페이지로 이동시키는 링크를 건다.
ㅁ NoticeController 수정
package com.br.mvc.controller;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import com.br.mvc.dto.NoticeDto;
import com.br.mvc.service.NoticeService;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor // lombok
//@AllArgsConstructor lombok
@RequestMapping("/notice")
@Controller
public class NoticeController {
// 직접 생성하면 결합도가 높아지는 문제가 있다. 매번 생성해서 메모리도 많이 사용한다.
// private NoticeService noticeService = new NoticeServiceImpl();
// @Autowired로 스프링에게서 주입받아 사용한다.
// 그러려면 NoticeServiceImpl을 빈으로 등록해야 한다.
// xml 파일에 가서 빈 등록을 해도 되지만 NoticeServiceImpl 클래스 위에 @Service 어노테이션을 작성하면 빈 스캐닝에 의해 빈 등록이 된다.
/*
(1) 필드 주입 방식
@Autowired
private NoticeService noticeService;
*/
/*
(2) 메소드 주입 방식
private NoticeService noticeService;
@Autowired
public void setNoticeService(NoticeService noticeService) { // 자동실행
this.noticeService = noticeService;
}
*/
//(3) 생성자 주입 방식 (생성자는 자동으로 실행되기 때문에 @Autowired 생략 가능)
private int no;
private final NoticeService noticeService;
/*
public NoticeController(NoticeService noticeService) { <- 롬복
this.noticeService = noticeService;
}
*/
// =========== 포워딩할 응답페이지에 필요한 데이터를 담는 방법 ===========
// (1) Model 객체 이용하기
@GetMapping("/list.do")
public String noticeList(Model model) {
List<NoticeDto> list = noticeService.selectNoticeList(); // 응답페이지에 필요한 데이터
model.addAttribute("list", list);
// /WEB-INF/views/notice/list.jsp로 포워딩
return "notice/list";
}
// (2) ModelAndView 객체 이용하기
@GetMapping("/detail.do")
public ModelAndView noticeDetail(int no, ModelAndView mv) { // key값과 매개변수명이 같으면 @RequestParam 생략가능
// NoticeDto n = noticeService.selectNoticeByNo(no); 응답 페이지에 필요한 데이터
// return "notice/detail"; 응답뷰
mv.addObject("notice", noticeService.selectNoticeByNo(no)); // 응답 페이지에 필요한 데이터
mv.setViewName("notice/detail"); // 응답뷰
// mv.addObject("notice", noticeService.selectNoticeByNo(no)).setViewName("notice/detail");
// addObject 메소드가 해당 ModelAndView 객체를 반환하기 때문에 한줄로 메소드 체이닝 가능하다.
return mv;
}
}
- Model 객체, ModelAndView 객체 중 원하는 방식으로 하면 된다.
- 매개변수에 ModelAndView 객체를 추가한다.
메소드의 반환형도 ModelAndView 객체로 작성한다.
- ModelAndView 객체의 addObject 메소드는 ModelAndView 객체를 반환한다.
그래서 메소드 체이닝이 가능하다.
- addObject 메소드를 쓰고 또 addObject 메소드를 쓰고 setViewName 메소드를 쓸 수 있다.
ㅁ notice 폴더에 detail.jsp를 만든다
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>공지사항 상세 페이지</h3>
<c:choose>
<c:when test="${ empty notice }">
조회된 공지사항이 없습니다.
</c:when>
<c:otherwise>
번호 : ${ notice.no } <br>
제목 : ${ notice.title } <br>
내용 : ${ notice.content }
</c:otherwise>
</c:choose>
</body>
</html>