티스토리 뷰
Business Logic 구현을 위해
현업에서는 순서가 달라질 수 있지만,
기본적으로는
< Model >
1. DTO
2. REPO
3. MAPPER
4. TEST
5. SERVICE
6. TEST
< View >
7. View
< Controller >
8. Controller
9. Final Test
쿼리만 추가될 경우에는 3 ~ 8 번 과정을
반복해서 다시 진행해주면 된다.
사전 작업이 필요한 경우 아래 글을 참고해보자.
[Spring-myBatis] Spring-myBatis 프로젝트를 만들어보자 !
[Spring-myBatis] Spring-myBatis Business Logic 구현을 해보자!
| DTO
javaBeans는 아래와 같이 만들어져야 한다.
1. private member variable
2. default constructor, constructor
3. getter/setter
+. toString
| Repository
Repository 는 DAO 역할을 한다.
mybatis-spring 을 연동할 것이므로 Impl을 사용하지 않고,
repository interface만 사용할 것이다.
/src/main/java/com/cristoval/web/model/repo/ProdRepository.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | package com.cristoval.web.model.repo; import java.util.List; import com.cristoval.web.model.dto.Product; public interface ProdRepository { int insert(Product info); int update(Product info); int delete(String prodId); Product select(String prodId); List<Product> selectAll(); } | cs |
생성한 interface를 Mapper의 namespace에 등록해주어야 한다.
/src/main/resources/product.xml
1 2 3 4 5 6 7 | <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- namespace : package + classname --> <mapper namespace="com.cristoval.web.model.repo.ProdRepository"> </mapper> | cs |
또한, ApplicationConfig 에서 Mapper를 Scan 할 수 있도록
@MapperScan 을 추가해주자.
/src/main/java/com/cristoval/web/config/ApplicationConfig.java
1 2 3 4 5 6 7 8 9 | @Configuration @ComponentScan("com.cristoval.web.model") //@Repository인 Impl이 없어지고 대신 interface로 읽어서 등록 @MapperScan("com.cristoval.web.model.repo") public class ApplicationConfig { // ... } | cs |
| Mapper
Mapper에 메서드를 작성해보자.
우선, mybatis-config.xml 에 <typeAliases> <mappers> 를 등록해보자.
/src/main/resources/mybatis-config.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 사용하려는 DTO에 대한 축약 이름 --> <typeAliases> <typeAlias type="com.cristoval.web.model.dto.Product" alias="product" /> </typeAliases> <!-- 사용할 쿼리에 대한 등록 --> <mappers> <mapper resource="product.xml" /> </mappers> </configuration> | cs |
편의를 위해 완성된 코드를 바로 올렸지만,
실제로는 한꺼번에 작성하면 안되고,
쿼리별로 테스트를 해보는게 좋다.
/src/main/resource/product.xml
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 29 30 31 32 33 34 35 36 37 38 39 40 | <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- namespace : package + classname --> <mapper namespace="com.cristoval.web.model.repo.ProdRepository"> <!-- Product select(String prodId); --> <select id="select" parameterType="string" resultType="product"> select * from product where id = #{id} </select> <!-- List<Product> selectAll(); --> <select id="selectAll" resultType="product"> select * from product order by id </select> <!-- int insert(Product info); --> <insert id="insert" parameterType="product"> insert into product values(#{id}, #{name}, #{price}, #{descript}); </insert> <!-- int update(Product info); --> <update id="update" parameterType="product"> update product set name = #{name}, price = #{price}, descript = #{descript} where id = #{id} </update> <!-- int delete(String prodId); --> <delete id="delete" parameterType="string"> delete from product where id = #{id} </delete> </mapper> | cs |
***
추가로 참고하면 좋을 동적 쿼리를 살펴보자.
부등호 사용
1 2 3 4 5 6 7 | <select id="selectByGNP" resultType="Country" parameterType="map"> select * from country <!-- where GNP < #{gnp} --> <![CDATA[ where GNP < #{gnp} ]]> </select> | cs |
like 처리
1 2 3 4 | <select id="selectLikeName" resultMap="countryBase" parameterType="map"> select * from country where Name like concat('%',#{name},'%') </select> | cs |
| Test(Mapper)
단위 테스트의 자세한 설명은
아래 포스트를 참고해보자 !
[Spring] 단위 테스트(spring-test, JUnit)
/scr/test/java/com/cristoval/web/RepoTest.java
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | package com.cristoval.web; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.util.List; 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.SpringRunner; import org.springframework.transaction.annotation.Transactional; import com.cristoval.web.config.ApplicationConfig; import com.cristoval.web.model.dto.Product; import com.cristoval.web.model.repo.ProdRepository; // 단위테스트를 스프링과 연동 @RunWith(SpringRunner.class) // 환경설정 파일 명시 @ContextConfiguration(classes = ApplicationConfig.class) public class RepoTest { @Autowired ProdRepository pRepo; @Test public void beanTest() { // ProdRepository Bean이 성공적으로 Autowired 되었다 ! assertNotNull(pRepo); } @Test public void selectTest() { Product prod = pRepo.select("001"); // 001번 상품이 있다 ! assertNotNull(prod); // 001번 상품 정보는 아래와 같다 ! assertEquals(prod.getName(), "냉장고"); assertTrue(prod.getPrice() == 130000); // 111번 상품은 없다 ! prod = pRepo.select("111"); assertNull(prod); } @Test public void selectAllTest() { List<Product> prods = pRepo.selectAll(); // 등록된 모든 상품은 3개다 ! assertTrue(prods.size() == 3); } @Test @Transactional // 테스트 끝난 후 자동 rollback public void insertTest() { Product prod = new Product("111", "청소기", 500000, "다 빨아들인다~~"); int result = pRepo.insert(prod); // insert 성공했다 ! assertTrue(result == 1); Product selected = pRepo.select(prod.getId()); // DB에 잘 등록이 되었다 ! assertEquals(prod.getName(), selected.getName()); } @Test @Transactional public void updateTest() { Product selected = pRepo.select("001"); selected.setDescript("설명 수정본"); pRepo.update(selected); Product selected2 = pRepo.select("001"); // DB에 update가 잘 반영되었다 ! assertEquals(selected2.getDescript(), "설명 수정본"); } @Test @Transactional public void deleteTest() { pRepo.delete("001"); Product selected = pRepo.select("001"); // DB에 delete가 잘 반영되었다 ! assertNull(selected); } } | cs |
테스트는 성공적 !!
| Service
Service interface 와
Impl Class를 만들어보자.
/scr/main/java/com/cristoval/web/model/service/ProdService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | package com.cristoval.web.model.service; import java.util.List; import com.cristoval.web.model.dto.Product; public interface ProdService { List<Product> getList(); Product select(String id); int regist(Product prod); int update(Product prod); int remove(String id); } | cs |
Service Class는 @Service annotation로 Bean 등록을 해주어야 한다.
@Autowired로 spring Bean을 불러올 수 있다.
Service에서도 마찬가지로
@Transactional annotation을 사용하면
메서드 내에서 runtime Exception 발생 시
자동적으로 rollback 처리를 해주고,
성공 시 commit 처리를 해준다.
/scr/main/java/com/cristoval/web/model/service/ProdServiceImpl.java
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | package com.cristoval.web.model.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.cristoval.web.model.dto.Product; import com.cristoval.web.model.repo.ProdRepository; @Service public class ProdServiceImpl implements ProdService { @Autowired ProdRepository pRepo; @Override public List<Product> getList() { List<Product> prods = pRepo.selectAll(); return prods; } @Override public Product select(String id) { return pRepo.select(id); } @Override @Transactional // 메서드 내에서 runtime exception 발생 --> rollback, 아니면 commit public int regist(Product prod) { int result = pRepo.insert(prod); // 이 뒤에서 duplicate 예외 발생 가능 return result; } @Override @Transactional public int update(Product prod) { return pRepo.update(prod); } @Override @Transactional public int remove(String id) { return pRepo.delete(id); } } | cs |
| Test (Service)
Repository에서 했던 것과 동일하게
Service도 단위 테스트를 진행해본다.
/scr/test/java/com/cristoval/web/ServiceTest.java
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 29 30 31 32 33 34 35 36 37 38 39 | package com.cristoval.web; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; 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.SpringRunner; import com.cristoval.web.config.ApplicationConfig; import com.cristoval.web.model.dto.Product; import com.cristoval.web.model.service.ProdService; //단위테스트를 스프링과 연동 @RunWith(SpringRunner.class) //환경설정 파일 명시 @ContextConfiguration(classes = ApplicationConfig.class) public class ServiceTest { @Autowired ProdService service; @Test public void testBean() { assertNotNull(service); } @Test public void registTest() { Product info = service.select("001"); assertEquals(info.getName(), "냉장고"); info = service.select("111"); assertNull(info); } } | cs |
| View
View 는
테스트로 간단하게 만들어볼 수 있다.
css는 MVCConfig에서 아래와 같이
실제로는 /resource/ 경로에 파일이 있지만 /res/ 로 요청하겠다고 명시하였으므로
${root }/res/css/common.css 로 불러올 수 있다.
MVCConfig.java 파일의 일부
1 2 3 4 5 6 | @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/res/**") // 화면에서 요청하는 리소스 위치(사용자) .addResourceLocations("/resources/") // 실제로 그 리소스들이 있는 물리적 경로 .setCachePeriod(60 * 60 * 24 * 365); // cache로 남겨둘 기간 설정 } | cs |
src/main/webapp/WEB-INF/views/main.jsp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <c:set value="${pageContext.request.contextPath }" var="root" scope="session"></c:set> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> <link rel="stylesheet" href="${root }/res/css/common.css" > </head> <body> <%@include file="header.jsp" %> <h2>This is Main Page. </h1> <ul> <li><a href="${root }/product/regist">상품 등록 !</a> <li><a href="${root }/product/search">상품 검색 !</a> <li><a href="${root }/product/list">전체 상품 검색 !</a> <li><a href="${root }/product/update">상품 수정 !</a> <li><a href="${root }/product/remove">상품 삭제 !</a> </ul> </body> </html> | cs |
***
참고를 위한 View 파일을 첨부한다 !
***
src/main/webapp/WEB-INF/views/list.jsp
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 | <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <%@ include file="/WEB-INF/views/include/header.jsp" %> <h1>사용자 목록</h1> <table> <tr> <th>아이디</th> <th>이름</th> <th>비밀번호</th> </tr> <c:forEach items="${users }" var="user"> <tr> <td>${user.userId }</td> <td>${user.name }</td> <td>${user.password }</td> </tr> </c:forEach> </table> </body> </html> | cs |
src/main/webapp/WEB-INF/views/login.jsp
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 | <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <c:set value="${pageContext.request.contextPath }" var="root" /> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <c:if test="${!empty msg }"> <h2>${msg }</h2> </c:if> <c:if test="${!empty loginUser }"> <h1>이미 로그인 되어있어요.</h1> </c:if> <c:if test="${empty loginUser }"> <h1>login</h1> <form method="post" action="${root }/user/login"> <input type="text" name="userId"> <input type="password" name="password"> <input type="submit"> </form> </c:if> </body> </html> | cs |
src/main/webapp/WEB-INF/views/regist.jsp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <%@ include file="/WEB-INF/views/include/header.jsp" %> <h1>사용자 추가</h1> <form action="${root }/user/regist" method="post"> <input type="text" name="userId"> <input type="text" name="name"> <input type="text" name="password"> <input type="submit"> </form> </body> </html> | cs |
src/main/webapp/WEB-INF/views/update.jsp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <%@ include file="/WEB-INF/views/include/header.jsp" %> <h1>사용자 정보 수정</h1> <form action="${root }/user/update" method="post"> <input type="text" name="userId" value="${loginUser.userId }" readonly="readonly"> <input type="text" name="name" value="${loginUser.name }"> <input type="text" name="password" value="${loginUser.password }"> <input type="submit"> </form> <a href="${root }/user/leave">탈퇴</a> </body> </html> | cs |
| Controller
Controller 생성 시
MVCConfig 에서 생성한 Controller 를 제대로 Scan 할 수 있도록 명시해주어야 한다.
1 2 3 4 5 6 7 8 9 10 | @Configuration // <annotation-driven /> @EnableWebMvc // <context:component-scan base-package="com.cristoval.web" /> @ComponentScan("com.cristoval.web.controller") public class MVCComfig implements WebMvcConfigurer { // ... } | cs |
***
참고를 위한 Controller 파일
***
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | @Controller @RequestMapping("/user") public class UserController { @Autowired UserService service; @GetMapping("/login") public String loginForm() { return "login"; } @PostMapping("/login") public String doLogin(@ModelAttribute UserInfo user, HttpSession session, Model model) { UserInfo selected = service.login(user.getUserId(), user.getPassword()); if(selected != null) { // 로그인 성공 --> 세션에 정보를 담아주고 main으로 이동 session.setAttribute("loginUser", selected); return "redirect:/main"; } else { // 로그인 실패 --> 다시 로그인 유도 model.addAttribute("msg", "로그인 실패, 다시 시도해주세요."); return "login"; } } /* * Map 은 form name 을 알고 있어야 함. 오타 발생률이 높음 @PostMapping("/login") public String doLogin(@RequestParam Map<String, String> info) { UserInfo selected = service.login(info.get("userId"), info.get("password")); return } */ @GetMapping("/list") public String lsit(Model model) { List<UserInfo> list = service.getList(); model.addAttribute("users", list); return "list"; } @GetMapping("/logout") public String logout(HttpSession session) { session.invalidate(); return "redirect:/user/login"; } @GetMapping("/regist") public String registForm() { return "regist"; } @PostMapping("/regist") public String doRegist(@ModelAttribute UserInfo user) { int result = service.join(user); return "redirect:/user/list"; } @GetMapping("/update") public String updateForm() { return "update"; } @PostMapping("/update") public String doUpdate(@ModelAttribute UserInfo user, HttpSession session) { service.update(user); session.setAttribute("loginUser", service.select(user.getUserId())); return "redirect:/main"; } @GetMapping("/leave") public String leave(HttpSession session) { UserInfo user = (UserInfo) session.getAttribute("loginUser"); service.leave(user.getUserId()); // 삭제 성공 시 세션 삭제 session.invalidate(); return "redirect:/main"; } @ExceptionHandler // ExceptionHandler 메서드 (Model을 받을 수 없음) public ModelAndView exHandler(Exception e) { ModelAndView mv = new ModelAndView(); mv.addObject("error", e.getMessage()); mv.setViewName("error/500"); return mv; } } | cs |
** 읽어보고 싶은 참고 글 **
[Spring-myBatis] Spring-myBatis 프로젝트를 만들어보자 !
'Web > Spring' 카테고리의 다른 글
[Spring] Swagger를 이용한 REST API 문서화를 해보자! (0) | 2020.10.27 |
---|---|
[Spring] REST API(jackson-databind) (0) | 2020.10.26 |
[Spring] 단위 테스트(spring-test, JUnit) (0) | 2020.10.23 |
[Spring-myBatis] Spring-myBatis 프로젝트를 만들어보자 ! (0) | 2020.10.23 |
[MyBatis] MyBatis Setting 을 해보자 ! (4) | 2020.10.22 |