60일차 내용 정리

채공부·2025년 8월 18일

Spring Framework

HomeController 클래스 분석

@Controller
public class HomeController {
	@GetMapping("/")
	public String home(Model model) {
		List<String> notice = List.of("공지사항 입니다", "공지사항1", "공지사항2");
		model.addAttribute("notice", notice);
        
		return "home";
	}
}

@Controller

  • 어노테이션으로 bean 이 되는 객체는 클라이언트의 요청을 처리할 수 있다
  • bean 으로 관리 = Spring bean container 에서 관리가 된다 (Spring 이 관리하는 객체가 된다)
  • 두번째 인자가 Obejct 이니 int, String, .. Dto, List... 등 모든 다 담을 수 있다
addAttribute(String attributeName, Object attributeValue)
  • 컨트롤러 메소드의 매개변수에 Model 을 선언하면 객체가 자동으로 전달된다
@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 페이지로 응답이 위임되어서 응답하게 되는 것이다
  • home.jsp 페이지에서는 request 영역에 "notice" 라는 키값으로 담긴 데이터를 EL 표현식을 이용해서 추출할 수 있다
    ${requestScope.notice} 또는 ${notice}
return "home";

회원 추가

  • list.jsp 링크 추가
<a href="${pageContext.request.contextPath}/member/new-form">회원 추가</a>
  • MemberController 에 newForm 메소드 생성
@GetMapping("/member/new-form")
public String newForm() {
	// 현재 여기서 수행할 로직은 없고 view page 의 위치만 리턴해준다
	return "member/new-form";
}
  • new-form.jsp 생성
<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 메소드를 만들어야 한다

@PostMapping

  • MemberController 에 save 메소드 생성

  • 매개변수에 MemberDto 를 선언하면 폼 전송되는 파라미터가 자동 추출되어서 MemberDto 객체에 담긴 채로 전달된다

@Setter
@Getter
public class MemberDto {
	private int num;
	private String name;
	private String addr;
}
  • 단 MemberDto 클래스의 필드명과 폼 전송되는 파라미터명이 같아야 한다
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: 으로 시작하는 문자열을 리턴하면 된다

회원 삭제

  • list.jsp 삭제 칼럼 추가
<th>삭제</th>

<td>
	<a href="delete?num=${tmp.num}">삭제</a>
</td>
  • MemberController 에 delete 메소드 생성
  • 매개변수에 int num 을 선언하면 요청 파라미터 중에서 num 이라는 파라미터 명으로 전달되는 문자열을 자동추출해서 Integer.parseInt() 를 수행해서 실제 int 값으로 바꾼 다음 해당 값을 매개 변수에 전달해 준다
  • 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";

회원 정보 수정

  • list.jsp 수정 칼럼 추가
<th>수정</th>

<td>
	<a href="edit?num=${tmp.num}">수정</a>
</td>
  • MemberController 에 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";
}
  • edit.jsp 생성
<%@ 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>
  • MemberController 에 update 메소드 생성
@PostMapping("/member/update")
public String update(MemberDto dto) {
	// 수정할 회원의 정보가 MemberDto 객체에 담겨서 전달된다
	dao.update(dto);		
	return "member/update";
}
  • update.jsp 생성
<div class="container">
	<p>
		<strong>${param.name}</strong>님의 정보 수정 완료
		<a href="${pageContext.request.contextPath}/member/list">목록 보기</a>
	</p>
</div>

의존 관계

spring04.service 패키지 생성

  • MemberService 인터페이스 생성
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);
}
  • MemberServiceImpl 클래스 생성
// 서비스 클래스에 붙여줄 어노테이션
@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) {
			
		}
	}
}
  • MemberController 에서 의존 객체 변경
@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 패키지 생성

  • ExceptionController 클래스 생성
  • @ControllerAdvice : 예외 처리를 할 컨트롤러에 해당 어노테이션을 붙여준다
  • Spring 프레임워크가 동작하는 과정 중에 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";
	}
}
  • MemberException 클래스 생성
  1. RuntimeException 클래스를 상속받은 나만의 예외 클래스를 정의

  2. 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+"번 회원이 없습니다");
		
	}
}
  • MemberServiceImpl 코드 추가
@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);
	}
}
  • ExceptionController 에 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";
}
  • member-exception.jsp 생성
<div class="container">
	<h1>${title}</h1>
	<p>${message}</p>
	<p>원인 : <strong>${reason}</strong></p>
</div>
  • MemberDaoImpl 의존 객체 코드 받는 걸 다른 방법으로 수정
// MyBatis 를 사용할 때 필요한 핵심 객체
private final SqlSession session;
	
// 생성자를 이용해서 의존 객체를 주입받는 것이 더 일반적 (lombock 의 기능을 이용하면 생략 가능)
@Autowired // 생성자가 오직 1개인 경우에는 생략 가능
public MemberDaoImpl(SqlSession session) {
	this.session = session;
}
  • MemberServiceImpl 의존 객체 선언 방법 수정
  • 의존 객체 final 예약어를 붙이고 클래스에 @RequiredArgsConstructor 를 붙이면 의존 객체를 전달받는 생성자가 자동 생성
    ➜ lombok 이 생성자를 자동으로 만들어 주도록 한다
@RequiredArgsConstructor
public class MemberServiceImpl implements MemberService{
	private final MemberDao dao;
	
}
  • MemberController 의존 객체 선언 수정
@RequiredArgsConstructor
public class MemberController {	
	// 필요한 의존 객체를 주입 받는다
	private final MemberService service;
}

별칭

  • application.properties 에서 설정
  • type 에 별칭을 붙인 클래스를 찾아서 로딩하기 위해
  • com.example.spring04. 하위 패키지에서 모든 @Alias 가 붙은 클래스를 찾아서 사용할 준비
mybatis.type-aliases-package=com.example.spring04.**
  • MemberDto 에 @Alias 어노테이션 추가
  • MyBatis 에서 제공하는 @Alias 어노테이션을 이용해서 MemberDto type 의 별칭을 부여할 수 있다
  • 부여된 별칭을 Mapper xml 문서에서 parameterType or resultType 으로 활용할 수 있다
@Alias("memberDto")
  • MemberMapper.xml 코드 수정
<!--  @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>

도서 (Book table) 실습

  • home.jsp 링크 추가
<li><a href="${pageContext.request.contextPath}/book/list">책 목록보기</a></li>

실습 순서

  1. BookDto 생성
  2. mapper/BookMapper.xml 생성
  3. BookDao 인터페이스, BookDaoImpl 클래스를 만들어 select, insert, update, delete 기능 생성
  4. BookService 인터페이스, BookServiceImpl 클래스를 만들어 서비스 메소드 완성
  5. BookController 를 생성하여
    /book/list, book/new-form, book/save, book/edit, book/update, book/delete 요청 처리
  6. view page 는 views/book/xxx.jsp 형식으로 모두 생성해 book 에 관련된 서비스를 spring framework 로 구현
  • BookDto 생성
@Alias("bookDto")
@Setter
@Getter
public class BookDto {
	private int num;
	private String title;
	private String author;
	private String publisher;
}
  • BookMapper.xml 생성
<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>
  • BookDao 인터페이스 생성
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);
}
  • BookDaoImpl 클래스 생성
@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;
	}
	
}
  • BookService 인터페이스 생성
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);
}
  • BookServiceImpl 클래스 생성
@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);
	}
}
  • BookController 생성
@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";
}
  • list.jsp 생성
<%@ 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>
  • list.jsp 도서 추가 링크 추가
<a href="${pageContext.request.contextPath}/book/new-form">도서 추가</a>
  • new-form.jsp 생성
<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>
  • BookController 에 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";
}
  • list.jsp 도서 정보 수정 링크 추가
<th>
	수정
</th>

<td>
	<a href="edit?num=${tmp.num}">수정</a>
</td>
  • edit.jsp 생성
<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>
  • BookController 에 edit 메소드 생성
@GetMapping("/book/edit")
public String edit(int num, Model model) {
	BookDto dto = service.getBook(num);
	model.addAttribute("dto", dto);
		
	return "book/edit";
}
  • BookController 에 update 메소드 생성
@PostMapping("/book/update")
public String update(BookDto dto) {
	service.updateBook(dto);
		
	return "book/update";
}
  • update.jsp 생성
<div class="container">
	<p>
		<strong>${param.title}</strong> 도서의 정보를 수정했습니다
		<a href="${pageContext.request.contextPath}/book/list">도서 목록보기</a>
	</p>
</div>
  • list.jsp 도서 정보 삭제 링크 추가
<th>삭제</th>

<td>
	<a href="delete?num=${tmp.num}">삭제</a>
</td>
  • BookController 에 delete 메소드 생성
@GetMapping("/book/delete")
public String delete(int num) {
	service.deleteBook(num);
		
	return "book/delete";
}
  • delete.jsp 생성
alert("${param.num}번 도서의 정보를 삭제했습니다");
location.href = "${pageContext.request.contextPath}/book/list"

코드 전체 흐름

⭐ 클라이언트 요청 ⭢ Controller ⭢ Service ⭢ DAO ⭢ Mapper ⭢ DB
                               ↑
                            예외 처리
profile
학원 공부 내용 정리

0개의 댓글