▶ 복습
→ Controller - Service - Repository 3 계층이 존재
→ update는 Service에서 진행된다.
▶ API 설계하기 (CRUD)
→ 타임라인 API
기능
|
Method
|
URL
|
Return
|
메모 생성하기
|
POST
|
/api/memos
|
Memo
|
메모 조회하기
|
GET
|
/api/memos
|
List<Memo>
|
메모 변경하기
|
PUT
|
/api/memeos/{id}
|
Long
|
메모 삭제하기
|
DELETE
|
/api/memos/{id}
|
Long
|
▶ Memo 클래스 만들기
→ 메모는 1)익명의 작성자 이름(username), 2)메모 내용(contents)으로 이루어져있다.
→ domain 패키지 생성 후 파일 생성
→ memo.java
package com.sparta.week03.domain;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@NoArgsConstructor //기본생성자를 만듭니다.
@Getter
@Entity // 테이블과 연계됨을 스프링에게 알려줍니다.
public class Memo extends Timestamped { // 생성,수정 시간을 자동으로 만들어줍니다.
@GeneratedValue(strategy = GenerationType.AUTO)
@Id
private Long id;
@Column(nullable = false)
private String username;
@Column(nullable = false)
private String contents;
public Memo(String username, String contents) {
this.username = username;
this.contents = contents;
}
public Memo(MemoRequestDto requestDto) {
this.username = requestDto.getUsername();
this.contents = requestDto.getContents();
}
public void update(MemoRequestDto requestDto){
this.username = requestDto.getUsername();
this.contents = requestDto.getContents();
}
}
→ Timestamped.java
package com.sparta.week03.domain;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;
@Getter //이게 없으면 db에 저장 안됨
@MappedSuperclass // Entity가 자동으로 컬럼으로 인식합니다.
@EntityListeners(AuditingEntityListener.class) // 생성/변경 시간을 자동으로 업데이트합니다.
public abstract class Timestamped {
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime modifiedAt;
}
→ MemoRepository.java
package com.sparta.week03.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import java.time.LocalDateTime;
import java.util.List;
public interface MemoRepository extends JpaRepository<Memo, Long> {
List<Memo> findAllByOrderByModifiedAtDesc();
//수정된 날짜를 기준으로 내림차순으로 정렬하고, 찾아줘줘
}
▶ JPA 공식 홈페이지
→ Spring Data JPA - Reference Documentation
▶ 연습퀴즈 - MemoRequestDto 클래스 만들기
→ MemoRequestDto.java
package com.sparta.week03.domain;
import lombok.Getter;
@Getter
public class MemoRequestDto {
private String username;
private String contents;
}
▶ MemoService 만들기 : update 역할
→ src > main > java > com.sparta.week03 에 service 패키지 생성
→ MemoService.java 생성
package com.sparta.week03.service;
import com.sparta.week03.domain.Memo;
import com.sparta.week03.domain.MemoRepository;
import com.sparta.week03.domain.MemoRequestDto;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
@RequiredArgsConstructor
@Service
public class MemoService {
private final MemoRepository memoRepository; //final 선언 중요!
@Transactional
public Long update(Long id, MemoRequestDto requestDto) { //아이디와 변경시킬내용
Memo memo = memoRepository.findById(id).orElseThrow(
() -> new IllegalArgumentException("아이디가 존재하지 않습니다.")
);
memo.update(requestDto);
return memo.getId();
}
}
→ update 기능 만들기: Memo.java에 update 메소드 추가하기
public void update(MemoRequestDto requestDto){
this.username = requestDto.getUsername();
this.contents = requestDto.getContents();
}
▶ MemoController.java 만들기
→ src > main > java > com.sparta.week03 에 controller 패키지 생성
→ MemoController.java 시작
@RequiredArgsConstructor//요청이 들어올 때 스프링이 알아서 해줌
@RestController
public class MemoController {
//생성,조회,변경,삭제
private final MemoRepository memoRepository;
private final MemoService memoService;
}
→ Create
@PostMapping("api/memos")
public Memo createMemo(@RequestBody MemoRequestDto requestDto){
Memo memo = new Memo(requestDto);
return memoRepository.save(memo);
}
→ Read
@GetMapping("api/memos")
public List<Memo> readMemo(){
LocalDateTime now = LocalDateTime.now();
LocalDateTime oneDayBefore = LocalDateTime.now().minusDays(1);
return memoRepository.findAllByModifiedAtBetweenOrderByModifiedAtDesc(oneDayBefore, now);
}
→ Delete
@DeleteMapping("api/memos/{id}")
public Long deleteMemo(@PathVariable Long id){ //PateVariavle: 경로에 있는 것을 가져온다
memoRepository.deleteById(id);
return id;
}
→ ⭐update⭐
@PutMapping("api/memos/{id}")
public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto){
memoService.update(id, requestDto);
return id;
}
▶ HTML, CSS, Javascript의 구분
→ HTML은 뼈대고, CSS는 꾸며주는 녀석이다.
여는 태그, 닫는 태그가 쌍으로 존재
→ 교차가 불가능
(O) <h1><span>타이틀</span>입니다.</h1>
(X) <span><h1>타이틀</span>입니다.</h1>
→ head, body 태크로 구분된다.
→ head 태그는 CSS, Javascript 코드를 포함하고, body 태그는 뼈대 전체를 포함한다.
▶ HTML의 기초
◾ 대표적인 HTML 태그들
· h1~h6 → headline의 약자, 신문 제목 같은 녀석들, 글자의 크기
· div → division, 나누는 녀석이다. 투명 비닐봉투!
· p → paragraph, 신문 기사 단락과 같은 텍스트 내용을 담는다.
· ul, ol, li → (un)ordered list, list에 해당한다. bullet point
· span → 글을 중간중간 잘라내서 색을 입혀준다든지 할 때 사용한다.
· table, th, tr, td → 표 이다! 엑셀 같은 표를 그릴 때 사용한다.
· img → 이미지를 나타낼 때 사용한다.
◾ 공부하기 좋은 HTML 강의 들
코드카데리 HTML : Learn HTML | Codecademy
생활코딩 HTML : HTML 수업 - 생활코딩 (opentutorials.org)
▶ CSS의 기초
→ 문법:
→ head > style 태그 안에 작성한다.
→ 세미콜론(;)으로 마무리한다.
wrap {
width: 538px;
margin: 10px auto;
}
#contents {
width: 538px;
}
.area-write {
position: relative;
width: 538px;
}
◾ 선택자(id, class)
→ 꾸미려면 가리켜야 겠지? 가리키는 방법은 두 가지가 있다.
→ id: HTML 파일을 통틀어 단 하나만 존재한다. #으로 표시한다. (#contents)
→ class: 중복 적용이 가능하다. (.)으로 표시한다. (.area-write)
▶ index.html
→src > main > resources > static 에 index.html 파일 생성
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Timeline Service</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@500&display=swap" rel="stylesheet">
<style>
@import url(//spoqa.github.io/spoqa-han-sans/css/SpoqaHanSans-kr.css);
body {
margin: 0px;
}
.area-edit {
display: none;
}
.wrap {
width: 538px;
margin: 10px auto;
}
#contents {
width: 538px;
}
.area-write {
position: relative;
width: 538px;
}
.area-write img {
cursor: pointer;
position: absolute;
width: 22.2px;
height: 18.7px;
bottom: 15px;
right: 17px;
}
.background-header {
position: fixed;
z-index: -1;
top: 0px;
width: 100%;
height: 428px;
background-color: #339af0;
}
.background-body {
position: fixed;
z-index: -1;
top: 428px;
height: 100%;
width: 100%;
background-color: #dee2e6;
}
.header {
margin-top: 50px;
}
.header h2 {
/*font-family: 'Noto Sans KR', sans-serif;*/
height: 33px;
font-size: 42px;
font-weight: 500;
font-stretch: normal;
font-style: normal;
line-height: 0.79;
letter-spacing: -0.5px;
text-align: center;
color: #ffffff;
}
.header p {
margin: 40px auto;
width: 217px;
height: 48px;
font-family: 'Noto Sans KR', sans-serif;
font-size: 16px;
font-weight: 500;
font-stretch: normal;
font-style: normal;
line-height: 1.5;
letter-spacing: -1.12px;
text-align: center;
color: #ffffff;
}
textarea.field {
width: 502px !important;
height: 146px;
border-radius: 5px;
background-color: #ffffff;
border: none;
padding: 18px;
resize: none;
}
textarea.field::placeholder {
width: 216px;
height: 16px;
font-family: 'Noto Sans KR', sans-serif;
font-size: 16px;
font-weight: normal;
font-stretch: normal;
font-style: normal;
line-height: 1;
letter-spacing: -0.96px;
text-align: left;
color: #868e96;
}
.card {
width: 538px;
border-radius: 5px;
background-color: #ffffff;
margin-bottom: 12px;
}
.card .metadata {
position: relative;
display: flex;
font-family: 'Spoqa Han Sans';
font-size: 11px;
font-weight: normal;
font-stretch: normal;
font-style: normal;
line-height: 1;
letter-spacing: -0.77px;
text-align: left;
color: #adb5bd;
height: 14px;
padding: 10px 23px;
}
.card .metadata .date {
}
.card .metadata .username {
margin-left: 20px;
}
.contents {
padding: 0px 23px;
word-wrap: break-word;
word-break: break-all;
}
.contents div.edit {
display: none;
}
.contents textarea.te-edit {
border-right: none;
border-top: none;
border-left: none;
resize: none;
border-bottom: 1px solid #212529;
width: 100%;
font-family: 'Spoqa Han Sans';
}
.footer {
position: relative;
height: 40px;
}
.footer img.icon-start-edit {
cursor: pointer;
position: absolute;
bottom: 14px;
right: 55px;
width: 18px;
height: 18px;
}
.footer img.icon-end-edit {
cursor: pointer;
position: absolute;
display: none;
bottom: 14px;
right: 55px;
width: 20px;
height: 15px;
}
.footer img.icon-delete {
cursor: pointer;
position: absolute;
bottom: 12px;
right: 19px;
width: 14px;
height: 18px;
}
#cards-box {
margin-top: 12px;
}
</style>
<script>
//....이하 생략
▶ Javascript란?
→ 브라우저를 살아 숨쉬게 만드는 친구이다. 클릭, 마우스 오버 시 색 밴화, 숨기기, 나타내기 등등 수많은 일을 수행 가능
▶ 크롬 개발자 도구
→ Javascript 공부, 웹서비스 개발을 할 때 필수적
→ F12 키로 개발자 도구를 열어 console 탭 클릭
▶ Javascript 기초 문법
◾ 변수
let a = 3; //변수를 처음 선언할 때 let을 써준다. 자료형은 써주지 않아도 된다.
let b = 2;
console.log(a + b); // System.out.println()과 같은 녀석: console.log()
b = 7;
console.log(a + b);
◾ 자료형(문자, 숫자, boolean, 리스트, 딕셔너리)
let name = 'bknam';
let course = "웹 개발의 봄, Spring" //자바와 다르게 홑/쌍따옴표 상관 없다.
let num = 10;
console.log(num + name); //문자 + 숫자 하면 둘 모두를 문자로 묶는다.
◾ Javascript boolean
let age1 = 18;
let age2 = 20;
let isAdult = age1 > 19;
console.log(isAdult); // false
isAdult = age2 > 19;
console.log(isAdult); // true
◾ Javascript list 리스트 -> 자료 형태 섞어서 해도 상관 없음
let fruits = ['사과', '딸기', '수박']; // List 보다 편하게 사용가능
console.log(fruits[0]);
console.log(fruits[1]);
console.log(fruits[2]);
◾ Javascript dictionary 딕셔너리
let course = {
'title': '웹 개발의 봄, Spring',
'tutor': '남병관'
};
console.log(course);
◾ Javascript 반복문
let fruits = ['사과', '딸기', '수박'];
for (let i=0; i<fruits.length; i++) {
let fruit = fruits[i];
console.log(fruit);
}
◾ 조건문
let fruits = ['사과', '딸기', '수박'];
for (let i=0; i<fruits.length; i++) {
let fruit = fruits[i];
console.log(fruit == '수박');
}
◾ 함수
function sample() {
alert('얼럿!'); //경고창
}
◾ 백틱(`) -> 문장에 원하는 변수 값을 넣고 싶을 때
let name = '내 이름';
let text = `${name}님의 스프링 5주 완주를 축하합니다!`;
console.log(text);
▶ jQuery란?
→ jQuery는 미리 작성된 자바스크립트 함수 모음집이다.
→ 되게 많이들 쓰는 HTML, CSS 조작 함수를 미리 만들어서 제공해주는 것!
→ 우리는 다 스스로 만들 필요 없이 사용법만 알면 된다.
→ jQuery Get Started (w3schools.com)
→ jQuery 임포트 (head 태그 사이에) : jQuery를 사용하려면
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
→ jQuery 사용하는 법: $로 시작하고, 괄호 안에 선택자로 대상을 적으면 된다.
$('#contents').hide(); //id가 contents인 녀석을 찾아서 숨겨줘
◾ 숨기기/나타내기
$('#post-box').show(); //HTML 나타내기
$('#post-box').hide(); //HTML 숨기기
◾ input 값 가져오기, 넣기
$('#post-url').val(); //id가 post-url인 녀석을 가져와서 그 안에 있는 값을 줘
$('#post-url').val('new text'); //new-text가 들어감
◾ HTML 없애기
$('#cards-box').empty();
◾ HTML 추가하기
$('#cards-box').append(`<div class="card">
<img class="card-img-top"
src="https://www.eurail.com/content/dam/images/eurail/Italy%20OCP%20Promo%20Block.adaptive.767.1535627244182.jpg"
alt="Card image cap">
<div class="card-body">
<a href="#" class="card-title">여기 기사 제목이 들어간다.</a>
<p class="card-text">기사의 요약 내용이 들어간다. 동해물과 백두산이 마르고 닳도록 하느님이 보우하사 ...</p
<p class="card-text comment">여기에 코멘트가 들어간다.</p>
</div>
</div>`);
▶ 클라이언트 설계하기 : 필요한 기능 살펴보기
1. 접속하자마자 메모 전체 목록 조회하기
a. GET API 사용해서 메모 목록 불러오기
b. 메모 마다 HTML 만들고 붙이기
2. 메모 생성하기
a. 사용자가 입력한 메모 내용 확인하기
b. POST API 사용해서 메모 신규 생성하기
c. 화면 새로고침하여 업데이트 된 메모 목록 확인하기
3. 메모 변경하기
a. 사용자가 클릭한 메모가 어떤 것인지 확인
b. 변경한 메모 내용 확인
c. PUT API 사용해서 메모 내용 변경하기
d. 화면 새로고침하여 업데이트 된 메모 목록 확인하기
4. 메모 삭제하기
a. 사용자가 클릭한 메모가 어떤 것인지 확인
b. DELETE API 사용해서 메모 삭제하기
c. 화면 새로고침하여 업데이트 된 메모 목록 확인하기
▶ ⭐메모 생성하기 - writePost 함수⭐
→ 개발 스펙 확인
1. 사용자가 입력한 메모 내용 확인하기
2. POST API 사용해서 메모 신규 생성하기
3. 화면 새로고침하여 업데이트 된 메모 목록 확인하기
// 메모를 생성합니다.
function writePost() {
// 1. 작성한 메모를 불러옵니다.
let contents = $('#contents').val();
// 2. 작성한 메모가 올바른지 isValidContents 함수를 통해 확인합니다.
if (isValidContents(contents) == false) {
return;
}
// 3. genRandomName 함수를 통해 익명의 username을 만듭니다.
let username = genRandomName(10);
// 4. 전달할 data JSON으로 만듭니다.
let data = {'username': username, 'contents': contents};
// 5. POST /api/memos 에 data를 전달합니다.
$.ajax({
type: "POST",
url: "/api/memos",
contentType: "application/json", //JSON 형식으로 전달함을 알리기
data: JSON.stringify(data), //=body
success: function (response) { //서버가 성공적으로 응답을 했을 때
alert('메시지가 성공적으로 작성되었습니다.');
window.location.reload(); //새로고침 코드
}
});
}
→ JSON.stringify란? JSON을 문자열로 만들어주는 것
→ ajax : 요청을 하는 것
▶ ⭐메모 조회하기 - getMessages 함수⭐
→ 개발 스펙 확인
1. 기존 메모 제거하기 : 화면에 있는 메모만 지워준다.
2. GET API 사용해서 메모 목록 불러오기
3. 메모마다 HTML 만들고 붙이는 함수 만들기
→ 아래의 붉은 상자 영역이 div#cards-box 이다. 메모 목록을 담은 div이다.
// 메모를 불러와서 보여줍니다.
function getMessages() {
// 1. 기존 메모 내용을 지웁니다.
$('#cards-box').empty();
// 2. 메모 목록을 불러와서 HTML로 붙입니다.
$.ajax({
type: 'GET',
url: '/api/memos',
success: function (response) {
for (let i = 0; i < response.length; i++) {
let message = response[i];
let id = message['id'];
let username = message['username'];
let contents = message['contents'];
let modifiedAt = message['modifiedAt'];
addHTML(id, username, contents, modifiedAt);
}
}
})
}
▶ Timestamped, Week03Application 수정하기
→ 뭔가 이상하지 않았나요? 왜 created_at, modified_at 데이터는 오지 않을까
→ ⭐Getter가 없기 때문!! 놓치기 쉬우니 주의
→ 또한, 스프링에게 Auditing 기능을 사용하고 있다록 알려주어야한다. -> ⭐@EnableJpaAuditing -> 생성시간, 수정 시간이 바뀌었을 때 자동으로 업데이트 됨
→ addHTML
// 메모 하나를 HTML로 만들어서 body 태그 내 원하는 곳에 붙입니다.
function addHTML(id, username, contents, modifiedAt) {
// 1. HTML 태그를 만듭니다.
let tempHtml = `<div class="card">
<!-- date/username 영역 -->
<div class="metadata">
<div class="date">
${modifiedAt}
</div>
<div id="${id}-username" class="username">
${username}
</div>
</div>
<!-- contents 조회/수정 영역-->
<div class="contents">
<div id="${id}-contents" class="text">
${contents}
</div>
<div id="${id}-editarea" class="edit">
<textarea id="${id}-textarea" class="te-edit" name="" id="" cols="30" rows="5"></textarea>
</div>
</div>
<!-- 버튼 영역-->
<div class="footer">
<img id="${id}-edit" class="icon-start-edit" src="images/edit.png" alt="" onclick="editPost('${id}')">
<img id="${id}-delete" class="icon-delete" src="images/delete.png" alt="" onclick="deleteOne('${id}')">
<img id="${id}-submit" class="icon-end-edit" src="images/done.png" alt="" onclick="submitEdit('${id}')">
</div>
</div>`;
// 2. #cards-box 에 HTML을 붙인다.
$('#cards-box').append(tempHtml);
}
let username = $(`#${id}-username`).text().trim(); //text로 박혀 있는 녀석
let contents = $(`#${id}-textarea`).val().trim(); //커서가 깜빡일 때 값을 가져오는 것(수정 중일 때)
▶ ⭐메모 변경하기 - submitEdit 함수⭐
→ 개발 스펙 확인
1. 작성 대상 메모의 username과 contents를 확인
2. 작성한 메모가 올바른지 확인
3. 전달할 data를 JSON으로 바꾸기
4. PUT /api/memos/{id} 에 data를 전달
// 메모를 수정합니다.
function submitEdit(id) {
// 1. 작성 대상 메모의 username과 contents 를 확인합니다.
let username = $(`#${id}-username`).text().trim();
let contents = $(`#${id}-textarea`).val().trim();
// 2. 작성한 메모가 올바른지 isValidContents 함수를 통해 확인합니다.
if (isValidContents(contents) == false) {
return;
}
// 3. 전달할 data JSON으로 만듭니다.
let data = {'username': username, 'contents': contents};
// 4. PUT /api/memos/{id} 에 data를 전달합니다.
$.ajax({
type: "PUT",
url: `/api/memos/${id}`,
contentType: "application/json",
data: JSON.stringify(data),
success: function (response) {
alert('메시지 변경에 성공하였습니다.');
window.location.reload();
}
});
}
▶ ⭐메모 삭제하기 - deleteOne 함수⭐
→ 개발 스펙 확인
1. DELETE API 사용해서 메모 삭제하기
// 메모를 삭제합니다.
function deleteOne(id) {
// 1. DELETE /api/memos/{id} 에 요청해서 메모를 삭제합니다.
$.ajax({
type: "DELETE",
url: `/api/memos/${id}`,
success: function (response) {
alert('메시지 삭제에 성공하였습니다.');
window.location.reload();
}
})
}
⭐과제
▶MemoRepository.java
public interface MemoRepository extends JpaRepository<Memo, Long> {
//지금: LocalDateTime.now();
//하루 전: LocalDateTime.now().minusDays(1);
List<Memo> findAllByModifiedAtBetweenOrderByModifiedAtDesc(LocalDateTime start, LocalDateTime end);
//수정된 날짜를 기준으로 내림차순으로 정렬하고, 찾아줘줘
}
▶MemoController.java
@RequiredArgsConstructor//요청이 들어올 때 스프링이 알아서 해줌
@RestController
public class MemoController {
//생성,조회,변경,삭제
private final MemoRepository memoRepository;
private final MemoService memoService;
@PostMapping("api/memos")
public Memo createMemo(@RequestBody MemoRequestDto requestDto){
Memo memo = new Memo(requestDto);
return memoRepository.save(memo);
}
@GetMapping("api/memos")
public List<Memo> readMemo(){
LocalDateTime now = LocalDateTime.now();
LocalDateTime oneDayBefore = LocalDateTime.now().minusDays(1);
return memoRepository.findAllByModifiedAtBetweenOrderByModifiedAtDesc(oneDayBefore, now);
}
@DeleteMapping("api/memos/{id}")
public Long deleteMemo(@PathVariable Long id){ //PateVariavle: 경로에 있는 것을 가져온다
memoRepository.deleteById(id);
return id;
}
@PutMapping("api/memos/{id}")
public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto){
memoService.update(id, requestDto);
return id;
}
}