# 4주차 (개발 시작)

# 팀장님 공유해주신 기본 모듈 코드로 시작

팀장님께서 공유해주신 기본 구조가 짜여진 코드를 기반으로 개발을 시작했다. Spring MVC2 구조이지만 팀장님이 현업에서 일할 당시 같이 일했던 AA가 Spring framework를 변경하여서 Dao 부분이 기존의 방식과 달라서 VO, DTO 파일이 없이 mybatis(mapper)와 Model로만 구성되어 있었다.

# Mysql 설정 수정

스타트 코드 파일에서 src > main > webapp > WEB-INF > applicationContext.xml에서 MySQL IP addressDB name을 현재 사용할 정보로 수정

<bean id="mysqlDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://[IP address]:[PORT number]/[DB name]?allowMultiQueries=true" />
    <property name="username" value="[Mysql 계정 입력]" />
    <property name="password" value="[password 입력]" />
    <property name="validationQuery" value="select 1" />
</bean>

# 코드의 로직 설명

코드의 로직이 어떻게 돌아가는지 팀원들이 방황하고 있는 상황에서 하나의 페이지에서 돌아가는 로직을 분석해서 공유하면 좋을 것 같아서 간단하게 정리하여 팀원들에게 발표를 하였다. 나 또한 Spring을 처음 접하고 기본 개념에 대한 설명을 하진 않았고, 페이지를 완성하기에 필요한 내용들을 설명하였다.

MVC 구조에 맞게 우리가 가지고 있는 코드들은 크게 Model(*Model.java), View(*.jsp), Controller(*Controller.java, *Service.java, *ServiceImpl.java, *Dao.java, *Mapper.xml)으로 구성되어 있었다.

구조의 흐름을 다음과 같이 정의할 수 있었다.

# 로직 흐름

  1. 특정 urlrequest를 사용자가 보내면 Controller에서 @Controller@RequestMapping annotation을 사용해서 어떤 결과를 return할지 정할 수 있다.

    1. 특정 *.jsp 파일을 return해서 사용자에게 jsp파일에 작성한 결과물을 화면에 보여줄 수 있다.
    2. 변수 또는 객체(Hashmap)를 return해서 ajax를 활용할 수 있도록 할 수 있다.
  2. Controller에서 return한 결과가 JSP 파일이라면 해당 내용을 웹에서 확인할 수 있다. 처음부터 테이블(<tb>...</tb>)에 데이터를 보여주는 것이 아닌 빈 테이블 형식을 만들어 놓고 Ajax를 사용해서 데이터를 넣는 과정을 진행한다.

  3. Ajax에서는 다른 url 주소에 request를 보내고 Controller에서는 이를 감지하여 해당 url을 RequestMapping하고 있는 매소드로 가서 이를 실행한다.

  4. Service는 interface인 *Service.java파일과 해당 interface의 구현 코드가 작성된 *ServiceImpl.java 파일 두 개의 짝으로 구성되어 있다. Service가 처리하는 상세한 코드내용은 *ServiceImpl.java에 작성한다.

  5. ServiceImpl에서는 *Dao.java를 호출한다. Dao는 interface로 구현되어 있으며 Service와 다르게 *DaoImpl.java가 따로 존재하지 않는다. Dao는 mybatis와 연결되어 있으며 *Mapper.xml를 호출한다. (* 팀장님은 반드시 파일명을 *Mapper.xml로 작성해야 코드가 실행되도록 되어있다고 말씀해주셨다. AA가 그렇게 프레임워크를 구성했다고 한다.)

  6. Mapper 파일에는 DB에서 가져올 데이터를 위한 SQL query문이 작성되어 있다. Query에 해당하는 데이터를 받아와서 *Model.java에 저장한다.

  7. Model에는 DB에서 받아올 데이터를 Field로 선언해놓고 각 변수의 Getter/Setter를 생성하여 데이터의 저장과 읽기를 가능하게 한다.

  8. Model에 저장된 결과를 앞의 순차적으로 내려온 로직에 반대 방향으로 return하여 Controller까지 돌아간다. (Model -> Dao -> Service -> Controller)

  9. Controller의 method의 인자(arguments) 중에서 Model 객체가 들어있는데 여기에 *Model.java에서 받아 온 결과를 추가하여 해당 데이터를 보내고자 하는 JSP를 return하면 JSP에 해당 데이터를 사용할 수 있게 된다.

# Controller


@Controller // Controller Annotaion
@RequestMapping("/scm") // RequestMapping Annotation
public class ScmOrderListController {
  // 실행되고 있는 class 체크
  private final Logger logger = LogManager.getLogger(this.getClass());
  // Get class name for this class name
  private final String className = this.getClass().toString();

  @Autowired  // Service.java 파일을 추적해서 사용할 수 있게 해줌
  ScmOrderListService scmOrderListService;

  // ...

  @RequestMapping("productInfo.do")
  public String productInfo(Model model, @RequestParam Map<String, Object> paramMap,
    HttpServletRequest request, HttpServletResponse response, HttpSession session) throws Exception {

    logger.info("+ Start " + className + ".productInfo");

    paramMap.put("loginId", session.getAttribute("loginId"));
    logger.info("paramMap:" + paramMap);

    ProductInfoModel productInfo = scmOrderListService.getProductInfo(paramMap);
    String scmManager = scmOrderListService.getScmManagerName(paramMap);
    productInfo.setScmManager(scmManager);

    model.addAttribute("productInfo", productInfo);

    logger.info("+ end " + className + ".productInfo");

    return "/scm/productInfo";
  }
}
  • @Controller annotation을 사용해서 해당 class가 Controller의 역할을 할 수 있도록 설정
  • @RequestMapping annotation을 사용해서 어떤 url에 반응할 것인지 설정, RequestMapping은 상위, 하위 url로 분리하여 사용 가능
  • log4j 라이브러리를 사용해서 log 추적 기능 활용
  • Controller 내부의 method들은 아래의 5가지 객체타입의 인자들을 가지고 있음
    1. Model: Model.java에 저장한 데이터를 JSP에 보낼 때 활용됨
    2. @RequestParam Map<String, Object>: JSP에서 Controller에 보내는 데이터들을 담고 있음, JSP에서 submit으로 보내어지는 값들이 아닌 직접 JS에서 만든 object 타입의 값을 넘겨받음. 이 값은 Service, Dao, Mapper(mybatis)에서 순차적으로 넘겨받아서 SQL에서 변수값으로 활용됨
    3. HttpServletRequest
    4. HttpServletResponse
    5. HttpSession

# Service

public interface ScmOrderListService {

	List<ScmOrderListModel> getOrderList(Map<String, Object> paramMap) throws Exception;

  // ...

	ProductInfoModel getProductInfo(Map<String, Object> paramMap) throws Exception;

  // ...
}
  • Service에서 사용할 method만 정의하는 interface 부분

# ServiceImpl

@Service
public class ScmOrderListServiceImpl implements ScmOrderListService {

  @Autowired
  ScmOrderListDao scmOrderListDao;

  // ...

  @Override
  public ProductInfoModel getProductInfo(Map<String, Object> paramMap) throws Exception {
    return scmOrderListDao.selectProductInfo(paramMap);
  }

  // ...
}

  • Service interface 구현 코드
  • @Service annotation을 적용한다.
  • *Service.javaimplements하여 해당 interface의 구현 코드를 작성한다.
  • @Autowired를 사용하여 해당 Service와 연결시킬 *Dao.java을 찾는다.
  • @Override를 사용하여 *Service.java의 어떤 method를 구현할지를 정하고 코드를 작성한다.

# Dao

public interface ScmOrderListDao {
	List<ScmOrderListModel> selectOrderList(Map<String, Object> paramMap) throws Exception;

  // ...

	ProductInfoModel selectProductInfo(Map<String, Object> paramMap) throws Exception;

	// ...
}
  • *Dao.java은 interface로 구현되어 있다.
  • 각 interface의 구현 코드는 *Mapper.xml에 작성되어 있음.

# Mapper

<?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">
<mapper namespace="kr.happyjob.study.scm.dao.ScmOrderListDao">
  <!-- ... -->

  <!-- 발주버튼 클릭 후 나타나는 발주지시서 모달창에 넣을 데이터 -->
  <select id="selectProductInfo" resultType="kr.happyjob.study.scm.model.ProductInfoModel">
    SELECT o.order_cd orderCode,
            o.order_date orderDate,
            o.product_cd productCode,
            NOW() submitDate,
            prod.prod_nm productName,
            mct.m_ct_nm middleCategory,
            prod.stock productCount,
            o.order_cnt orderCount,
            prod.purchase_price purchasePrice,
            prod.supply_cd supplyCode,
            sup.supply_nm supplyName
    FROM tb_order o
    LEFT JOIN tb_scm_product prod ON prod.product_cd = o.product_cd
    LEFT JOIN tb_scm_m_category mct ON mct.m_ct_cd = prod.m_ct_cd
    LEFT JOIN tb_scm_supply sup ON sup.supply_cd = prod.supply_cd
    WHERE o.order_cd = #{orderCode}
  </select>

  <!-- 발주지시서 담당SCM 이름 가져오기 -->
  <select id="selectScmManagerName" resultType="String">
    SELECT name
    FROM tb_userinfo
    WHERE loginId = #{loginId}
  </select>

  <!-- ... -->
</mapper>
  • <!DOCTYPE mapper ...>를 작성하여 mapper 역할을 하는 xml 파일임을 정의한다.
  • <mapper namespace="{*Dao.java 파일의 위치 및 파일명}">을 작성하여 어떤 Dao 파일을 mapping할 것인지 설정한다.
  • <select id="{interface에 존재하는 method명}" resultType="{Model 위치 및 파일명}">을 작성하여 Dao interface의 어떤 method에 연결할 것인지와 SQL의 결과데이터를 어떤 Model에 담을 것인지를 설정한다.
  • 두 번째 <select> 태그처럼 결과값을 Model에 넣을 것이 아닌 값을 반환한다면 resultType에 값의 타입을 작성한다.

# Model

public class ProductInfoModel {
	/*
	 발주지시서에 나타날 데이터 목록
	 SCM관리자
	 주문번호
	 제출일자 (SQL에서 NOW()함수 사용하기)
	 제품명
	 품목명
	 총 재고 개수
	 고객 주문 개수
	 장비구매액
	 (제품 수량) jsp 단에서 input으로 갯수를 받을 것
	 (합계) jsp 단에서 장비구매액 * 제품수량을 계산해서 받을 것
	 */

	private String scmManager;
	private String orderCode;
	private String orderDate;
	private String submitDate;
	private String productName;
	private String middleCategory;
	private int productCount;
	private int orderCount;
	private int purchasePrice;
	private String supplyCode;
	private String productCode;
	private String supplyName;

	public String getScmManager() {
		return scmManager;
	}

	public void setScmManager(String scmManager) {
		this.scmManager = scmManager;
	}

	public String getOrderCode() {
		return orderCode;
	}

	public void setOrderCode(String orderCode) {
		this.orderCode = orderCode;
	}

	public String getOrderDate() {
		return orderDate;
	}

	public void setOrderDate(String orderDate) {
		this.orderDate = orderDate;
	}

	public String getSubmitDate() {
		return submitDate;
	}

	public void setSubmitDate(String submitDate) {
		this.submitDate = submitDate;
	}

	public String getProductName() {
		return productName;
	}

	public void setProductName(String productName) {
		this.productName = productName;
	}

	public String getMiddleCategory() {
		return middleCategory;
	}

	public void setMiddleCategory(String middleCategory) {
		this.middleCategory = middleCategory;
	}

	public int getProductCount() {
		return productCount;
	}

	public void setProductCount(int productCount) {
		this.productCount = productCount;
	}

	public int getorderCount() {
		return orderCount;
	}

	public void setorderCount(int orderCount) {
		this.orderCount = orderCount;
	}

	public int getpurchasePrice() {
		return purchasePrice;
	}

	public void setpurchasePrice(int purchasePrice) {
		this.purchasePrice = purchasePrice;
	}

	public String getSupplyCode() {
		return supplyCode;
	}

	public void setSupplyCode(String supplyCode) {
		this.supplyCode = supplyCode;
	}

	public String getProductCode() {
		return productCode;
	}

	public void setProductCode(String productCode) {
		this.productCode = productCode;
	}

	public String getSupplyName() {
		return supplyName;
	}

	public void setSupplyName(String supplyName) {
		this.supplyName = supplyName;
	}

}

# View

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!-- 데이터가 존재하지 않는 경우 -->
<c:if test="${empty productInfo}">
	<h1>데이터가 존재하지 않습니다.</h1>
</c:if>
<c:if test="${!empty productInfo}">
	<form name="purchaseDirectionInfo" id="purchaseDirectionInfo">
		<input type="hidden" name="orderCode" value="${productInfo.orderCode}">
		<input type="hidden" name="submitDate" value="${productInfo.submitDate}">
		<input type="hidden" name="productCode" value="${productInfo.productCode}">
		<input type="hidden" name="supplyCode" value="${productInfo.supplyCode}">
		<input type="hidden" name="purchaseCount" value="">
		<input type="hidden" name="purchasePrice" value="${productInfo.purchasePrice}">
		<input type="hidden" name="totalAmount" value="">
		<input type="hidden" name="STTcd" value="9">
	</form>
	<h1>발주 지시서</h1>
	<div class="prodInfoContainer">
		<div>
			<span>SCM관리자 : </span><span>${productInfo.scmManager}</span>
		</div>
		<div>
			<span>주문 번호 : </span><span>${productInfo.orderCode}</span>
		</div>
		<div>
	        <c:set var="orderDate" value="${productInfo.orderDate}"/>
			<span>주문 일자 : </span><span>${fn:substring(orderDate, 0, 11)}</span>
		</div>
		<div>
			<c:set var="submitDate" value="${productInfo.submitDate}"/>
			<span>제출 일자 : </span><span>${fn:substring(submitDate, 0, 11)}</span>
		</div>
		<div>
			<span>제품명 : </span><span>${productInfo.productName}</span>
		</div>
		<div>
			<span>상호명 : </span><span>${productInfo.middleCategory}</span>
		</div>
		<div>
			<span>공급처명 : </span><span>${productInfo.supplyName}</span>
		</div>
		<div>
			<span>총 재고 개수 : </span><span>${productInfo.productCount}</span>
		</div>
		<div>
			<span>고객 주문 개수 : </span><span>${productInfo.orderCount}</span>
		</div>
		<div>
			<span>제품 단가 : </span>
			<span id="purchasePrice">
				<input type="hidden" name="hidden_purchasePrice" value="${productInfo.purchasePrice}"/>
				<fmt:formatNumber value="${productInfo.purchasePrice}" pattern="#,###"/></span>
		</div>
		<div>
			<span>수량 : </span>
			<span><input id="purchaseCount"
					 	 type="number"
					 	 oninput="javascript:purchaseMultiply()"
					 	 value="${productInfo.orderCount - productInfo.productCount}"
					 	 min="${productInfo.orderCount - productInfo.productCount}"
					 	 max="9999"/> EA
	 	    </span>
		</div>
		<div class="bar"></div>
		<div>
			<span id="sumAmt" style="float: right;"></span>
			<br>
			<span id="sumAmt-han" style="float: right;">&nbsp</span>
		</div>
	</div>
</c:if>
  • Controller에서 넘겨 받은 model 객체의 정보들을 JSP에서 활용할 수 있다.
Last Updated: 8/4/2021, 1:21:06 AM