목차
9. 비즈니스 계층
- 고객의 요구사항을 반영하는 계층.
- 프레젠테이션 계층과 영속 계층의 중간 다리 역할
- 로직을 기준으로 처리.
- ex) 쇼핑몰에서 상품을 구매 : Business- 구매 서비스 / Persistence- 상품 처리 객체, 회원 처리 객체 (포인트 적립)
9.1 비즈니스 계층의 설정
인터페이스 BoardService.java
package site.levinni.service;
import java.util.List;
import site.levinni.domain.BoardVO;
public interface BoardService {
void register (BoardVO boardVO);
BoardVO get(Long bno); // 상세 조회
boolean modify(BoardVO boardVO);
boolean remove(Long bno);
List<BoardVO> getList();
}
🔹 각 계증 간의 연결은 인터페이스를 이용해서 느슨한 연결을 함.
🔹 명백하게 반환해야 할 데이터가 있는 'select'를 해야 하는 메서드는 리턴 타입을 지정할 수 있음.
🔹 인터페이스 앞에만 접근제한자 public 쓰면 되고 인터페이스 블럭 안에는 public을 쓰지 않아도 자동으로 public으로 인식함.
BoardServiceImpl.java
package site.levinni.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j;
import site.levinni.domain.BoardVO;
import site.levinni.mapper.BoardMapper;
@Service
@Log4j
@AllArgsConstructor
public class BoardServiceImpl implements BoardService{
private BoardMapper mapper;
@Override
public void register(BoardVO boardVO) {
log.info("register...");
mapper.insertSelectKey(boardVO);
}
@Override
public BoardVO get(Long bno) {
// TODO Auto-generated method stub
return mapper.read(bno);
}
@Override
public boolean modify(BoardVO boardVO) {
// TODO Auto-generated method stub
return mapper.update(boardVO) > 0;
}
@Override
public boolean remove(Long bno) {
// TODO Auto-generated method stub
return mapper.delete(bno) > 0;
}
@Override
public List<BoardVO> getList() {
// TODO Auto-generated method stub
log.info("getList....");
return mapper.getList();
}
}
🔹 @Service : 계층 구조상 비즈니스 영역을 담당하는 객체임을 표시.
🔹 BoardServiceImpl이 동작하기 위해선 BoardMapper 객체가 필요하다.
🔹 @AutoWired를 쓸 수도 있지만, 스프링 4.3부터 단일 파라미터의 생성자가 있으면 자동으로 주입할 수 있으므로 @AllArgsConstructor로 대체
🎈 스프링의 서비스 객체 설정(root-context.xml)
<context:component-scan base-package="site.levinni.service"/>
9.2 비즈니스 계층의 구현과 테스트
BoardServiceTests.java
package site.levinni.service;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import lombok.extern.log4j.Log4j;
import site.levinni.domain.BoardVO;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class BoardServiceTests {
@Autowired
private BoardService service;
@Test
public void testExist() {
assertNotNull(service);
log.info(service);
}
@Test
public void testRegister() {
BoardVO boardVO = new BoardVO();
boardVO.setTitle("단위 테스트 작성 제목 in service");
boardVO.setContent("단위 테스트 작성 내용 in service");
boardVO.setWriter("newbie");
service.register(boardVO);
log.info(boardVO);
}
@Test
public void testGetList() {
service.getList().forEach(log::info);
}
@Test
public void testModify() {
BoardVO boardVO = new BoardVO();
boardVO.setTitle("수정 단위 테스트 작성 in service");
boardVO.setContent("수정 단위 테스트 작성 in service");
boardVO.setWriter("newbie");
boardVO.setBno(6L);
log.info(service.modify(boardVO));
}
@Test
public void testGet() {
log.info(service.get(1L));
}
@Test
public void testRemove() {
log.info(service.remove(85L));
}
}
🔹 테스트를 하기 전 먼저 BoardService가 존재하는지 부터 데스트 해봐야 함.
🔹 assertNotnull임!
🔹 글번호 타입이 Long이었으니깐 숫자 뒤에 L 붙여 주자.
10. 프레젠테이션(웹) 계층의 CRUD 구현
10.1 Controller의 작성
- 하나의 클래스 내에서 여러 메서드를 작성하고, @RequestMapping 등을 이용해 URL을 분기하는 구조로 작성할 수 있기 때문에 하나의 클래스에서 필요한 만큼 메서드의 분기를 이용하는 구조로 작성함.
- 이전에는 Tomcat(WAS)를 실행해서 웹 화면에서 결과를 확인했었는데 이 방법은 시간도 오래 걸리고, 테스트를 자동화 하기에 어려움.
🎈 BoardController의 분석
작성하기 전 원하는 기능을 호출하는 방식에 대해 정리하는 것이 좋음.
Task - URL - Method - Parameter - form - URL 이동
전체 목록 - /board/list - GET
등록 처리 - /board/register - POST - 모든 항목 - 입력화면 필요 - 이동
조회 - /board/read - GET - bno=123
삭제 처리 - /board/remove - bno - 입력화면 필요 - 이동
수정 처리 - /board/modify - 모든 항목 - 입력화면 필요 - 이동
10.2 BoardController의 작성
BoardController.java
package site.levinni.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j;
import site.levinni.domain.BoardVO;
import site.levinni.service.BoardService;
@Controller
@Log4j
@RequestMapping("/board/*")
@AllArgsConstructor
public class BoardController {
private BoardService service;
@GetMapping("/list")
public void list(Model model) {
log.info("list.....");
model.addAttribute("list", service.getList());
}
@GetMapping("get")
public void get(@RequestParam Long bno, Model model) {
log.info("get.....");
model.addAttribute("board", service.get(bno));
}
@GetMapping("register") //jsp 찾으려 함.
public void register() {
}
@PostMapping("register")
public String register(BoardVO boardVO, RedirectAttributes rttr) {
log.info("register...." + boardVO);
service.register(boardVO);
rttr.addFlashAttribute("result", boardVO.getBno());
return "redirect:/board/list";
}
@PostMapping("modify")
public String modify(BoardVO boardVO, RedirectAttributes rttr) {
log.info("modify....");
if(service.modify(boardVO)) {
rttr.addFlashAttribute("result", "success");
}
return "redirect:/board/list";
}
@PostMapping("remove")
public String remove(@RequestParam Long bno, RedirectAttributes rttr) {
log.info("remove....");
if(service.remove(bno)) {
rttr.addFlashAttribute("result", "success");
}
return "redirect:/board/list";
}
}
🔹 @Controller를 추가해서 스프링의 빈으로 이신할 수 있게 함.
🔹 @RequestMapping: '/board'로 시작하는 모든 처리를 BoardController가 하도록 지정.
🔹 BoardService 타입 객체와 같이 연동해야 하므로 의존성 처리도 해야 함. (@AllArgsConstructor 이용)
🔹 list()는 게시물의 목록을 전달해야 하므로 Model을 파라미터로 지정하고 이를 통해 BoardServiceImpl 객체의 getList() 결과를 담아 전달.
🔹 register(): 리턴 타입 String, 파라미터 RedirectAttributes를 통해 글 등록 작업이 끝난 후 목록 화면으로 다시 이동하고 새로운 게시물의 번호를 같이 전달. 리턴 시에는 redirect:
접두어 사용.
서블릿으로 했던 게시판 작업을 스프링으로 바꿀 때, 로그인 후 세션 처리는 세션 객체를 파라미터로 담으면 됨.
BoardControllerTests.java
package site.levinni.controller;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import lombok.extern.log4j.Log4j;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration({"file:src/main/webapp/WEB-INF/spring/root-context.xml",
"file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
@Log4j
public class BoardControllerTests {
@Autowired
private WebApplicationContext ctx;
private MockMvc mvc;
@Before
public void setup() {
mvc = MockMvcBuilders.webAppContextSetup(ctx).build();
}
@Test
public void testList() throws Exception {
log.info(mvc.perform(MockMvcRequestBuilders.get("/board/list"))
.andReturn()
.getModelAndView()
.getModelMap()
);
}
@Test
public void testGet() throws Exception {
log.info(mvc.perform(MockMvcRequestBuilders
.get("/board/get")
.param("bno", "88"))
.andReturn()
.getModelAndView()
.getModelMap());
}
@Test
public void testRegister() throws Exception {
log.info(mvc.perform(MockMvcRequestBuilders.post("/board/register")
.param("title", "컨트롤러 테스트 제목")
.param("content", "컨트롤러 테스트 내용")
.param("writer", "user00")
)
.andReturn()
.getModelAndView()
.getViewName());
}
@Test
public void testModify() throws Exception {
log.info(mvc.perform(MockMvcRequestBuilders.post("/board/modify")
.param("title", "컨트롤러 테스트 제목 수정")
.param("content", "컨트롤러 테스트 내용 수정")
.param("writer", "user00")
.param("bno", "88")
)
.andReturn()
.getModelAndView()
.getViewName());
}
@Test
public void testRemove() throws Exception {
log.info(mvc.perform(MockMvcRequestBuilders
.post("/board/remove")
.param("bno", "86"))
.andReturn()
.getModelAndView()
.getViewName());
}
}
🔹 @Before가 적용된 메서드는 모든 테스트 전에 매번 실행됨. (필요할 때마다 객체를 만들겠다는 뜻)
🔹 MockMvc: 가짜로 URL과 파라미터 등을, 브라우저에서 사용하는 것처럼 만들어서 Controller를 실행해 봄.
🔹 testList()는 MockMvcRequestBuilders
를 통해 GET 방식의 호출을 함.
🔹 반환된 결과를 이용해 Model에 어떤 데이터들이 담겨 있는지 확인함.
🔹 {}: 맵의 투스트링 부분
🔹 로그가 이전보다 긴 이유- 서블릿컨텍스트도 포함하기 때문에. (이전엔 root-context만 이용)
🔹testRegister() 로그 기록인데 register()의 리턴 값인 redirect:/board/list
문자열을 확인 할 수 있다.
11. 화면 처리 ✨
1.1 목록 페이지 작업과 includes
less 스크립트 기반
sass 루비 기반 - 더 빠름
w3school 부트스트랩 참고하기!
다운받은 부트스트랩에서 .html을 sts로 가져온 후 내용 복사해서 실행 브라우저에서 f12로 경로 하나하나 수정.
(5교시)
🧩(참고) 스프링 타일즈
헤더, 푸터, 사이드 바 등 페이지를 include 방식으로 나누는 기존 구조를 쉽게 적용하기 위한 템플릿 프레임워크.
장점: include 디자인을 변경하면 페이지를 전체적으로 수정해야 하는 번거로움을 없애고 일괄적인 페이지 관리를 가능하도록 함.
include 방식은 jsp가 jsp를 가져 오는 것인데, 타일즈를 사용하면 스프링에 있는 타일즈가 jsp를 관리하는 것.
텍스트 -> 이미지화 ex캡챠
갠플
부트스트랩 테마 + 약간의 양념
카카오로그인 ? 텍스트 에디터? 그 안에 파일첨부 ? 회원 간의 쪽지?
텍스트 마이닝, 워드 클라우드 (파이썬 자연어 처리)
✅ 재전송(redirect) 처리
post 방식에서 재전송 처리를 하지 않으면 사용자가 브라우저에서 '새로고침'을 통해 동일한 내용을 계속 서버에 등록할 수 있기 때문에 문제가 된다.
따라서, 등록·수정·삭제 작업은 처리가 완료된 후 다시 같은 내용을 전송할 수 없도록 아예 브라우저의 URL을 이동하는 방식을 이용한다. 이 때, 경고창이나 모달창을 이용해 브라우저에서 등록·수정·삭제의 결과를 알 수 있게 피드백을 줘야 한다.addFlashAttribute()
를 이용하면 일회성으로만 데이터를 사용할 수 있으므로 이를 이용해 경고창이나 모달창 등을 보여주는 방식으로 처리할 수 있다.
'스프링 Spring' 카테고리의 다른 글
21. 04. 12. (0) | 2021.04.13 |
---|---|
21. 04. 07. (0) | 2021.04.08 |
Part3. 기본적인 웹 게시물 관리 21. 04. 05. (0) | 2021.04.06 |
21. 04. 02. (0) | 2021.04.03 |
21. 04. 01. (0) | 2021.04.01 |