티스토리 뷰

반응형



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 번 과정을 

반복해서 다시 진행해주면 된다.


사전 작업이 필요한 경우 아래 글을 참고해보자.





mybatis-spring Documentaion


| 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


header.jsp 파일도 <%@include 로 include 할 수 있다.


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








반응형
댓글
최근에 올라온 글
최근에 달린 댓글
링크
Total
Today
Yesterday