@Controller
public class HomeController {
@GetMapping("/")
public String home(Model model) {
List<String> notice = List.of("공지사항 입니다", "공지사항1", "공지사항2");
model.addAttribute("notice", notice);
return "home";
}
}
@Controller
addAttribute(String attributeName, Object attributeValue)
@GetMapping("/")
public String home(Model model) {
}
Model 객체에 담는다Model 객체에 담으면 자동으로 HttpServletRequest 객체에 담긴다List<String> notice = List.of("공지사항 입니다", "공지사항1", "공지사항2");
notice 라는 키값으로 List<String> type 을 담는다model.addAttribute("notice", notice);
여기서 home 이라는 문자열을 리턴하면
접두어 /WEB-INF/views/ 가 붙고 , 접미어 .jsp 가 붙어서
"/WEB-INF/views/home.jsp"` 라는 view page 가 구성이 된다
⭐ 따라서 해당 jsp 페이지로 응답이 위임되어서 응답하게 되는 것이다
"notice" 라는 키값으로 담긴 데이터를 EL 표현식을 이용해서 추출할 수 있다${requestScope.notice} 또는 ${notice}return "home";
<a href="${pageContext.request.contextPath}/member/new-form">회원 추가</a>
newForm 메소드 생성@GetMapping("/member/new-form")
public String newForm() {
// 현재 여기서 수행할 로직은 없고 view page 의 위치만 리턴해준다
return "member/new-form";
}
<div class="container">
<h1>회원 추가 양식</h1>
<form action="${pageContext.request.contextPath}/member/save" method="post">
<div>
<label for="name">이름</label>
<input type="text" name="name" id="name"/>
</div>
<div>
<label for="addr">주소</label>
<input type="text" name="addr" id="addr"/>
</div>
<button type="submit">저장</button>
</form>
</div>
➜ "/member/save" 이 요청을 처리할 controller 메소드를 만들어야 한다
MemberController 에 save 메소드 생성
매개변수에 MemberDto 를 선언하면 폼 전송되는 파라미터가 자동 추출되어서 MemberDto 객체에 담긴 채로 전달된다
@Setter
@Getter
public class MemberDto {
private int num;
private String name;
private String addr;
}
private String name ⟺ <input type="text" name="name">
private String addr ⟺ <input type="text" name"addr">
@PostMapping("/member/save")
public String save(MemberDto dto) {
dao.insert(dto);
// 회원 목록 보기 "/member/list" 요청을 다시 하라는 redirect 응답 하기
return "redirect:/member/list";
}
➜ "redirect:리다이렉트 경로" 처럼 redirect: 으로 시작하는 문자열을 리턴하면 된다
<th>삭제</th>
<td>
<a href="delete?num=${tmp.num}">삭제</a>
</td>
delete 메소드 생성Integer.parseInt() 를 수행해서 실제 int 값으로 바꾼 다음 해당 값을 매개 변수에 전달해 준다@GetMapping("/member/delete")
public String delete(int num)
dao.deleteByNum(num);
return "member/delete";
}
delete.jsp 생성
파라미터로 전달되었던 값을 필요하다면 EL 로 추출해서 응답할 때 활용할 수 있다 ➜ ${param.파라미터명}
alert("${param.num}번의 회원 정보 삭제 완료");
location.href = "${pageContext.request.contextPath}/member/list";

<th>수정</th>
<td>
<a href="edit?num=${tmp.num}">수정</a>
</td>
edit 메소드 생성@GetMapping("/member/edit")
public String edit(int num, Model model) {
// 수정할 회원의 정보를 얻어와서
MemberDto dto = dao.getByNum(num);
// Model 객체에 담고
model.addAttribute("dto", dto);
// view page 로 forward 이동해서 응답
return "member/edit";
}
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<div class="container">
<h1>회원 수정 폼</h1>
<form action="${pageContext.request.contextPath}/member/update" method="post">
<input type="hidden" name="num" value="${dto.num}"/>
<div>
<label for="name">이름</label>
<input type="text" name="name" id="name" value="${dto.name}"/>
</div>
<div>
<label for="addr">주소</label>
<input type="text" name="addr" id="addr" value="${dto.addr}"/>
</div>
<button type="submit">수정 확인</button>
<button type="reset">취소</button>
</form>
</div>
update 메소드 생성@PostMapping("/member/update")
public String update(MemberDto dto) {
// 수정할 회원의 정보가 MemberDto 객체에 담겨서 전달된다
dao.update(dto);
return "member/update";
}
<div class="container">
<p>
<strong>${param.name}</strong>님의 정보 수정 완료
<a href="${pageContext.request.contextPath}/member/list">목록 보기</a>
</p>
</div>

spring04.service 패키지 생성
public interface MemberService {
public List<MemberDto> getAll();
public MemberDto getMember(int num);
public void addMember(MemberDto dto);
public void updateMember(MemberDto dto);
public void deleteMember(int num);
}
// 서비스 클래스에 붙여줄 어노테이션
@Service
public class MemberServiceImpl implements MemberService{
// 의존 객체
@Autowired
private MemberDao dao;
@Override
public List<MemberDto> getAll() {
return dao.selectAll();
}
@Override
public MemberDto getMember(int num) {
MemberDto dto = dao.getByNum(num);
// 만일 select 되는 회원 정보가 없다면?
if(dto == null) {
}
return dto;
}
@Override
public void addMember(MemberDto dto) {
/*
* insert 과정에서 SQLException 이 발생하면 자동으로 DataAccessException 이 발생한다
* dao 에 붙여놓은 @Repository 어노테이션 때문에
* */
dao.insert(dto);
}
@Override
public void updateMember(MemberDto dto) {
int rowCount = dao.update(dto);
// 만일 수정되지 않았다면?
if(rowCount == 0) {
}
}
@Override
public void deleteMember(int num) {
int rowCount = dao.deleteByNum(num);
// 만일 삭제되지 않았다면?
if(rowCount == 0) {
}
}
}
@Controller
public class MemberController {
// 필요한 의존 객체를 주입 받는다
@Autowired private MemberService service;
@PostMapping("/member/update")
public String update(MemberDto dto) {
service.updateMember(dto);
return "member/update";
}
@GetMapping("/member/edit")
public String edit(int num, Model model) {
MemberDto dto = service.getMember(num);
model.addAttribute("dto", dto);
return "member/edit";
}
@GetMapping("/member/delete")
public String delete(int num) {
service.deleteMember(num);
return "member/delete";
}
@PostMapping("/member/save")
public String save(MemberDto dto) {
service.addMember(dto);
return "redirect:/member/list";
}
@GetMapping("/member/new-form")
public String newForm() {
return "member/new-form";
}
@GetMapping("/member/list")
public String list(Model model) {
List<MemberDto> list = service.getAll();
model.addAttribute("list", list);
return "member/list";
}
}
spring04.exception 패키지 생성
@ControllerAdvice : 예외 처리를 할 컨트롤러에 해당 어노테이션을 붙여준다DataAccessExceptio type 의 예외가 발생하면 @ExceptionHandler 메소드는 자동으로 호출된다@ControllerAdvice
public class ExceptionController {
@ExceptionHandler(DataAccessException.class)
public String dataAccess(DataAccessException dae, Model model) {
model.addAttribute("title", "DB 관련 작업 중에 예외가 발생");
model.addAttribute("message", dae.getMessage());
model.addAttribute("status", 500);
// /WEB-INF/views/error/data-access.jsp 페이지에서 에러 정보를 응답한다
return "error/data-access";
}
}
RuntimeException 클래스를 상속받은 나만의 예외 클래스를 정의
Member 카테고리 관련 작업을 하다가 발생하는 에러를 처리할 예정
public class MemberException extends RuntimeException{
// 열거형 클래스
public enum Reason{
NOT_FOUND, UPDATE_FAILED, DELETE_FAILED
}
// 필드
public Reason reason;
// 생성자
public MemberException(Reason reason, String message) {
// 부모 생성자에 예외 메세지를 전달한다
super(message);
// 필드에 예외의 원인을 저장한다
this.reason = reason;
}
// 예외 객체를 편리하게 만들어서 리턴해주는 편의 메소드
public static MemberException notFound(int num) {
return new MemberException(Reason.NOT_FOUND, num+"번 회원이 없습니다");
}
public static MemberException updateFailed(int num) {
return new MemberException(Reason.UPDATE_FAILED, num+"번 회원 수정 실패");
}
public static MemberException deleteFailed(int num) {
return new MemberException(Reason.DELETE_FAILED, num+"번 회원이 없습니다");
}
}
@Override
public MemberDto getMember(int num) {
MemberDto dto=dao.getByNum(num);
//만일 select 되는 회원 정보가 없다면?
if(dto==null) {
// 예외 발생시키기
throw MemberException.notFound(num);
}
return dto;
}
@Override
public void updateMember(MemberDto dto) {
int rowCount = dao.update(dto);
//만일 수정되지 않았다면?
if(rowCount == 0) {
throw MemberException.updateFailed(dto.getNum());
}
}
@Override
public void deleteMember(int num) {
int rowCount = dao.deleteByNum(num);
//만일 삭제되지 않았다면?
if(rowCount == 0) {
throw MemberException.deleteFailed(num);
}
}
memberException 메소드 생성@ExceptionHandler(MemberException.class)
public String memberException(MemberException me, Model model) {
model.addAttribute("title", "Member 관련 작업 중에 예외가 발생");
model.addAttribute("message", me.getMessage());
model.addAttribute("reason", me.reason.name());
return "error/member-exception";
}
<div class="container">
<h1>${title}</h1>
<p>${message}</p>
<p>원인 : <strong>${reason}</strong></p>
</div>
// MyBatis 를 사용할 때 필요한 핵심 객체
private final SqlSession session;
// 생성자를 이용해서 의존 객체를 주입받는 것이 더 일반적 (lombock 의 기능을 이용하면 생략 가능)
@Autowired // 생성자가 오직 1개인 경우에는 생략 가능
public MemberDaoImpl(SqlSession session) {
this.session = session;
}
@RequiredArgsConstructor 를 붙이면 의존 객체를 전달받는 생성자가 자동 생성@RequiredArgsConstructor
public class MemberServiceImpl implements MemberService{
private final MemberDao dao;
}
@RequiredArgsConstructor
public class MemberController {
// 필요한 의존 객체를 주입 받는다
private final MemberService service;
}
mybatis.type-aliases-package=com.example.spring04.**
@Alias 어노테이션 추가@Alias 어노테이션을 이용해서 MemberDto type 의 별칭을 부여할 수 있다Mapper xml 문서에서 parameterType or resultType 으로 활용할 수 있다@Alias("memberDto")
<!-- @ALias("memberDto") 로 설정된 type 의 별칭을 사용할 수 있다 -->
<select id="selectAll" resultType="memberDto">
SELECT num, name, addr
FROM member
ORDER BY num ASC
</select>
<insert id="insert" parameterType="memberDto">
<selectKey keyProperty="num" resultType="int" order="BEFORE">
SELECT member_seq.NEXTVAL FROM DUAL
</selectKey>
INSERT INTO member
(num, name, addr)
VALUES(#{num}, #{name}, #{addr})
</insert>
<update id="update" parameterType="memberDto">
UPDATE member
SET name=#{name}, addr=#{addr}
WHERE num=#{num}
</update>
<li><a href="${pageContext.request.contextPath}/book/list">책 목록보기</a></li>
실습 순서
@Alias("bookDto")
@Setter
@Getter
public class BookDto {
private int num;
private String title;
private String author;
private String publisher;
}
<mapper namespace="book">
<select id="selectAll" resultType="bookDto">
SELECT num, title, author, publisher
FROM book
ORDER BY num ASC
</select>
<select id="select" parameterType="int" resultType="bookDto">
SELECT num, title, author, publisher
FROM book
WHERE num=#{num}
</select>
<insert id="insert" parameterType="bookDto">
<selectKey keyProperty="num" resultType="int" order="BEFORE">
SELECT book_seq.NEXTVAL FROM DUAL
</selectKey>
INSERT INTO book(num, title, author, publisher)
VALUES(#{num}, #{title}, #{author}, #{publisher})
</insert>
<update id="update" parameterType="bookDto">
UPDATE book
SET title=#{title}, author=#{author}, publisher=#{publisher}
WHERE num=#{num}
</update>
<delete id="delete" parameterType="int">
DELETE FROM book
WHERE num=#{num}
</delete>
</mapper>
public interface BookDao {
public List<BookDto> selectAll();
public void insert(BookDto dto);
public int update(BookDto dto);
public int deleteByNum(int num);
public BookDto getByNum(int num);
}
@Repository
public class BookDaoImpl implements BookDao{
private final SqlSession session;
public BookDaoImpl(SqlSession session) {
this.session = session;
}
@Override
public List<BookDto> selectAll() {
List<BookDto> list = session.selectList("book.selectAll");
return list;
}
@Override
public void insert(BookDto dto) {
session.insert("book.insert", dto);
}
@Override
public int update(BookDto dto) {
return session.update("book.update", dto);
}
@Override
public int deleteByNum(int num) {
return session.delete("book.delete", num);
}
@Override
public BookDto getByNum(int num) {
BookDto dto = session.selectOne("book.select", num);
return dto;
}
}
public interface BookService {
public List<BookDto> getAll();
public BookDto getBook(int num);
public void insertBook(BookDto dto);
public void updateBook(BookDto dto);
public void deleteBook(int num);
}
@Service
@RequiredArgsConstructor
public class BookServiceImpl implements BookService{
private final BookDao dao;
@Override
public List<BookDto> getAll() {
return dao.selectAll();
}
@Override
public BookDto getBook(int num) {
return dao.getByNum(num);
}
@Override
public void insertBook(BookDto dto) {
dao.insert(dto);
}
@Override
public void updateBook(BookDto dto) {
dao.update(dto);
}
@Override
public void deleteBook(int num) {
dao.deleteByNum(num);
}
}
@Controller
@RequiredArgsConstructor
public class BookController {
private final BookService service;
}
views/book 폴더 생성
BookController 에 list 메소드 생성
@GetMapping("/book/list")
public String list(Model model) {
List<BookDto> list = service.getAll();
model.addAttribute("list", list);
return "book/list";
}
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<div class="container">
<h1>도서 목록</h1>
<table>
<thead>
<tr>
<th>번호</th>
<th>제목</th>
<th>저자</th>
<th>출판사</th>
</tr>
</thead>
<tbody>
<c:forEach var="tmp" items="${list}">
<tr>
<td>${tmp.getNum()}</td>
<td>${tmp.title}</td>
<td>${tmp.author}</td>
<td>${tmp.publisher}</td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
<a href="${pageContext.request.contextPath}/book/new-form">도서 추가</a>
<div class="container">
<h1>도서 추가 양식</h1>
<form action="${pageContext.request.contextPath}/book/save" method="post">
<div>
<label for="title">제목</label>
<input type="text" name="title" id="title"/>
</div>
<div>
<label for="author">저자</label>
<input type="text" name="author" id="author"/>
</div>
<div>
<label for="publisher">출판사</label>
<input type="text" name="publisher" id="publisher"/>
</div>
<button type="submit">저장</button>
</form>
</div>
newForm , save 메소드 생성@GetMapping("/book/new-form")
public String newForm() {
return "/book/new-form";
}
@PostMapping("/book/save")
public String save(BookDto dto) {
service.insertBook(dto);
return "redirect:/book/list";
}
<th>
수정
</th>
<td>
<a href="edit?num=${tmp.num}">수정</a>
</td>
<div class="container">
<h1>도서 정보 수정 양식</h1>
<form action="${pageContext.request.contextPath}/book/update" method="post">
<input type="hidden" name="num" value="${dto.num}"/>
<div>
<label for="title">제목</label>
<input type="title" name="title" id="title" value="${dto.title}"/>
</div>
<div>
<label for="author">저자</label>
<input type="text" name="author" id="author" value="${dto.author}"/>
</div>
<div>
<label for="publisher">출판사</label>
<input type="text" name="publisher" id="publisher" value="${dto.publisher}" />
</div>
<button type="submit">수정</button>
<button type="reset">취소</button>
</form>
</div>
edit 메소드 생성@GetMapping("/book/edit")
public String edit(int num, Model model) {
BookDto dto = service.getBook(num);
model.addAttribute("dto", dto);
return "book/edit";
}
update 메소드 생성@PostMapping("/book/update")
public String update(BookDto dto) {
service.updateBook(dto);
return "book/update";
}
<div class="container">
<p>
<strong>${param.title}</strong> 도서의 정보를 수정했습니다
<a href="${pageContext.request.contextPath}/book/list">도서 목록보기</a>
</p>
</div>
<th>삭제</th>
<td>
<a href="delete?num=${tmp.num}">삭제</a>
</td>
delete 메소드 생성@GetMapping("/book/delete")
public String delete(int num) {
service.deleteBook(num);
return "book/delete";
}
alert("${param.num}번 도서의 정보를 삭제했습니다");
location.href = "${pageContext.request.contextPath}/book/list"
⭐ 클라이언트 요청 ⭢ Controller ⭢ Service ⭢ DAO ⭢ Mapper ⭢ DB
↑
예외 처리