간단하게 Springboot 프로젝트를 생성하여 클라이언트의 값을 처리하는 3가지 방법을 알아보자.
SpringBoot 3.x, Java 11, Gradle, Mustache를 활용하여 작성해보겠다.
Springboot 프로젝트 생성
File -> New -> Other -> Spring Starter Project
선택영역의 이름은 보통 도메인명을 기입한다.
다음 화면에서는 모듈을 선택한다. 이전에 한 번 선택했던 모듈은 상단 빠른메뉴(?)에 나와있었다. 없다면 추가해주도록 하자.
- Spring Boot DevTools : 파일이 수정되었을때 서버를 재시작하지 않는다. (추천!!)
- Mustache : 스프링부트에서 공식적으로 지원하는 서버 템플릿 엔진.
- Spring Web : HTML, CSS 파일을 생성할 수 있게 해준다. 없어도 문제는 없지만 편의성을 높여준다
프로젝트를 생성후 프로젝트 구성을 보면, java 패키지에는 java 파일이 들어가며, templates 폴더에 .html 파일을, static에는 css, js, images 폴더 등을 작성해서 사용하면 된다.
사전준비(Application properties 설정)
Application properties파일에 각종 설정을 기입한다. 나는 아래의 설정을 기입했다.
# 서버의 포트번호를 9000으로 설정한다.
server.port=9000
# .mustache 확장자를 .html 확장자로 변환한다.
spring.mustache.suffix=.html
# 언어 설정
server.servlet.encoding.force=true
해당 파일은 저장시 팝업이 나오는데 UTF-8로 저장하겠다는 버튼을 눌러주자.
실습을 위해 java 패키지 아래에 controller, dao, dto, service 패키지를 추가해준다. 참고로 이러한 패키지들은 아래 사진과 같이 java.com.homework.myapp 패키지 아래에 위치해야 한다.
코드 작성
이제 본격적으로 시작해보자!
index와 연결해주기 위해 아래와 같이 controller 패키지 내부에 IndexController.java 파일을 추가해줬다.
package com.homework.myapp.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class IndexController {
@GetMapping("/")
public String index() {
return "index";
}
}
그리고 디비연결없이, 간단하게 사람들의 점수를 입력하고, 저장하고, 볼 수 있는 코드를 작성하겠다.
DTO 생성
각 필드는 직접 접근할수없도록 private로 선언, getter와 setter를 생성하였다.
Dto는 DB 테이블의 데이터를 담아두기 위한 용도로 사용된다.
package com.homework.myapp.dto;
public class ScoreDto {
private int idx;
private String name;
private int kor, eng, mat, total, avg;
public ScoreDto() {
super();
}
public ScoreDto(int idx, String name, int kor, int eng, int mat, int total, int avg) {
super();
this.idx = idx;
this.name = name;
this.kor = kor;
this.eng = eng;
this.mat = mat;
this.total = total;
this.avg = avg;
}
public int getIdx() {
return idx;
}
public void setIdx(int idx) {
this.idx = idx;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getKor() {
return kor;
}
public void setKor(int kor) {
this.kor = kor;
}
public int getEng() {
return eng;
}
public void setEng(int eng) {
this.eng = eng;
}
public int getMat() {
return mat;
}
public void setMat(int mat) {
this.mat = mat;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
public int getAvg() {
return avg;
}
public void setAvg(int avg) {
this.avg = avg;
}
}
Dao 생성
가급적 클래스간의 연결고리는 인터페이스로 연결한다.
보통 Dao는 테이블단위로 생성한다.
먼저 인터페이스를 작성한다.
package com.homework.myapp.dao;
import java.util.List;
import com.homework.myapp.dto.ScoreDto;
public interface ScoreDao {
List<ScoreDto> getList();
void insert(ScoreDto dto);
ScoreDto getView(int idx);
}
그리고 이를 구현한 구현체를 작성했다. 현재는 디비 연결을 안한 상태이므로, 몇가지의 데이터를 수기로 작성해줬다.
package com.homework.myapp.dao;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Repository;
import com.homework.myapp.dto.ScoreDto;
@Repository
public class ScoreDaoImpl implements ScoreDao {
List<ScoreDto> sList = new ArrayList<ScoreDto>();
public ScoreDaoImpl () {
sList.add(new ScoreDto(1, "홍길동", 70, 60, 50, 180, 60));
sList.add(new ScoreDto(2, "배성언", 70, 40, 40, 150, 50));
sList.add(new ScoreDto(3, "임기현", 80, 80, 50, 210, 70));
sList.add(new ScoreDto(4, "임꺽정", 90, 95, 85, 270, 90));
}
@Override
public List<ScoreDto> getList() {
return sList;
}
@Override
public void insert(ScoreDto dto) {
this.sList.add(dto);
}
@Override
public ScoreDto getView(int idx) {
return sList.get(idx - 1);
}
}
Service 작성
Service도 마찬가지로 인터페이스를 먼저 작성한다.
package com.homework.myapp.service;
import java.util.List;
import com.homework.myapp.dto.ScoreDto;
public interface ScoreService {
List<ScoreDto> getList();
void insert(ScoreDto dto);
ScoreDto getView(int idx);
}
그리고 이를 구현한 구현체를 작성했다.
package com.homework.myapp.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.homework.myapp.dao.ScoreDao;
import com.homework.myapp.dto.ScoreDto;
@Service
public class ScoreServiceImpl implements ScoreService{
@Autowired
ScoreDao dao;
@Override
public List<ScoreDto> getList() {
return dao.getList();
}
@Override
public void insert(ScoreDto dto) {
dao.insert(dto);
}
@Override
public ScoreDto getView(int idx) {
return dao.getView(idx);
}
}
이제 @Autowired 어노테이션을 활용해 객체를 의존주입해준다. @Autowired 어노테이션은 @Controller, @RestController, @Service, @Respository 클래스를 찾아 객체를 의존주입 해준다. (DI)
Controller 작성
package com.homework.myapp.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import com.homework.myapp.dto.ScoreDto;
import com.homework.myapp.service.ScoreService;
@Controller
public class ScoreController {
@Autowired
ScoreService service;
@GetMapping("/score/list")
public String score_list(Model model) {
model.addAttribute("sList", service.getList());
return "score_list";
}
@GetMapping("/score/write")
public String score_write() {
return "score_write";
}
@PostMapping("/score/save")
public String score_save(ScoreDto dto) {
dto.setTotal(dto.getKor() + dto.getEng() + dto.getMat());
dto.setAvg((dto.getKor() + dto.getEng() + dto.getMat()) / 3);
service.insert(dto);
return "redirect:/score/list";
}
@GetMapping("/score/view/{idx}")
public String score_view(Model model, @PathVariable int idx) {
model.addAttribute("detail", service.getView(idx));
return "/score_view";
}
}
나는 /score/view?idx=4 이런식의 url이 아니라 @PathVariable 어노테이션을 활용하여 /score/view/4 처럼 보이도록 작업해줬다.
HTML 문서 작성
이제 각각의 html 문서를 생성한다.
index는 그냥 가볍게 각 페이지로 이동할 수 있는 링크만 삽입했다.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<a href="/score/list">점수 목록</a>
<a href="/score/write">점수 쓰기</a>
</body>
</html>
score_list.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<table border="1" cellpadding="5" cellspacing="0">
<thead>
<tr>
<td>번호</td>
<td>이름</td>
<td>국어</td>
<td>영어</td>
<td>수학</td>
<td>총점</td>
<td>평균</td>
</tr>
</thead>
{{#sList}}
<tr>
<td>{{idx}}</td>
<td><a href="/score/view/{{idx}}">{{name}}</a></td>
<td>{{kor}}</td>
<td>{{eng}}</td>
<td>{{mat}}</td>
<td>{{total}}</td>
<td>{{avg}}</td>
</tr>
{{/sList}}
</table>
<a href="/score/write">점수 쓰기</a>
</body>
</html>
mustache의 반복문 구조는 특이하다. 이번에 새로 사용해봤는데 가독성이 좋아보인다. 반복을 시작할 시점에는 #을 붙이고, 끝날때에는 /를 활용해 닫아준다.
그리고 내부에서는 해당 객체 하나하나를 선택하므로 각 키를 입력해주면된다. jsp의 경우 sList.getIdx() 이런식으로 작성했겠지. jsp와 비교하여 가독성이 매우 좋아진거같다.
score_write. 폼에서 데이터를 보내는 3가지 방법.
javascript, jquery, ajax를 활용하여 데이터를 보내보도록한다. 체감상 가장 간결해보이는것은 jquery이다.
참고로 html의 속성은 스크립트를 통해 제어해줄 수 있다!
javascript를 활용한 처리
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script>
window.onload(() => {
let btnJs = document.getElementById("btnJs");
btnJs.addEventListener("click", () => {
document.scoreForm.submit();
});
});
</script>
</head>
<body>
<form id="scoreForm" name="scoreForm" action="/score/save" method="post">
<p>점수를 입력하세요.</p>
번호 : <input type="text" name="idx" /><br />
이름 : <input type="text" name="name" /><br />
국어점수 : <input type="text" name="kor" /><br />
영어점수 : <input type="text" name="eng" /><br />
수학점수 : <input type="text" name="mat" /><br />
<button type="button" id="btnJs">입력</button>
<button type="button" id="btnSubmit">입력</button>
</form>
</body>
</html>
jquery를 활용한 처리
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script>
$(() => {
$("#btnSubmit").click(() => {
$("#scoreForm").submit();
});
});
</script>
</head>
<body>
<form id="scoreForm" action="/score/save" method="post">
<p>점수를 입력하세요.</p>
번호 : <input type="text" name="idx" /><br />
이름 : <input type="text" name="name" /><br />
국어점수 : <input type="text" name="kor" /><br />
영어점수 : <input type="text" name="eng" /><br />
수학점수 : <input type="text" name="mat" /><br />
<button id="btnSubmit">제출</button>
</form>
</body>
</html>
ajax를 활용해 처리
ajax를 활용해 처리하려면 serialize() 함수를 이용한다. serialize() 함수는 폼의 데이터를 한번에 보낼 수 있게 도움을 주는 jquery의 함수이다. 또, 각 필드에 id를 추가해야한다. 또한, Controller에서 별도의 구문을 작성해준다.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script>
$(() => {
$("#btnSubmit").click(() => {
$("#scoreForm").submit();
});
$("#btnAjax").click(() => {
let data = $("#scoreForm").serialize();
console.log(data);
$.ajax({
url : "/score/save2",
data : data,
dataType:"json",
method: "POST"
})
.done((res)=> {
if (res.result == "success") {
alert("등록되었습니다.");
location.href="/score/list"
}
})
.fail((res, error, status) => {
console.log("Error");
})
})
});
</script>
</head>
<body>
<form id="scoreForm" name="scoreForm" action="/score/save" method="post">
<p>점수를 입력하세요.</p>
번호 : <input type="text" name="idx" id="idx" /><br />
이름 : <input type="text" name="name" id="name" /><br />
국어점수 : <input type="text" name="kor" id="kor" /><br />
영어점수 : <input type="text" name="eng" id="eng" /><br />
수학점수 : <input type="text" name="mat" id="mat" /><br />
<button type="button" id="btnJs">입력(JS)</button>
<button type="button" id="btnSubmit">입력</button>
<button type="button" id="btnAjax">입력(AJAX)</button>
</form>
</body>
</html>
@PostMapping("/score/save2")
@ResponseBody
public HashMap<String, Object> person_save2(ScoreDto dto) {
dto.setTotal(dto.getKor() + dto.getEng() + dto.getMat());
dto.setAvg((dto.getKor() + dto.getEng() + dto.getMat()) / 3);
service.insert(dto);
HashMap<String, Object> resultMap = new HashMap<String, Object>();
resultMap.put("result", "success");
resultMap.put("name", dto.getName());
resultMap.put("kor", dto.getKor());
resultMap.put("eng", dto.getEng());
resultMap.put("mat", dto.getMat());
resultMap.put("total", dto.getTotal());
resultMap.put("avg", dto.getAvg());
return resultMap;
}
ajax로 처리시에 기본적으로 text로 데이터가 넘어오므로 괜찮지만, axios나 fetch를 사용할 경우 메서드의 파라미터 앞에 @RequestBody 어노테이션을 붙여준다.
'TIL' 카테고리의 다른 글
TIL (0) | 2023.10.31 |
---|---|
TIL | DB Connection Pool, HikariCP (0) | 2023.10.30 |