67일차 내용 정리

채공부·2025년 8월 27일
post-thumbnail

게시판

목록 검색

  • list.html 에 검색 버튼 수정 및 초기화 버튼 생성

  • visually-hidden : 시각적으로 숨김

<input th:value="${#strings.isEmpty(dto.keyword) ? '' : dto.keyword}" type="text" name="keyword" class="form-control" placeholder="검색어 입력..."/>	
<button type="submit" class="btn btn-outline-secondary">
	<i class="bi bi-search"></i>
	<span class="visually-hidden">검색</span>
</button>
<a class="btn btn-outline-danger" th:href="@{/board/list}">
	<i class="bi bi-arrow-clockwise"></i>
	<span class="visually-hidden">새로고침</span>
</a>
  • BoardServiceImpl 에 query = ...& 추가
  • 검색 키워드가 없으면 query="" 빈 문자열
  • 검색 키워드가 있으면 query="&search=검색조건&keyword=검색어" 형식의 문자열
String query = "";
if(dto.getKeyword() != null) {
	query = "&search="+dto.getSearch()+"&keyword="+dto.getKeyword();
}
  • list.html pagination 페이징 처리 수정
  • @{'/board/list?pageNum='+${dto.startPageNum-1} + ${dto.query}}
  • 위와 같이 작성하거나 혹은 문자열을 이어 붙일 때는 아래 방식이 간편
  • @{|/board/list?pageNum=${dto.startPageNum-1}${dto.query}|}
<ul class="pagination">
	<li th:if="${dto.startPageNum ne 1}" class="page-item">
		<a class="page-link"
			th:href="@{|/board/list?pageNum=${dto.startPageNum-1}${dto.query}|}">&lsaquo;</a>
	</li>		
	<li th:each="i : ${#numbers.sequence(dto.startPageNum, dto.endPageNum)}" class="page-item">
		<a class="page-link" th:classappend="${i eq dto.pageNum ? 'active' : null}" 
			th:href="@{|/board/list?pageNum=${i}${dto.query}|}" th:text="${i}">
		</a>
	</li>			
					
	<li th:if="${dto.endPageNum lt dto.totalPageCount}" class="page-item">
		<a class="page-link" 
			th:href="@{|/board/list?pageNum=${dto.endPageNum+1}${dto.query}|}">&rsaquo;</a>
	</li>
</ul>

검색 후 상세보기에서 prev next 버튼 처리

  • list.html 에 th:each="tmp : ${dto.list}" 코드 수정
<td>
	<a th:href="@{|/board/view?num=${tmp.num}${dto.query}|}" th:text="${tmp.title}"></a>
</td>
  • BoardService 에 getDetail 메소드 수정

  • 글 자세히 보기를 보여주기 위한 서비스 메소드

public BoardDto getDetail(BoardDto dto);
  • BoardService 에 getDetail 오버라이드 수정
@Override
public BoardDto getDetail(BoardDto dto) {

	return boardDao.getByDto(dto);
}
  • BoardDao 에 getByDto 메소드 생성
public BoardDto getByDto(BoardDto dto);
  • BoardDaoImpl 에 오버라이드
@Override
public BoardDto getByDto(BoardDto dto) {
	return session.selectOne("board.getByDto", dto);
}
  • BoardMapper 에 getByDto 문 추가
<select id="getByDto" parameterType="boardDto" resultType="boardDto">
	SELECT *
	FROM
		(SELECT b.num, writer, title, content, viewCount,
		TO_CHAR(b.createdAt, 'YY"년" MM"월" DD"일" HH24:MI') AS createdAt,
		profileImage,
		LAG(b.num, 1, 0) OVER (ORDER BY b.num DESC) AS prevNum,
		LEAD(b.num, 1, 0) OVER (ORDER BY b.num DESC) AS nextNum
		FROM board b
		INNER JOIN users u ON b.writer = u.userName)	
	WHERE num = #{num}
</select>
  • BoardMapper 에 sqlsearch 생성
<sql id="search">
	<choose>
		<when test="search == 'writer'">
			WHERE writer LIKE '%' || #{keyword} || '%'
		</when>
		<when test="search == 'title'">
			WHERE title LIKE '%' || #{keyword} || '%'
		</when>
		<when test="search == 'title_content'">
			WHERE title LIKE '%' || #{keyword} || '%' OR content LIKE '%' || #{keyword} || '%' 
		</when>
	</choose>
</sql>
  • BoardMapper 에 selectPage & getCount & getByDto
    해당 search 문으로 변경
<select id="selectPage" parameterType="boardDto" resultType="boardDto">
	SELECT *
	FROM
		(SELECT result1.*, ROWNUM AS rnum
		FROM	
			(SELECT num, writer, title, viewCount, createdAt
			FROM board
				
			<include refid="search"/>
				
			ORDER BY num DESC
				
			) result1)
	WHERE rnum BETWEEN #{startRowNum} AND #{endRowNum}
</select>
	
<select id="getCount" parameterType="boardDto" resultType="int">
	SELECT NVL(MAX(ROWNUM), 0) AS count
	FROM board
	<include refid="search"/>
</select>

<select id="getByDto" parameterType="boardDto" resultType="boardDto">
	SELECT *
	FROM
		(SELECT b.num, writer, title, content, viewCount,
		TO_CHAR(b.createdAt, 'YY"년" MM"월" DD"일" HH24:MI') AS createdAt,
		profileImage,
		LAG(b.num, 1, 0) OVER (ORDER BY b.num DESC) AS prevNum,
		LEAD(b.num, 1, 0) OVER (ORDER BY b.num DESC) AS nextNum
		FROM board b
		INNER JOIN users u ON b.writer = u.userName
		<include refid="search"/>
        )
	WHERE num = #{num}
</select>
  • BoardController 에 view 메소드 수정
  • 전달할 파라미터값을 int num 이 아닌 BoardDto requestDto 로 수정
  • 검색 query 정보 전달
@GetMapping("/board/view")
public String boardView(BoardDto requestDto, Model model) {
	/*
	 * requestDto 에는 자세히 보여줄 글의 num 와
	 * search (검색 조건), keyword (검색어) 가 들어 있을 수도 있다
	 * 검색어가 없는 경우는 search 와 keyword 에는 null 이 들어 있다
	 * */
		
	// 서비스를 이용해서 응답에 필요한 데이터를 얻어내서
	BoardDto dto = service.getDetail(requestDto);
    
    SString query = "";
	if(requestDto.getKeyword() != null) {
		query = "&search="+requestDto.getSearch()+"&keyword="+requestDto.getKeyword();
	}
	// 검색 query 정보도 view page 에 전달
	model.addAttribute("query", query);
		
	// 댓글 목록은 원글의 글번호를 전달해서 얻어낸다
	List<CommentDto> comments = service.getComments(requestDto.getNum());
		
	// 모델 객체에 담고
	model.addAttribute("dto", dto);
	model.addAttribute("commentList", comments);
	// 로그인된 userName 얻어내기
	// 로그인 안 했으면 "annontmousUser" 가 리턴
	String userName = SecurityContextHolder.getContext().getAuthentication().getName();
	boolean isLogin = userName.equals("anonymousUser") ? false:true;
	// 위의 추가 정보고 모델 객체에 담는다
	model.addAttribute("userName", userName);
	model.addAttribute("isLogin", isLogin);
	// 타임리프 페이지에서 응답
	return "board/view";
}
  • view.html 다음글 & 이전글 이동 코드 수정
<div class="btn-group mb-2">
	<a class="btn btn-outline-secondary bg-primary-subtle btn-sm"
 		th:classappend="${dto.prevNum eq 0 ? 'disabled' : null}"
		th:href="@{|/board/view?num=${dto.prevNum}${query}|}">    
      <i class="bi bi-arrow-left"></i>
      Prev
	</a>
	<a class="btn btn-outline-secondary bg-primary-subtle btn-sm"
		th:classappend="${dto.nextNum eq 0 ? 'disabled' : null}"
		th:href="@{|/board/view?num=${dto.nextNum}${query}|}">
		Next
		<i class="bi bi-arrow-right"></i>
	</a>
</div>
  • view.html 에 코드 추가
  • Thymeleaf view page 에서 요청 파라미터로 전달되었던 값도 추출 가능
  • ${param.파라미터명}
  • ${#strings } : 문자열에 관련된 유틸리티 객체를 활용 가능
  • ${!#strings.isEmpty(param.search)} : search 라는 파라미터명으로 전달된 값이 있는지 확인
<p th:if="${!#strings.isEmpty(param.search)}" 
	class="alert alert-info px-3 py-2 rounded-3 shadow-sm">
	<i class="bi bi-search me-2"></i>
	<strong th:text="${param.search}"></strong> 조건
	<strong th:text="${param.keyword}"></strong> 검색 결과 입니다
</p>

게시글 삭제

  • BoardController 에 boardDelete 메소드 생성
@GetMapping("/board/delete")
public String boardDelete(int num) {
		
	return "board/delete";
}
  • BoardService 에 deleteContent 메소드 생성
  • 게시글 삭제
public void deleteContent(int num);
  • BoardServiceImpl 에 추상 메소드 오버라이드
@Override
public void deleteContent(int num) {
	// 글 작성자와 로그인된 userName 이 동일한지 비교해서 동일하지 않으면 예외를 발생시킨다
	String writer = boardDao.getByNum(num).getWriter();
	String userName = SecurityContextHolder.getContext().getAuthentication().getName();
	if(!writer.equals(userName)) {
		throw new RuntimeException("남의 글을 지울 수 없습니다");
	}		
}
  • BoardDao 에 delete 메소드 생성
public int delete(int num);
  • BoardDaoImpl 에 추상 메소드 오버라이드
@Override
public int delete(int num) {
	return session.delete("board.delete", num);
}
  • BoardMapper 에 delete 문 추가
  • 파라미터로 전달되는 data 의 type 이 숫자나 문자인 경우
    " #{여기는 아무 단어나 써도 된다} "
  • dto 인 경우 dto 의 필드명을 작성해야 한다
<delete id="delete" parameterType="int">
	DELETE FROM board
	WHERE num = #{num}
</delete>
  • BoardServiceImpl 에 deleteContent 메소드 완성
@Override
public void deleteContent(int num) {
	// 글 작성자와 로그인된 userName 이 동일한지 비교해서 동일하지 않으면 예외를 발생시킨다
	String writer = boardDao.getByNum(num).getWriter();
	String userName = SecurityContextHolder.getContext().getAuthentication().getName();
	if(!writer.equals(userName)) {
		throw new RuntimeException("남의 글을 지울 수 없습니다");
	}
	// 삭제하고 삭제된 row 의 갯수를 리턴받는다 정상 삭제 시 1, 삭제 실패 시 0 이 리턴
	int rowCount = boardDao.delete(num);
	if(rowCount == 0) {
		throw new RuntimeException("삭제 실패");
	}
}
  • BoardController 에 boardDelete 메소드 완성
@GetMapping("/board/delete")
public String boardDelete(int num) {
	service.deleteContent(num);
	return "board/delete";
}
  • delete.html 생성
<script>
	alert("[[${param.num}]] 번 글 삭제 완료");
	location.href = "[[@{/board/list}]]";
</script>

게시글 수정

  • BoardController 에 boardEdit 메소드 생성
@GetMapping("/board/edit")
public String boardEdit(int num, Model model) {
		
	return "board/edit";
}
  • BoardService 에 getData 메소드 생성

  • 수정할 글 정보를 보여주기 위한 서비스 메소드

public BoardDto getData(int num);
  • BoardServiceImpl 에 추상 메소드 오버라이드
@Override
public BoardDto getData(int num) {
	return boardDao.getByNum(num);
}
  • BoardController 에 boardEdit 메소드 완성
@GetMapping("/board/edit")
public String boardEdit(int num, Model model) {
	model.addAttribute("dto", service.getData(num));
	return "board/edit";
}
  • edit.html 생성
<div class="container pt-3">
  <nav aria-label="breadcrumb">
    <ol class="breadcrumb">
      <li class="breadcrumb-item">
        <a href="@{/}/">Home</a>
      </li>
      <li class="breadcrumb-item">
        <a href="@{/board/list}">Board</a>
      </li>
      <li class="breadcrumb-item active">Edit</li>
    </ol>
  </nav>

  <h1>글 수정 페이지</h1>

  <form th:action="@{/board/update}" method="post" id="editForm">
    <div>
      <label class="form-label" for="num">글번호</label>
      <input class="form-control" type="text" name="num" id="num" 
             th:value="${dto.num}" readonly />
    </div>

    <div>
      <label class="form-label" for="writer">작성자</label>
      <input type="text" class="form-control" name="writer" id="writer"
             th:value="${dto.writer}" readonly />
    </div>

    <div>
      <label class="form-label" for="title">제목</label>
      <input type="text" class="form-control" name="title" id="title"
             th:value="${dto.title}" />
    </div>

    <div>
      <label class="form-label" for="editor">내용</label>
      <div id="editor"></div>
      <textarea class="form-control d-none" name="content" id="hiddenContent"></textarea>
    </div>

    <button class="btn btn-success btn-sm">수정</button>
    <button class="btn btn-danger btn-sm">Reset</button>
  </form>
</div>

<script>
  // 위에 toast ui javascript 가 로딩되어 있으면 toastui.Editor 클래스를 사용할 수 있다
  // 해당 클래스를 이용해서 객체 생성하면서 {} object 로 ui 에 관련된 옵션을 잘 전달하면
  // 우리가 원하는 모양의 텍스트 편집기를 만들 수 있다
  const editor = new toastui.Editor({
    el: document.querySelector('#editor'),
    height: '500px',
    initialEditType: 'wysiwyg',
    previewStyle: 'vertical',
    language: 'ko',
    initialValue: `[(${dto.content})]` // unescape 된 text 를 출력
  });

  document.querySelector("#editForm").addEventListener("submit", (e) => {
    // 에디터로 작성된 문자열 읽어오기
    const content = editor.getHTML();
    // 테스트로 콘솔에 출력하기
    console.log(content);
    // 에디터로 작성된 문자열을 폼 전송이 될수 있도록 textarea 의 value 로 넣어준다.
    document.querySelector("#hiddenContent").value = content;

    // 테스트 하기 위해 폼 전송 막기
    // e.preventDefault();

    // 폼전송을 막지 않으면 전송이 된다.
  });
</script>
  • BoardController 에 boardUpdate 메소드 생성

  • 게시글 수정 반영 요청 처리

@PostMapping("/board/update")
public String boardUpdate(BoardDto dto, RedirectAttributes ra) {
	// 글 수정 반영하고
		
	// 글 자세히 보기로 리다일렉트 이동
	return "redirect:/board/view?num="+dto.getNum();
}
  • BoardService 인터페이스에 updateContent 메소드 생성
public void updateContent(BoardDto dto); // 게시글 수정
  • BoardServiceImpl 에 추상 메소드 오버라이드
@Override
public void updateContent(BoardDto dto) {
	// 글 작성자와 로그인된 userName 이 동일한지 비교해서 동일하지 않으면 예외를 발생시킨다
	String writer = boardDao.getByNum(dto.getNum()).getWriter();
	String userName = SecurityContextHolder.getContext().getAuthentication().getName();
	if(!writer.equals(userName)) {
		throw new RuntimeException("본인이 작성한 글만 수정이 가능합니다");
	}
}
  • BoardDao 에 update 메소드 생성
public int update(BoardDto dto);
  • BoardDaoImpl 에 추상 메소드 오버라이드
@Override
public int update(BoardDto dto) {
	return session.update("board.update", dto);
}
  • BoardMapper 에 update 문 추가
<update id="update" parameterType="boardDto">
	UPDATE board
	SET title = #{title}, content = #{content}
	WHERE num = #{num}
</update>
  • BoardServiceImpl 에 추상 메소드 완성
@Override
public void updateContent(BoardDto dto) {
	// 글 작성자와 로그인된 userName 이 동일한지 비교해서 동일하지 않으면 예외를 발생시킨다
	String writer = boardDao.getByNum(dto.getNum()).getWriter();
	String userName = SecurityContextHolder.getContext().getAuthentication().getName();
	if(!writer.equals(userName)) {
		throw new RuntimeException("본인이 작성한 글만 수정이 가능합니다");
	}
	int rowCount = boardDao.update(dto);
	if(rowCount == 0) {
		throw new RuntimeException("수정 실패");
	}
}
  • BoardController 에 boardUpdate 메소드 완성
  • RedirectAttributes 매개변수 전달
    addFlashAttribute("message", ...) : view page 에서 ${message} 로 참조 가능
@PostMapping("/board/update")
public String boardUpdate(BoardDto dto, RedirectAttributes ra) {
	// 글 수정 반영하고
	service.updateContent(dto);
	// 리다일렉트 이동해서 출력할 메세지도 담는다
	ra.addFlashAttribute("message", "게시글을 성공적으로 수정 완료");
	// 글 자세히 보기로 리다일렉트 이동
	return "redirect:/board/view?num="+dto.getNum();
}
  • view.html 에 메세지 출력 코드 추가
  • 일회성 메세지
  • th:if="${message != null}" th:if="${!#strings.isEmpty(message)}" 대신에
  • th:if="${message}" 로 사용하면 편리
    만일 message 가 존재한다면 이라고 읽으면 된다
<p th:if="${message}" class="alert alert-success p-3" th:text="${message}"></p>

실습1 (Member)

MemberDao, MemberDaoImpl, MemberMapper 을 만들지 않고
@Mapper
public interface MemberMapper{ } 로 검색해서 구현

  • MemberDto 생성
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class MemberDto {
	private int num;
	private String name;
	private String addr;
}
  • MemberMapper 인터페이스 생성
  • MyBatis 의 Mapper 를 인터페이스 설정으로 사용할 수 있도록 어노테이션 추가
  • Select & Insert & Update & Delete & SelectKey 어노테이션 추가
@Mapper
public interface MemberMapper {
	
	@Select("SELECT num, name, addr FROM member ORDER BY num ASC")
	public List<MemberDto> selectAll();
		
	@Select("SELECT num, name, addr FROM member WHERE num = #{num}")
	public MemberDto getByNum(int num);
	
	@Insert("""
			INSERT INTO member(num, name, addr)
			VALUES(#{num}, #{name}, #{addr})
    """)
	@SelectKey(
		statement = "SELECT member_seq.NEXTVAL FROM DUAL",
		keyProperty = "num",
		resultType = int.class,
		before = true
	)
	public void insert(MemberDto dto);
	
	@Update("UPDATE member SET name = #{name}, addr = #{addr} WHERE num = #{num}")
	public int update(MemberDto dto);
	
	@Delete("DELETE FROM member WHERE num = #{num}")
	public int deleteByNum(int num); // delete 된 rowCount 가 리턴
}
  • MemberException 생성
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+"번 회원이 없습니다");
		
	}
}
  • 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";
}
  • 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 클래스 생성

  • MemberMapper type 을 주입 받아서 사용

@RequiredArgsConstructor
@Service
public class MemberServiceImpl implements MemberService{
	
	private final MemberMapper mapper;

	@Override
	public List<MemberDto> getAll() {
		return mapper.selectAll();
	}

    @Override
    public MemberDto getMember(int num) {        
        MemberDto dto = mapper.getByNum(num);
		if(dto==null) {
			throw MemberException.notFound(num);
		}
		return dto;
    }

    @Override
	public void addMember(MemberDto dto) {
    		mapper.insert(dto);
	}
    
	@Override
	public void updateMember(MemberDto dto) {
		int rowCount = mapper.update(dto);
		if(rowCount == 0) {
			throw MemberException.updateFailed(dto.getNum());
		}
	}

	@Override
	public void deleteMember(int num) {
		int rowCount = mapper.deleteByNum(num);
		if(rowCount == 0) {
			throw MemberException.deleteFailed(num);
		}
	}
}
  • MemberController 클래스 생성
@RequiredArgsConstructor
@Controller
public class MemberController {
	// 필요한 의존 객체를 주입 받는다
	private final 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";
	}
}
  • list.html 생성
<div class="container">
	<a th:href="@{/member/new-form}">회원 추가</a>
	<h1>회원 목록 입니다</h1>
	<table>
		<thead>
			<tr>
				<th>번호</th>
				<th>이름</th>
				<th>주소</th>
				<th>수정</th>
				<th>삭제</th>
			</tr>
		</thead>
		<tbody>

			<tr th:each="tmp : ${list}">
				<td>[[${tmp.num }]]</td>
				<td>[[${tmp.name }]]</td>
				<td>[[${tmp.addr }]]</td>
				<td>
					<a th:href="@{/member/edit(num=${tmp.num})}">수정</a>
				</td>
				<td>
					<a th:href="@{/member/delete(num=${tmp.num})}">삭제</a>
				</td>
			</tr>		
		</tbody>
	</table>
</div>
  • new-form.html 생성
<div class="container">
	<h1>회원 추가 양식</h1>
	<form th:action="@{/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>
  • edit.html 생성
<div class="container">
	<h1>회원 수정 폼</h1>
	<form th:action="@{/member/update}" method="post">
		<input type="hidden" name="num" th:value="${dto.num}"/>
		<div>
			<label for="name">이름</label>
			<input type="text" name="name" id="name" th:value="${dto.name}"/>
		</div>
		<div>
			<label for="addr">주소</label>
			<input type="text" name="addr" id="addr" th:value="${dto.addr}"/>
		</div>
		<button type="submit">수정 확인</button>
		<button type="reset">취소</button>
	</form>
</div>
  • update.html 생성
<div class="container">
	<p>
		<strong>[[${param.name}]]</strong>님의 정보 수정 완료
		<a th:href="@{/member/list}">목록 보기</a>
	</p>
</div>
  • delete.html 생성
<script th:inline="javascript">
	alert( [[${param.num}]] + " 번 회원의 정보를 삭제 했습니다");
	location.href=[[@{/member/list}]] ;
</script>
  • data-access.html 생성
<div class="container">
	<h1>[[${title}]]</h1>
	<p>[[${message}]]</p>
	<p>상태 코드 : <strong>[[${status}]]</strong></p>
	<a th:href="@{/}">인덱스로</a>
</div>
  • member-exception.html 생성
<div class="container">
	<h1 th:text="${title}"></h1>
	<p th:text="${message}"></p>
	<p>원인 : <strong th:text="${reason}"></strong></p>

</div>

복잡한 Mapper

따로 xml 문서로 빼는 것이 편하다

  • MemberMapper 인터페이스 insert 메소드 수정
  • 어노테이션 없이 메소드만 정의하기
  • Mapper xml 문서의 namespace="패키지명 포함해서 @Mapper 클래스 정의하기"
  • 메소드명이 xml 문서 안에 들어있는 sql 의 id 가 된다
  • namespace = "com.example.spring08.repository.MemberMapper"
  • <insert id="insert" parameterType="memberDto">
public void insert(MemberDto dto);
  • MemberMapper.xml 생성
  • @Mapper 어노테이션이 붙어 있는 인터페이스의
    전체 패키지명, 인터페이스의 이름을 namespace 에 명시하
  • public void insert(MemberDto dto); 에서 메소드명과
    xml 문서의 sql id 를 일치 시킨다
  • 매개변수에 선언한 데이터의 type 이 parameterType 이 된다
<mapper namespace="com.example.spring08.repository.MemberMapper">
	<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>
</mapper>

AOP

관점지향 프로그래밍

횡단 관심사란?

여러 곳에 반복되는 공통 기능을 한 곳에 모아 처리하는 것

spring08.test 패키지 생성

  • WritingUtil 클래스 생성

  • @Component 어노테이션을 붙여서 spring 이 관리하는 bean으로 등록

@Component
public class WritingUtil {
	public void writeLetter() {
		
		System.out.println("편지를 써요");	
		try {
			Thread.sleep(1000);
		}catch(Exception e) {}
		
	}
	
	public void writeReport() {
		
		System.out.println("보고서를 써요");
		try {
			Thread.sleep(1000);
		}catch(Exception e) {}
		
	}
	public void writeDiary() {
		
		System.out.println("일기를 써요");	
		try {
			Thread.sleep(1000);
		}catch(Exception e) {}
		
	}
}
  • Spring08ExampleApplication 에 의존 객체 주입 & testAop 메소드 생성
// 의존 객체 주입 받기
@Autowired
private WritingUtil util;
	
// spring 프레임 워크가 준비되고 난 이후에 호출할 메소드
@PostConstruct
public void testAop() {
	util.writeLetter();
	util.writeReport();
	util.writeDiary();
}
편지를 써요
보고서를 써요
일기를 써요

spring08.aop 패키지 생성

  • pom.xml 에 dependency 추가

  • aop (Aspect Oriented Programming) 관련 dependency

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId></dependency>
⭐ maven ➜ pom.xml
   gradle ➜ build.gradle

AspectJ Expression

  • TimeAspect 클래스 생성 후
    @Before("execution(...)") , @After("execution(...)") 어노테이션 생성
  • 횡단 관심사를 따로 클래스를 만들어서 작성
    관심사는 @Aspect 어노테이션을 이용해서 만든다
@Aspect
@Component
public class TimeAspect {
	
    @Before("execution(void writeLetter())")
	public void start() {
		Date start = new Date();
		long startTime = start.getTime();
		System.out.println("시작 시간 : " +startTime);
	}
	
    @After("execution(void writeLetter())")
	public void end() {
		Date start = new Date();
		long startTime = start.getTime();
		System.out.println("종료 시간 : " +startTime);
	}
}
시작 시간 : 17562788117561/1000 초 단뒤
편지를 써요
종료 시간 : 1756278812757
보고서를 써요
일기를 써요
  • write*() 로 변경
  • 매개변수가 없고
    리턴 타입이 void이며
    메소드 이름이 write로 시작하는
    모든 메소드의 시작 전과 종료 후에 AOP가 개입

  • 원하는 위치에 aop 를 적용하려면 이 부분을 잘 작성하면 된다
    여기의 작성 문법이 AspectJ Expression

@Before("execution(void write*())")
public void start() {
	Date start = new Date();
	long startTime = start.getTime();
	System.out.println("시작 시간 : " +startTime);
}
	
@After("execution(void write*())")
public void end() {
	Date start = new Date();
	long startTime = start.getTime();
	System.out.println("종료 시간 : " +startTime);
}

실습2 (Gallery)

GalleryService 에서 GalleryDto 목록을 얻어오는 작업

// 갤러리 목록을 읽어온 다음
List<GalleryDto> list = mapper.getList();

// GalleryDto 에 Gallery 이미지 목록을 하나씩 반복문 돌면서 넣어준다
for(GalleryDto tmp : list){
	List<GalleryImageDto> imageList = mapper.getImageLIst(tmp.getNum());
	tmp.setImageList(imageList);
}
profile
학원 공부 내용 정리

0개의 댓글