Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
Tags
- 선형대수
- iterator
- 코드로배우는스프링부트웹프로젝트
- d
- 리눅스
- baeldung
- 이터레이터
- 서버설정
- 구멍가게코딩단
- resttemplate
- 스프링 시큐리티
- 코드로배우는스프링웹프로젝트
- /etc/network/interfaces
- 자료구조와 함께 배우는 알고리즘 입문
- GIT
- 알파회계
- 처음 만나는 AI 수학 with Python
- ㅒ
- 스프링부트핵심가이드
- 목록처리
- 처음 만나는 AI수학 with Python
- 자료구조와함께배우는알고리즘입문
- Kernighan의 C언어 프로그래밍
- 자바편
- 티스토리 쿠키 삭제
- 데비안
- 네트워크 설정
- 친절한SQL튜닝
- network configuration
- 페이징
Archives
- Today
- Total
bright jazz music
guestbook : 10. guestbook 게시글 수정/삭제 처리(1) 본문
● 수정과 삭제의 시작은 '수정' 페이지로 진입하는 것이다. 수정 화면에서 수정 또는 삭제 버튼을 눌러 처리한다.
수정 처리
- 게시글 조회 - 수정 버튼 클릭 - 내용 수정 - 수정 완료버튼 클릭- POST방식으로 데이터 전달 - 목록 페이지로 이동(PRG패턴)
삭제 처리
- 게시글 조회 - 삭제 버튼 클릭 - POST방식으로 데이터 전달 - 삭제 후 목록 페이지로 이동
1. 수정/삭제 처리를 위한 Controller 계층 수정
- 게시물의 수정과 삭제는 수정/삭제가 가능한 페이지에서 이루어진다.
- 이 페이지를 반환하기 위해 기존 GuestbookController.java의 read()메서드의 경로에 /modify를 추가한다.
//GuestbookController.java
package com.example.guestbook.controller;
import com.example.guestbook.dto.GuestbookDTO;
import com.example.guestbook.dto.PageRequestDTO;
import com.example.guestbook.dto.PageResultDTO;
import com.example.guestbook.service.GuestbookService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@Controller
@RequestMapping("/guestbook")
@Log4j2
@RequiredArgsConstructor //자동 주입을 위한 어노테이션
public class GuestbookController {
private final GuestbookService service; // final로 선언
@GetMapping("/")
public String index(){
return "redirect:/guestbook/list";
}
@GetMapping("/list")
public String list(PageRequestDTO pageRequestDTO, Model model){
log.info("list...................." + pageRequestDTO);
// model에 result를 key로 담아서 list 페이지에 뿌려준다.
model.addAttribute("result", service.getList(pageRequestDTO));
return "/guestbook/list";
}
/*
SpringDAta JPA를 이용하는 경우 @Pageable 어노테이션으로 Pageable 타입을 이용할 수도 있고,
application.properties에 0이 아닌 1부터 페이지 번호를 시작하도록 받을 수 있도록 처리할 수도 있다.
예제에서는 그냥 0부터 받는 방식을 사용하였다. 추후 검색조건 등과 같이 추가로 전달되어야 하는 데이터가
많을 경우 더욱 복잡해질 수 있기 때문이다.
*/
@GetMapping("/register")
public void register(){
log.info("register get...");
}
@PostMapping("/register")
public String registerPost(GuestbookDTO dto, RedirectAttributes redirectAttributes){
log.info("dto..." + dto);
//새로 추가된 엔티티의 번호
Long gno = service.register(dto);
redirectAttributes.addFlashAttribute("msg", gno);
return "redirect:/guestbook/list";
/*
* 등록 작업은 GET 방식에서는 화면을 보여주고 POST 방식에서는 처리 후에 목록페이지로 이동하도록 설계.
* 이 때 RedirectAttributes를 이용해서 한 번만 화면에서 'msg'라는 이름의 변수를 사용할 수 있도록 처리.
* addFlashAttribute()는 단 한 번만 데이터를 전달하는 용도로 사용함.
* --> 브라우저에 전달되는 'msg'를 이용해서 화면 창에 모달 창을 보여주는 용도로 사용.
* */
}
@GetMapping({"/read", "/modify"}) // modify도 추가하였다.
public void read(long gno, @ModelAttribute("requestDTO") PageRequestDTO requestDTO, Model model){
log.info("gno: " + gno);
GuestbookDTO dto = service.read(gno);
model.addAttribute("dto", dto);
}
}
2. 수정/삭제 처리할 화면 만들기
- modify.html 생성 후 read.html 내용을 복사한 뒤 수정한다. 페이지의 구성이 거의 유사하기 떄문이다.
- 이 때 수정/삭제할 작업을 <form>태그로 감싸야 한다. POST방식으로 전달할 것이기 때문이다.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/basic :: setContent(~{this::content})}">
<th:block th:fragment="content">
<h1 class="mt-4">GuestBook Modify Page!!</h1> <!-- 제목 변경 -->
<form action="/guestbook/modify" method="post"> <!-- form태그로 감싸기 -->
<div class="form-group">
<label>Gno</label> <!--수정불가-->
<input type="text" class="form-control" name="gno" th:value="${dto.gno}" readonly>
<!--
name 속성은 DTO의 프로퍼티와 같게 해줘야 한다. DTO에서 소문자로 선언했으므로
여기서도 소문자 gno로 해야 한다. 대문자로 했다가 아래와 같은 에러를 만났다.
아래의 태그들의 속성 값도 전부 소문자로 해줄 것.
java.lang.IllegalStateException:
Optional long parameter 'gno' is present but cannot be translated into a null value
due to being declared as a primitive type. Consider declaring it as object wrapper
for the corresponding primitive type.
-->
</div>
<div class="form-group">
<label>Title</label> <!--수정가능-->
<input type="text" class="form-control" name="title" th:value="${dto.title}">
</div>
<div class="form-group">
<label>Content</label> <!--수정가능-->
<textarea area class="form-control" rows="5" name="content">[[${dto.content}]]</textarea>
</div>
<div class="form-group">
<label>Writer</label> <!--수정불가-->
<input type="text" class="form-control" name="writer" th:value="${dto.writer}" readonly>
</div>
<div class="form-group">
<label>RegDate</label> <!--수정불가, name 속성도 제거. 화면 수정 자체도 불가하고 jpa에서 자동처리 할 것이기 때문-->
<input type="text" class="form-control"
th:value="${#temporals.format(dto.regDate, 'yyyy/MM/dd HH:mm:ss')}" readonly>
</div>
<div class="form-group">
<label>ModDate</label> <!--수정불가, name 속성도 제거. 화면 수정 자체도 불가하고 jpa에서 자동처리 할 것이기 때문-->
<input type="text" class="form-control"
th:value="${#temporals.format(dto.modDate, 'yyyy/MM/dd HH:mm:ss')}" readonly>
</div>
</form> <!-- form태그로 감싸기 -->
<!--수정/삭제는 form 태그의 action을 이용해서 처리할 수 있음. 이 부분은 추후 처리-->
<!-- 버튼 구분을 위해 class 속성에 구분용 단어를 첨자하였다. -->
<!--<button type="button" class="btn btn-primary">Modify</button>-->
<button type="button" class="btn btn-primary modifyBtn">Modify</button>
<!--<button type="button" class="btn btn-info">List</button>-->
<button type="button" class="btn btn-info listBtn">List</button>
<!--<button type="button" class="btn btn-danger">Remove</button>-->
<button type="button" class="btn btn-danger removeBtn">Remove</button>
</th:block>
</th:block>
</html>
3. 서비스 계층에서의 수정과 삭제
3-1. 서비스 인터페이스 수정 (수정/삭제 메서드 추가)
//GuestbookService.java (인터페이스)
package com.example.guestbook.service;
import com.example.guestbook.dto.GuestbookDTO;
import com.example.guestbook.dto.PageRequestDTO;
import com.example.guestbook.dto.PageResultDTO;
import com.example.guestbook.entity.Guestbook;
import org.springframework.stereotype.Service;
//@Service 어노테이션은 GuestbookImple에 적어준다.
public interface GuestbookService { //GuestbookImple 클래스에서 이 인터페이스를 상속한다.
Long register(GuestbookDTO dto); // GuestbookImpl 클래스에서 오버라이딩
//getList()메서드
PageResultDTO<GuestbookDTO, Guestbook> getList(PageRequestDTO requestDTO);
//dto에서 Entity로의 변환작업
default Guestbook dtoToEntity(GuestbookDTO dto) {
Guestbook entity = Guestbook.builder()
.gno(dto.getGno())
.title(dto.getTitle())
.content(dto.getContent())
.writer(dto.getWriter())
.build();
return entity;
}
//Entity에서 DTO로의 변환작업
default GuestbookDTO entityToDto(Guestbook entity) {
GuestbookDTO dto = GuestbookDTO.builder()
.gno(entity.getGno())
.title(entity.getTitle())
.content(entity.getContent())
.writer(entity.getWriter())
.regDate(entity.getRegDate()) //빼먹어서 추가함
.modDate(entity.getModDate()) //빼먹어서 추가함
.build();
return dto;
}
//게시글을 조회하는 메서드. 접근 제한자는 default이다. 생략되어 있다.
GuestbookDTO read(Long gno);
//삭제를 위한 메서드
void remove(Long gno);
//수정을 위한 메서드
void modify(GuestbookDTO dto);
}
/*
* default 메서드:
* 인터페이스의 실제 내용을 가지는 코드를 default라는 키워드로 생성 가능.
* default 메서드를 이용하면 기존에 추상 클래스를 통해 전달해야 하는 실제 코드를
* 인터페이스에 선언할 수 있음.
*
* '인터페이스 -> 추상클래스 -> 구현 클래스'의 형태에서 추상클래스를 생략하는 것이 가능해짐.
* */
3-2. 서비스 구현 클래스 수정 (오버라이딩 추가)
//GuestbookImpl.java
package com.example.guestbook.service;
import com.example.guestbook.dto.GuestbookDTO;
import com.example.guestbook.dto.PageRequestDTO;
import com.example.guestbook.dto.PageResultDTO;
import com.example.guestbook.entity.Guestbook;
import com.example.guestbook.repository.GuestbookRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import java.util.Optional;
import java.util.function.Function;
@Service //스프링이 빈으로 처리하도록 @Service 어노테이션 추가
@Log4j2
@RequiredArgsConstructor // <== 의존성 자동 주입!
public class GuestbookServiceImpl implements GuestbookService {
private final GuestbookRepository repository; //jpa 처리를 위해 repository 주입! 반드시 fianl 사용
@Override //GuestbookService에서 상속한 메서드 오버라이딩
public Long register(GuestbookDTO dto) {
log.info("DTO--------------------------------------");
log.info(dto);
Guestbook entity = dtoToEntity(dto); // GuestbookService에서 default를 이용하여 생성한 메서드
log.info(entity);
repository.save(entity); // 처리 저장.
// return null;
return entity.getGno(); //처리 후에는 엔티티의 gno 리턴
}
@Override
public PageResultDTO<GuestbookDTO, Guestbook> getList(PageRequestDTO requestedDTO){
Pageable pageable = requestedDTO.getPageable(Sort.by("gno").descending());
Page<Guestbook> result = repository.findAll(pageable);
Function<Guestbook, GuestbookDTO> fn = (entity -> entityToDto(entity));
return new PageResultDTO<>(result, fn);
}
//게시글 조회 메서드드
@Override
public GuestbookDTO read(Long gno) {
Optional<Guestbook> result = repository.findById(gno);
return result.isPresent() ? entityToDto(result.get()) : null;
//삼항연사자. GuestbookRepository에서 findById()를 통해 엔티티 객체를 가져온다면
// 이를 DTO로 바꾸어서 반환한다.
}
//게시물 삭제 메서드
@Override
public void remove(Long gno){
repository.deleteById(gno);
}
//게시물 수정 메서드
@Override
public void modify(GuestbookDTO dto){
//업데이트 항목은 '제목', '내용'
//Optional<T> //java.util
Optional<Guestbook> result = repository.findById(dto.getGno());
if(result.isPresent()){
Guestbook entity = result.get();
entity.changeTitle(dto.getTitle());
entity.changeContent(dto.getContent());
repository.save(entity);
}
}
}
Guestbook 게시글의 수정은 기존의 엔티티에서 'Title'과 'Content'만 수정하고 저장하는 방식으로 구현
4. 컨트롤러에 삭제를 처리할 메서드 추가
remove 메서드 추가. 삭제 후 목록으로 리다이렉트
//GuestbookController.java
package com.example.guestbook.controller;
import com.example.guestbook.dto.GuestbookDTO;
import com.example.guestbook.dto.PageRequestDTO;
import com.example.guestbook.dto.PageResultDTO;
import com.example.guestbook.service.GuestbookService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@Controller
@RequestMapping("/guestbook")
@Log4j2
@RequiredArgsConstructor //자동 주입을 위한 어노테이션
public class GuestbookController {
private final GuestbookService service; // final로 선언
@GetMapping("/")
public String index(){
return "redirect:/guestbook/list";
}
@GetMapping("/list")
public String list(PageRequestDTO pageRequestDTO, Model model){
log.info("list...................." + pageRequestDTO);
// model에 result를 key로 담아서 list 페이지에 뿌려준다.
model.addAttribute("result", service.getList(pageRequestDTO));
return "/guestbook/list";
}
/*
SpringDAta JPA를 이용하는 경우 @Pageable 어노테이션으로 Pageable 타입을 이용할 수도 있고,
application.properties에 0이 아닌 1부터 페이지 번호를 시작하도록 받을 수 있도록 처리할 수도 있다.
예제에서는 그냥 0부터 받는 방식을 사용하였다. 추후 검색조건 등과 같이 추가로 전달되어야 하는 데이터가
많을 경우 더욱 복잡해질 수 있기 때문이다.
*/
@GetMapping("/register")
public void register(){
log.info("register get...");
}
@PostMapping("/register")
public String registerPost(GuestbookDTO dto, RedirectAttributes redirectAttributes){
log.info("dto..." + dto);
//새로 추가된 엔티티의 번호
Long gno = service.register(dto);
redirectAttributes.addFlashAttribute("msg", gno);
return "redirect:/guestbook/list";
/*
* 등록 작업은 GET 방식에서는 화면을 보여주고 POST 방식에서는 처리 후에 목록페이지로 이동하도록 설계.
* 이 때 RedirectAttributes를 이용해서 한 번만 화면에서 'msg'라는 이름의 변수를 사용할 수 있도록 처리.
* addFlashAttribute()는 단 한 번만 데이터를 전달하는 용도로 사용함.
* --> 브라우저에 전달되는 'msg'를 이용해서 화면 창에 모달 창을 보여주는 용도로 사용.
* */
}
@GetMapping({"/read", "/modify"}) // modify도 추가하였다. 조회/수정을 위한 메서드
public void read(long gno, @ModelAttribute("requestDTO") PageRequestDTO requestDTO, Model model){
log.info("gno: " + gno);
GuestbookDTO dto = service.read(gno);
model.addAttribute("dto", dto);
}
@PostMapping("/remove") //삭제를 위한 메서드
public String remove(long gno, RedirectAttributes redirectAttributes) {
log.info("gno: " + gno);
service.remove(gno); //서비스 계층으로 전달
redirectAttributes.addFlashAttribute("msg", gno); //모달창 사용할 목적
return "redirect:/guestbook/list";
}
}
5. 뷰페이지의 삭제버튼 연동(modify.html 수정)
- 삭제 작업은 GET방식으로 수정 페이지에 들어가서 삭제 버튼을 클릭하는 방식으로 제작한다.
- modify.html에는 삭제 외에 다른 버튼도 있으므로 구분하기 위해 클래스 속성을 추가한다.
- Remove버튼을 눌렀을 때 컨트롤러로 POST방식을 통해 전달하도록 처리한다.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/basic :: setContent(~{this::content})}">
<th:block th:fragment="content">
<h1 class="mt-4">GuestBook Modify Page!!</h1> <!-- 제목 변경 -->
<form action="/guestbook/modify" method="post"> <!-- form태그로 감싸기 -->
<div class="form-group">
<label>Gno</label> <!--수정불가-->
<input type="text" class="form-control" name="gno" th:value="${dto.gno}" readonly>
<!--
name 속성은 DTO의 프로퍼티와 같게 해줘야 한다. DTO에서 소문자로 선언했으므로
여기서도 소문자 gno로 해야 한다. 대문자로 했다가 아래와 같은 에러를 만났다.
아래의 태그들의 속성 값도 전부 소문자로 해줄 것.
java.lang.IllegalStateException:
Optional long parameter 'gno' is present but cannot be translated into a null value
due to being declared as a primitive type. Consider declaring it as object wrapper
for the corresponding primitive type.
-->
</div>
<div class="form-group">
<label>Title</label> <!--수정가능-->
<input type="text" class="form-control" name="title" th:value="${dto.title}">
</div>
<div class="form-group">
<label>Content</label> <!--수정가능-->
<textarea area class="form-control" rows="5" name="content">[[${dto.content}]]</textarea>
</div>
<div class="form-group">
<label>Writer</label> <!--수정불가-->
<input type="text" class="form-control" name="writer" th:value="${dto.writer}" readonly>
</div>
<div class="form-group">
<label>RegDate</label> <!--수정불가, name 속성도 제거. 화면 수정 자체도 불가하고 jpa에서 자동처리 할 것이기 때문-->
<input type="text" class="form-control"
th:value="${#temporals.format(dto.regDate, 'yyyy/MM/dd HH:mm:ss')}" readonly>
</div>
<div class="form-group">
<label>ModDate</label> <!--수정불가, name 속성도 제거. 화면 수정 자체도 불가하고 jpa에서 자동처리 할 것이기 때문-->
<input type="text" class="form-control"
th:value="${#temporals.format(dto.modDate, 'yyyy/MM/dd HH:mm:ss')}" readonly>
</div>
</form> <!-- form태그로 감싸기 -->
<!--수정/삭제는 form 태그의 action을 이용해서 처리할 수 있음. 이 부분은 추후 처리-->
<!-- 버튼 구분을 위해 class 속성에 구분용 단어를 첨자하였다. -->
<!--<button type="button" class="btn btn-primary">Modify</button>-->
<button type="button" class="btn btn-primary modifyBtn">Modify</button>
<!--<button type="button" class="btn btn-info">List</button>-->
<button type="button" class="btn btn-info listBtn">List</button>
<!--<button type="button" class="btn btn-danger">Remove</button>-->
<button type="button" class="btn btn-danger removeBtn">Remove</button>
<script th:inline="javascript">
var actionForm = $("form"); //form 태그 객체
$(".removeBtn").click(function(){
//삭제 버튼 처리. 버튼을 누르면 form태그의 action 속성과 method 속성을 조정한다.
actionForm
.attr("action", "/guestbook/remove")
.attr("method", "post");
actionForm.submit();
})
</script>
</th:block>
</th:block>
</html>
확인
다음 글에선 수정을 다룬다.
'Framework > Spring' 카테고리의 다른 글
guestbook : 10. guestbook 게시글 수정/삭제 처리(3) (0) | 2022.07.21 |
---|---|
guestbook : 10. guestbook 게시글 수정/삭제 처리(2) (0) | 2022.07.21 |
guestbook : 09. guestbook 게시글 조회 처리 (0) | 2022.07.20 |
guestbook : 08. 등록페이지의 링크와 조회 페이지의 링크처리(1) (0) | 2022.07.19 |
guestbook : 07. 등록페이지와 등록처리 (2) (0) | 2022.07.17 |
Comments