《 SW 아키텍쳐 : 전체를 아우르는 모델. 》 《 디자인 패턴 : 문제를 해결하기 위한 방법. 》
Model1,2 (SW아키텍쳐) ==> MVC패턴 ==> Layered 구조
: 소프트웨어 아키텍쳐인 Model 2 를 이용하여 SOLID의 (첫번째 S) 원칙(객체는 단 하나의 책임만 가져야 한다는 원칙) 수행한다. 이것을 수행하기 위해 MVC 패턴을 사용한다. Controller는 Client의 request를 Model에게 전달하고 자원을 확보하는 것이 가장 큰 역할이다. 자원을 확보하는 과정을 모델링이라고 하며 모델링 역할을 하는 곳은 Model 이다. Model은 데이터를 받아와서 가공하고 View는 그것을 Client가 보기 좋게 하여 UI를 제공한다. 이때, 각 C, M, V는 각자의 할 일만 알고 다른 계층에서 하는 일을 알지 못한다. ( = 결합도를 낮췄다.) 이렇게 되면 수정이 필요했을 때 여러 부분을 손대지 않고 단일의 책임을 가진 한 부분만 수정하여 재사용, 유지보수가 용이하다. 각 계층은 하나의 명령이 발생하면 명령에 대한 결과가 나갈 때 까지 순차적으로 동작하는 Layered 구조이다. 레이어드가 쪼개질수록 레이어드의 차수가 달라진다.
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함수로 만들어보자.
$.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);
데이터를 만들때 문자열이 아닌 객체로 만들면 그 객체의 구조를 활용할 수 있어 좋다.
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
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 : 프라미스 패턴 : 후에 어떤 일이 벌어질거다 알려주는 코드
'내가 보려고 정리하는 > Spring' 카테고리의 다른 글
웹 : exeption, 예외처리, custom Exception : 0314 보강 (0) | 2023.03.14 |
---|---|
웹 : SOLID원칙에 맞춰 5Layered Model2 방식 만들기 : 0314 (0) | 2023.03.14 |
웹 : 300번 응답코드 : 0309 (0) | 2023.03.09 |
웹 : 응답상태코드 : 0308 (0) | 2023.03.08 |
웹:0307 (0) | 2023.03.07 |