코드를 먼저 보시져
@PostMapping("/save")
public String memberAdd(@ModelAttribute Member member){
memberService.join(member);
log.info("Call MemberAdd memberId : {}",member.getId());
return "save-success";
}
가정된 상황과 똑같은 형태라고 보시면 될 것 같습니다!!
MemberService
Controller에서 비즈니스 로직을 호출하기 위한 Service 객체라고 보시면 될 것 같 습니다!
이해를 돕기 위해 코드를 보여 드리겠습니당
MemberRepository@Repository @RequiredArgsConstructor public class MemberRepository { private final EntityManager em; public void save(Member member){ em.persist(member); } public Member findById(Long memberId){ return em.find(Member.class,memberId); } public List<Member> findAll(){ return em.createQuery("select m from Member m",Member.class).getResultList(); } }
MemberService
public interface MemberService { public Long join(Member member); public Member findOne(Long memberId); public List<Member> findAll(); }
MemberServiceImpl
@Service @RequiredArgsConstructor @Transactional(readOnly = true) public class MemberServiceImpl implements MemberService{ private final MemberRepository memberRepository; @Transactional @Override public Long join(Member member) { memberRepository.save(member); return member.getId(); } @Override public Member findOne(Long memberId) { return memberRepository.findById(memberId); } @Override public List<Member> findAll() { return memberRepository.findAll(); } }
Member를 등록하고 나서 한번 새로고침을 5번 정도 해보겠습니다.
결과
어찌된 영문인지 똑같은 정보의 Member가 id만 다르게 저장이 되었습니다!
그럼 서버의 로그를 한번 보죠!
서버에서도 새로고침을 한 횟수 만큼 똑같은 Contoller가 5번 호출된 것을 볼 수 있습니다.
그럼 DB에는??
DB에서도 똑같이 ID만 다른 똑같은 정보가 새로 고침을 한 횟수 만큼 저장이 되었습니다.
바로 새로 고침에 있습니다!
새로 고침은 마지막에 한 행위를 다시 요청하는 일입니다.
즉, 웹브라우저에서는 멤버 저장이 완성되고 나서 서버로 응답을 받은 화면을 보고 있지만 마지막에 한 행위는 POST를 이용하여 Member를 저장하려고 하는 행위 입니다.
이런 이유로 새로고침을 할 때마다 계속해서 똑같은 정보를 저장하는 것이죠..
이것은 본질적인 문제로 볼 수 있습니다.
그렇다면 서버에서의 코드를 한번 보죠.
@PostMapping("/save")
public String memberAdd(@ModelAttribute Member member){
memberService.join(member);
log.info("Call MemberAdd memberId : {}",member.getId());
return "save-success";
}
이 코드였습니다. Controller에서 바로 View로 forward 하였기 때문에 어쩔 수 없이 웹브라우저가 마지막으로 한 요청이 지금 이 Controller에서의 행위이기 때문에! DB에 저장과 같은 행위의 요청 뒤 새로고침을 할 때 마다 문제가 생길 수 밖에 없는 거죠...
그러면 코드를 바꿔줘야 합니다... 어떻게 할까요???!!?!??!
바로 바로 redirect를 사용하는 것입니다.
redirect
리다이렉트는 실제 클라이언트에 응답이 나갔다가, 클라이언트가 redirect 경로로 다시 서버에 요청을 한다고 보면 됩니다.
코드를 바꿔 보아요~
@GetMapping("/{memberId}")
public String memberDetail(@PathVariable Long memberId,Model model){
Member member = memberService.findOne(memberId);
model.addAttribute("member",member);
return "save-success";
}
@PostMapping("/save")
public String memberAdd(@ModelAttribute Member member, RedirectAttributes redirectAttributes){
memberService.join(member);
log.info("Call MemberAdd memberId : {}",member.getId());
redirectAttributes.addAttribute("memberId",member.getId());
return "redirect:/spring-mvc/members/{memberId}";
}
코드에서 보시다 시피 redirect를 활용하여 멤버 저장 후 다시 멤버 상세 화면으로 이동하게 만들으시면 됩니다. 마지막에 호출한 내용이 @GetMapping("/{memberId}")이기 때문에 이후 새로 고침을 하더라도 멤버 상세 화면으로 요청이 가는 것이므로 새로 고침시 문제를 해결할 수 있습니다!!
지금 까지 알아본 이와 같은 패턴을 Post-Redirect-Get, PRG라고 부르게 됩니다!
이 글은 인프런 김영한님의 '스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술'을 수강하고 작성합니다.
출처:https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard