[Spring]3주차

💡 학습내용
1. 스프링을 이용해 API 만들기

API 설계하기

Spring 서버에는
Controller, Service, Repository 3계층 존재

Repository => Service => Controller

타임라인 API

POST => /api/memos => Retrun Memo
GET => /api/memos => Retrun List<Memo>
PUT => /api/memos/{id} => Retrun Long
DELETE => /api/memos/{id} => Retrun Long

Repository 만들기

com > sparta > week03 > 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();
    }
}

Timestamped.java

@MappedSuperclass Entity 가 자동으로 컬럼으로 인식합니다.
@EntityListeners(AuditingEntityListener.class) 생성/변경 시간을 자동으로 업데이트. 변화가 일어나면 자동으로 업데이트 해주겠다.

package com.sparta.week03.domain;

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;

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class Timestamped {

    @CreatedDate
    private LocalDateTime createdAt; // 생성시간

    @LastModifiedDate
    private LocalDateTime modifiedAt; // 수정시간
}

MemoRepository.java

Query Lookup Strategies
List<Memo> findAllByOrderByModifiedAtDesc();
findAll 을 쓰지만, 그냥 findAll 을 하는게 아니라, 즉 전부 다 찾을 건데, 생성 시간을 최신순으로 정렬을 해줘 라고 요청하는 것!

package com.sparta.week03.domain;

import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;
// JpaRepository<Memo, Long> : Memo 라는 클래스고, Long 은 id 타입
public interface MemoRepository extends JpaRepository<Memo, Long> { 
    List<Memo> findAllByOrderByModifiedAtDesc(); // findAll By OrderBy Modified At Desc 순서대로 정렬을 해줘!
    // 수정된 날짜 기준으로 정렬 하라는 뜻! 최신 순으로 정렬을 해줘라는 뜻
}

MemoRequestDto.java

package com.sparta.week03.domain;

import lombok.Getter;

@Getter
public class MemoRequestDto {
    private String username;
    private String contents;
}

Service 만들기

Update 기능
com > sparta > week03 > service 위치에 만들기

MemoService.java

...

@RequiredArgsConstructor // final 로 선언된 변수가 있으면, 그 변수를 생성할 때 반드시 넣어 주겠다는 뜻!
@Service
public class MemoService {

    private final MemoRepository memoRepository; // final 꼭 필요하다고 말해줘야함

    @Transactional // update 할 때 이게 DB에 직접 반영 되야한다는 것을 알려줌.
    public Long update(Long id, MemoRequestDto requestDto) { // id와 변경시킬 때 필요한 정보
        Memo memo = memoRepository.findById(id).orElseThrow(
                () -> new IllegalArgumentException("아이디가 존재하지 않습니다.")
        );
        memo.update(requestDto);
        return memo.getId();
    }
}

Memo 클래스에 update 매서드 추가하기

...
public void update(MemoRequestDto requestDto) {
	this.username = requestDto.getUsername();
	this.contents = requestDto.getContents();
}
...

Controller 만들기

MemoController.java


@RequiredArgsConstructor // new memoRepository, new memoService 이렇게 안하고, 요청이 들어올 때 이런 거는 스프링이 알아서 해준다.
@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> readMemos() {
        return memoRepository.findAllByOrderByModifiedAtDesc();
    }

    @GetMapping("/api/memos/{id}")
    public Memo readMemos(@PathVariable Long id) {
        return memoRepository.findById(id).orElseThrow(
                () -> new IllegalArgumentException("해당 id 없음")
        );
    }

    @PutMapping("/api/memos/{id}")
    public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) {
        return memoService.update(id, requestDto);
    }

    @DeleteMapping("/api/memos/{id}")
    public Long deleteMemo(@PathVariable Long id) {
        memoRepository.deleteById(id);
        return id;
    }
}

jQuery

// 없에기
$('#cards-box').empty();

// 생성하기
$('#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>`);

클라이언트 설계하기

writePost

💎 writePost 함수

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),
    success: function (response) {
      alert('메시지가 성공적으로 작성되었습니다.');
      window.location.reload();
    }
  });
}

contentType: "application/json"
받는 쪽에다가 JSON이다, 번역해라 라고 말해주는 것
data: JSON.stringify(data)
원래 String 형태밖에 못주고 받으니까 JSON 형식을 String 형태로 바꾸기
success: function (response) {...}
성공적으로 응답이 왔을 때, 응답을 response 라는 것 안에다가 넣어주겠다.
window.location.reload()
화면 새로고침

getMessages

created_at, modified_at 데이터가 오지 않았던 이유는 Getter 가 없었기 때문.

  • Spring 한테 Auditing 기능을 사용하고 있다고 알려줘야함.

Timestamped 사용할 때 Getter 꼭 사용하기

Timestamped.java

package com.sparta.week03.domain;

...

@Getter
@MappedSuperclass // Entity 가 자동으로 컬럼으로 인식합니다.
@EntityListeners(AuditingEntityListener.class) // 생성/변경 시간을 자동으로 업데이트합니다. 변화가 일어나면 자동으로 업데이트 해주겠다.
public abstract class Timestamped {

    @CreatedDate
    private LocalDateTime createdAt; // 생성시간

    @LastModifiedDate
    private LocalDateTime modifiedAt; // 수정시간
}

Week03Application.java

package com.sparta.week03;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing // 꼭 넣어주기!
@SpringBootApplication
public class Week03Application {

    public static void main(String[] args) {
        SpringApplication.run(Week03Application.class, args);
    }

}

💎 getMessages 함수

$(document).ready(function () {
  // HTML 문서를 로드할 때마다 실행합니다.
  getMessages();
})

// 메모를 불러와서 보여줍니다.
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 memo = response[i];
        let id = memo.id;
        let username = memo.username;
        let contents = memo.contents;
        let modifiedAt = memo.modifiedAt;
        addHTML(id, username, contents, modifiedAt)
      }
    }
  })
}

// 메모 하나를 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);
}

submitEdit

💎 submitEdit 함수

// 메모를 수정합니다.
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

💎 deleteOne 함수

function deleteOne(id) {
    $.ajax({
        type: "DELETE",
        url: `/api/memos/${id}`,
        success: function (response) {
            alert('메시지 삭제에 성공하였습니다.');
            window.location.reload();
        }
    })
}

좋은 웹페이지 즐겨찾기