내가 보려고 정리하는/Spring

웹 : (sw아키텍처/디자인패턴 루틴 시작__), include, page 모듈화, bootstrap : 0313

보동이용용 2023. 3. 13. 11:05
반응형

《  SW 아키텍쳐 : 전체를 아우르는 모델.  》   디자인 패턴 : 문제를 해결하기 위한 방법.  》

Model1,2 (SW아키텍쳐) ==> MVC패턴 ==> Layered 구조

: 소프트웨어 아키텍쳐인 Model 2 를 이용하여 SOLID의 (첫번째 S) 원칙(객체는 단 하나의 책임만 가져야 한다는 원칙)  수행한다. 이것을 수행하기 위해 MVC 패턴을 사용한다. Controller는 Client의 request를 Model에게 전달하고 자원을 확보하는 것이 가장 큰 역할이다. 자원을 확보하는 과정을 모델링이라고 하며 모델링 역할을 하는 곳은 Model 이다.  Model은 데이터를 받아와서 가공하고 View는 그것을 Client가 보기 좋게 하여 UI를 제공한다. 이때, 각 C, M, V는 각자의 할 일만 알고 다른 계층에서 하는 일을 알지 못한다. ( = 결합도를 낮췄다.) 이렇게 되면 수정이 필요했을 때 여러 부분을 손대지 않고 단일의 책임을 가진 한 부분만 수정하여 재사용, 유지보수가 용이하다.  각 계층은 하나의 명령이 발생하면 명령에 대한 결과가 나갈 때 까지 순차적으로 동작하는 Layered 구조이다. 레이어드가 쪼개질수록 레이어드의 차수가 달라진다.

0313 C/S 그림

DAO( : Data Access Object)는 말 그대로 데이터에 접근하는 역할을 하며 그 데이터의 대상이 DB일수도, File일수도 Properties 파일일수도 있다. 

VO(혹은 DTO : Data Transfer Object)는 레이어와 레이어 사이에서 데이터의 낱개를 의미하며 데이터를 자바객체로 사용할 수 있게 해주는 역할을 한다. 


SOLID에서 S는 결합도에 대한 내용, O, L, I, D는 응집도에 대한 내용이다.


 

🚩오늘은 ?

servlet의 method를 달리하여 properties 파일을 가지고 CRUD를 이어서 하면서...

  • include 를 해보자.
  • 이를 통해 페이지 모듈화를 해보자.
  • 더불어 Bootstrap 까지 
  • 오늘 사용하는 파일 : propertyView.jsp, PropertyControllerServlet.java, PropertyDAOImpl.java
  • 참고할 파일 : PropertyVO.java, JsonViewServlet.java, RequestBodyProcessor.java

《 반복되는 코드를 함수로 만들어주는 방법 》

$(this).find(":input[name]").each(function(idx, input){
		let propName = this.name;
		let propValue = $(this).val();
		data[propName] = propValue;
	});

Form의 data를 json형태로 받으려고 하다보니 반복되는 이 코드를 jquery함수로 만들어보자.

Chrom의 개발자 툴(F12) console 창에서 각각 jquery 함수 코드 어시스트를 받아보면...

$.fn  ==> fn에서 이용할 수 있는 함수들  ||   $.ajax ==>  jquery 자체의 함수들 

> 차이 : 어디에 바인딩을 할것인가? 

우리는 폼태그가 필요하기 때문에 fn을 잡고 쓴다.

//Form의 data를 객체로 만드는 코드
if($){
	$.fn.getObject=function(){
        // json형태로 하려면 객체가 필요해서 data를 객체로! (여기서는 모두 json으로 주고받아보기로 함)
        let data = {}; 
        //fn자체가 jquery함수라서 this를 $()로 묶을 필요가 없음
        this.find(":input[name]").each(function(idx, input){
            let propName = this.name;
            let propValue = $(this).val();
            data[propName] = propValue;
        });
        return data;
    }
}
// 플러그인하기 위한 코드
<script type="text/javascript" src="<%=request.getContextPath() %>/resources/js/packages/commons/custom.js"></script>

플러그인 까지 해주어야 완성


 insert하기 위한 Form 태그 

<form id="insertForm" method="post"
    action="<%=request.getContextPath()%>/props">
    <input type="text" name="propertyName" placeholder="property name" />
    <input type="text" name="propertyValue" placeholder="property value" />
    <input type="submit" value="저장" />
</form>

 

반복되는 순서다. 기억하자!

1. Form 태그를 객체로 받고, submit이벤트를 막는다.
2. 정보들을 꺼내고, 비동기 방식으로 요청을 보낸다.
3. 성공 결과를 받아와 가공하여 응답한다.
<script>

	//1.Form태그를 받아와서
    let insertForm = $("#insertForm").on("submit", function(event){
     	//event를 막는다.
     	event. preventDefault();
        //요청할 정보들을 꺼내고
        let url = this.action;
        let method = this.method;
        //data는 객체로 받아와서 json형식으로 마셜링한다. 
        let data = JSON.stringify( $(this).getObject() );
        
        //2.비동기로 요청을 받아오고
        $.ajax({
            url : url,
            method : method,
            contentType : "application/json;charset=UTF-8",
            data : data,
            dataType : "json"
        }).done(success) //익명함수 대신 석세스 펑션 객체를 넣는다.
        .fail(function(jqXHR, status, error) {
            console.log(`상태코드 : \${status}, 에러메세지 : \${error}`);
        });
        return false;
    });
    
    //성공했을 시, success function
    let success = function(resp, textStatus, jqXHR){
	if(jqXHR.responseJSON){
		if(resp.error){
			Swal.fire({
				  icon: 'error',
				  title: 'Oops...',
				  text: resp.message,
				  footer: '<a href="">Why do I have this issue?</a>'
				})
		}else if(resp.status && resp.location){
			loadBtn.trigger("click");
		}else{
        	// 성공했다면 tr태그를 생성한다.
			let trTags=[];
			list = resp.propertyList
			list.forEach(prop => { //람다식 활용
				let tr = $("<tr>").append(
								$("<td data-bs-toggle='modal' data-bs-target='#exampleModal'>").html(prop.propertyName)
								, $("<td>").html(prop.propertyValue)
								, $("<td>").html(
									$("<button>")
										.addClass("deleteBtn")
										.text("삭제")		
								)
							).data("source", prop);
				trTags.push(tr);
			});
			listBody.html(trTags);
			insertForm[0].reset();
			exampleModal.modal("hide");
		}
	}
}
</script>

 

DAO를 만들고, 

// class PropertyDAOImpl

// DataStore를 받을 properties를 생성하고
// 데이터가 있는 DataStore라는 properties파일을 객체로 생성하고
// loadData()에서 입력스트림으로 읽어들인다.
	private Properties properties;
	public PropertyDAOImpl() {
		properties = new Properties();
		loadData();
	}
	private void loadData() {
		try(
			InputStream is = this.getClass().getResourceAsStream("../DataStore.properties");
		){
			properties.load(is);
		}catch(IOException e) {
			throw new RuntimeException(e);
		}
	}
    
    // insert를 하고 storeData()메서드로 새로 넣은 내용을 저장한다.
    public int insertProperty(PropertyVO propertyVO) {
		properties.setProperty(propertyVO.getPropertyName(), propertyVO.getPropertyValue());
		storeData();
		return 1;
	}
    
    private void storeData() {
    	//properties파일의 상대경로를 가져와서 url을 얻어온다.
		URL url = this.getClass().getResource("../DataStore.properties");
		try {
        	//url로 uri를 찾아내고 그것으로 다시 path를 만들어낸다.
			Path path = Paths.get(url.toURI());
            //outputStream으로 정보를 내보내어 저장한다.
			try(
				OutputStream os = Files.newOutputStream(path);
			){
				properties.store(os, String.format("%s 에 저장함.", LocalDateTime.now()));
			}
		}catch(Exception e) {
			throw new RuntimeException(e);
		}
	}

Controller도 보자.

//dao를 먼저 객체로 생성하고
    private PropertyDAOImpl dao = new PropertyDAOImpl();

//메서드 상관없이 제일 먼저 실행되는 service에 인코딩 방식을 먼저 설정해준다.
	@Override
	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		req.setCharacterEncoding("UTF-8");
		super.service(req, resp);
	}
    
//insert는 내용을 가지고 오므로 body가 있는 post방식으로 보낸다.
//unMarshalling해줄 RequestBodyProcessor에서 언마샬링된 객체를 VO로 변수명 newProp으로 받아온다.
//그것을 dao의 insertProperty를 이용해 insert한다.
//properties파일에 갱신하는 것까지 dao에서 해준다.
//redirect방식으로 이동하는데 그러면 'PRG패턴'이 된다.
    @Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		PropertyVO newProp = RequestBodyProcessor.getContentFromRequestBody(req, PropertyVO.class); //언마샬링
		dao.insertProperty(newProp);
		resp.sendRedirect(req.getContextPath() + "/props");
	}

PRG 패턴 :  post로 요청하고 get으로 넘기는 방식으로 300번대 상태메세지를 생성하여 location정보를 가지고 list를 다시 불러올 수 있다. 


put과 delete는 redirect 하는 PRG 패턴이 잘 안들어간다. 

put/ delete 메서드에서는 307 요청을 보낸다.

(  307: 기존의 요청 내용을 그대로 발송하는 상태메세지 )

> 그렇다면 방법은...?

방법1. 서버사이드에서 doGet을 실행시키거나

방법2. 클라이언트 사이드에서 status(302)와 location(위치)을 설정해주거나.

req.setAttribute("status", 302);
req.setAttribute("location", req.getContextPath() + "/props");

브라우저의 매커니즘이 원하는대로 동작하지 않는다면 내가 원하는대로 새로 설정을 해줘버려도 된다.


//PRG패턴에 의해 실행되는 list를 받아오는 doGet메서드
    @Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		List<PropertyVO> propertyList = dao.selectProperties();
		req.setAttribute("propertyList", propertyList); 
		req.getRequestDispatcher("/jsonView.view").forward(req, resp);
	}
    
    
//DAO
//doGet메서드가 실행되면 DAO에서 리스트를 생성한것을 반환해준다.
//stream()을 이용하여 데이터들이 바이트 단위로 흘러가면 .map((e)->{...})으로 
//먹물 똑 떨어트려 오염시키듯이 
//정보를 물들인다.
//.collect(Collectors.toList())를 이용해서 물들여진 정보들을 list로 모아모아 수집해온다.
	public List<PropertyVO> selectProperties() {
		return properties.entrySet().stream()
					.map((e)->{
						PropertyVO propertyVO = new PropertyVO();
						propertyVO.setPropertyName(e.getKey().toString());
						propertyVO.setPropertyValue(e.getValue().toString());
						return propertyVO;
					}).collect(Collectors.toList());
	}


여긴 그냥 필기...

디센던트구조 (디센던트 : 자손)

: 대상을 클릭하면 그의 자손인 ".deleteBtn"에 함수를 줘라.

.on("click", ".deleteBtn", function(){

//success function 중 일부
	}else {
        let trTags=[];
        list = resp.propertyList
        list.forEach(prop => {
            let tr = $("<tr data-bs-toggle='modal' data-bs-target='#exampleModal'>").append(
                    $("<td>").html(prop.propertyName)
                    , $("<td>").html(prop.propertyValue)
                    , $("<td>").html(
                        $("<button>")
                            .addClass("deleteBtn")
                            .text("삭제")		
                    )
                ).data("source", prop);
            trTags.push(tr);
        });
        listBody.html(trTags);
    }


//Tbody인 data("source")를 받아와서 그 자체에 이벤트핸들링을 한다.
    let exampleModal = $("#exampleModal").on("show.bs.modal", function(event){
        let propTr = event.relatedTarget;
        let modifyProp = $(propTr).data("source");
        updateForm.find(":input[name]").each(function(idx, input){
            let propName = this.name;
            $(this).val( modifyProp[propName] ); 
        });
    }).on("hidden.bs.modal", function(){
        updateForm[0].reset();
    });

이벤트 버블링 구조 : tbody에서 버튼 클릭할거니까 tbody자체에 클릭이벤트를 준다. 

.data("source", prop);

데이터를 만들때 문자열이 아닌 객체로 만들면 그 객체의 구조를 활용할 수 있어 좋다. 


https://getbootstrap.com/

 

Bootstrap

Powerful, extensible, and feature-packed frontend toolkit. Build and customize with Sass, utilize prebuilt grid system and components, and bring projects to life with powerful JavaScript plugins.

getbootstrap.com

 

// 앞쪽에 include할 링크들
// preScript.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<link rel="stylesheet" href="<%=request.getContextPath()%>/resources/js/bootstrap-5.2.3-dist/css/bootstrap.min.css">
<script type="text/javascript" src="<%=request.getContextPath()%>/resources/js/sweetalert2/sweetalert2.all.min.js"></script>
<link rel="stylesheet" href="<%=request.getContextPath()%>/resources/js/sweetalert2/sweetalert2.min.css">
<script type="text/javascript" src="<%=request.getContextPath()%>/resources/js/jquery-3.6.3.min.js"></script>
// 뒤쪽에 include할 링크
//postScript.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
    <script type="text/javascript" src="<%=request.getContextPath()%>/resources/js/bootstrap-5.2.3-dist/js/bootstrap.bundle.min.js"></script>
<%@ 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>
<jsp:include page="/includee/preScript.jsp" />
</head>
<body>
			.
			.
			.
<script>
			.
			.
			.
</script>
<jsp:include page="/includee/postScript.jsp" />
</body>
</html>

<jsp:include page="/includee/preScript.jsp" />

                                   ⁞

<jsp:include page="/includee/postScript.jsp" />

 


≪  alert의 실수  ≫

: 비슷한 것 : confirm, prompt

자바 스크립트는 싱글 스레드로 진행된다.

alert가 실행되면 그 이후의 나올 동작들이 스레드를 사용할 수 없어서 멈춰있다. 정지!

alert를 닫아주기 전에는 모든 스레드가 스탑. 

대신해서 사용할 수 있는 것?

https://sweetalert2.github.io/

 

SweetAlert2

A beautiful, responsive, customizable and accessible (WAI-ARIA) replacement for JavaScript's popup boxes

sweetalert2.github.io

sweetAlert2 다운로드 방법

javascript의 장점을 백그라운드에서 쓸수 있게 나온 것 : nodejs (npm은 nodejs꺼..)

아래 jsdelivr CDN 주소로 이동하여 받아오기

 

코드 사용 예시

Swal.fire({
  title: 'Do you want to save the changes?',
  showDenyButton: true,
  showCancelButton: true,
  confirmButtonText: 'Save',
  denyButtonText: `Don't save`,
}).then((result) => {
  /* Read more about isConfirmed, isDenied below */
  if (result.isConfirmed) {
    Swal.fire('Saved!', '', 'success')
  } else if (result.isDenied) {
    Swal.fire('Changes are not saved', '', 'info')
  }
})

then : 프라미스 패턴 : 후에 어떤 일이 벌어질거다 알려주는 코드

 

 

 

 

 

 

 

 

 

 

 

반응형