클라이언트 설계하기

    • 필요 기능
      • 메모 조회
        - GET API 사용해서 메모 목록 불러오기
        - 메모마다 HTML 만들고 붙이기
      • 메모 생성 
        - 사용자가 입력한 메모 내용 확인
        - POST API 사용해서 메모 신규 생성
        - 화면 새로고침하여 업데이트 된 메모 목록 확인
      • 메모 변경
        - 사용자가 클릭한 메모가 어떤 것인지 확인
        - 변경한 메모 내용 확인
        - PUT API 사용해서 메모 내용 변경
        - 화면 새로고침하여 업데이트 된 메모 목록 확인
      • 메모 삭제
        - 사용자가 클릭한 메모가 어떤 것인지 확인
        - DELETE API 사용해서 메모 삭제
        - 화면 새로고침하여 업데이트 된 메모 목록 확인

     

    함수 작성하기

    • 메모 조회 - getMessages, addHTML, makeMessage 함수
      // 메모를 불러와서 보여줌
      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);
                  }
              }
          });
      }
      // 메모 하나를 HTML로 만들어서 body 태그 내 원하는 곳에 붙임
      function addHTML(id, username, contents, modifiedAt) {
          // 1. HTML 태그 생성
          let tempHtml = makeMessage(id, username, contents, modifiedAt);
      
          // 2. #card-box 에 HTML 붙임
          $('#cards-box').append(tempHtml);
      }
      
      // HTML 태그 생성 함수
      function makeMessage(id, username, contents, modifiedAt, i) {
          return `<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>`;
      }

     

    • 메모 생성 - 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 = {'usename': username, 'contents': contents};
      
          // 5. POST /api/memos 에 data 전달
          $.ajax({
              type: "POST",
              url: "/api/memos",
              contentType: "application/json",    // JSON 형식으로 전달함을 알리기
              data: JSON.stringify(data), // JSON을 문자열로 만들어줌
              success: function (response) {
                  alert('메시지가 성공적으로 작성되었습니다.');
                  window.location.reload();   // 화면 새로고침
              }
          });
      }

     

    • 메모 변경 - 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 함수
      // 메모 삭제
      function deleteOne(id) {
          $.ajax({
              type: "DELETE",
              url: `/api/memos/${id}`,
              success: function (response) {
                  alert('메시지 삭제에 성공하였습니다.');
                  window.location.reload();
              }
          })
      }

    타임라인 서비스 화면

     

    타임라인 API 설계

    기능 Method URL Return
    메모 생성 POST /api/memos Memo
    메모 조회 GET /api/memos List<Memo>
    메모 변경 PUT /api/memos/{id} Long
    메모 삭제 DELETE /api/memos/{id} Long

     

    프로젝트 생성

    ➡️ 메모는 1) 익명의 작성자 이름(username), 2) 메모 내용(contents)로 구성 

    • domain (src > main > java > com.sparta.week03 에 패키지 생성)
      • Memo.java
        @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
        @Getter	// 데이터 연결을 위해
        @MappedSuperclass	// Entitiy가 자동으로 컬럼으로 인식
        @EntityListeners(AuditingEntityListener.class)	// 생성/변경 시간을 자동으로 업데이트
        public class Timestamped {
        
            @CreatedDate
            private LocalDateTime createAt;
        
            @LastModifiedDate
            private LocalDateTime modifiedAt;
        }
      • MemoRequestDto.java
        @Getter
        public class MemoRequestDto {
            private String username;
            private String contents;
        }
      • MemoRepository.java - Interface / ID가 Long 타입
        public interface MemoRepository extends JpaRepository<Memo, Long> {
            List<Memo> findAllByOrderByModifiedAtDesc();
        }
    • service (src > main > java > com.sparta.week03 에 패키지 생성)
      • MemoService.java
        @RequiredArgsConstructor
        @Service
        public class MemoService {
            private final MemoRepository memoRepository;
        
            @Transactional
            public Long update(Long id, MemoRequestDto requestDto) {
                Memo memo = memoRepository.findById(id).orElseThrow(
                        () -> new IllegalArgumentException("아이디가 존재하지 않습니다.")
                );
                memo.update(requestDto);
                return memo.getId();
            }
        }
    • controller (src > main > java > com.sparta.week03 에 패키지 생성)
      • 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> getMemos() {
                return memoRepository.findAllByOrderByModifiedAtDesc();
            }
        
            @PutMapping("/api/memos/{id}")
            public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) {
                memoService.update(id, requestDto);
                return id;
            }
        
            @DeleteMapping("/api/memos/{id}")
            public Long deleteMemo(@PathVariable Long id) {
                memoRepository.deleteById(id);
                return id;
            }
        }
    • Spring에게 Auditing 기능을 사용하고 있다고 알려주어야 함
      ➡️ Week03Application.java@EnableJpaAuditing 추가

     

    Spring jpa localtime between

    - 메모 목록의 시간을 조회시간으로부터 24시간 이내로 변경

    - 지금: LocalDateTime.now(), 하루 전: LocalDateTime.now().minusDays(1)

    • MemoRepository.java
      public interface MemoRepository extends JpaRepository<Memo, Long> {
          List<Memo> findAllByModifiedAtBetweenOrderByModifiedAtDesc(LocalDateTime start, LocalDateTime end);
      }

     

    • MemoController.java
      // 중략 //
      
      @GetMapping("/api/memos")
      public List<Memo> getMemos() {
          LocalDateTime start = LocalDateTime.now().minusDays(1);
          LocalDateTime end = LocalDateTime.now();
          return memoRepository.findAllByModifiedAtBetweenOrderByModifiedAtDesc(start, end);
      }
      
      // 중략 //
    • API
      : 클라이언트 - 서버 간의 약속
      클라이언트가 정한대로 서버에게 요청(Request)을 보내면, 서버가 요구사항을 처리하여 응답(Response)을 반환

     

    • REST
      : 주소에 명사, 요청 박식에 동사(CRUD)를 사용함으로써 의도를 명확히 드러냄을 의미
      ➡️ 즉, 생성(POST)/조회(GET)/수정(PUT)/삭제(DELETE) 요청
      ✔️예시
      ⚠️ 주소에 들어가는 명사들은 복수형을 사용하고, 동사는 가급적 사용하지 않음
      • GET /courses -> 강의 전체 목록 조회 요청
      • GET /courses/1 -> ID가 1번인 녀석 조회 요청
      • POST /coures -> 강의 생성 요청
      • PUT /courses/3 -> ID가 3번인 녀석 수정 요청
      • DELETE /courses/2 -> ID가 2번인 녀석 삭제 요청

     

    • API 생성
      1. src > main > java > com.sparta.week02 에 controller 패키지 생성
      2. CourseController.java 파일 생성
        @RequiredArgsConstructor
        @RestController
        public class CourseController {
            
            private final CourseRepository courseRepository;
            
           	// API 작성
        }
        • GET
          @GetMapping("/api/courses")
          public List<Course> getCourses() {
              return courseRepository.findAll();
        • POST
        • @PostMapping("/api/courses")
          public Course createCourse(@RequestBody CourseRequestDto requestDto) {
              Course course = new Course(requestDto);
          
              return courseRepository.save(course);
          }

          • Course.java 에 생성자 추가
            public Course(CourseRequestDto requestDto) {
                this.title = requestDto.getTitle();
                this.tutor = requestDto.getTutor();
            }
        • PUT
          @PutMapping("/api/courses/{id}")
          public Long updateCourse(@PathVariable Long id, @RequestBody CourseRequestDto requestDto) {
              return courseService.update(id, requestDto);
          }
           
        • DELETE
          @DeleteMapping("/api/courses/{id}")
          public Long deleteCourse(@PathVariable Long id) {
              courseRepository.deleteById(id);
              return id;
          }

     

    • ARC(Advanced REST Client)
      : 현업에서 API를 생성하고 테스트 및 기능 확인을 위해 사용하는 툴의 한 종류
      Method 에서 방식 선택, Request URL에 확인하고자 하는 API 주소 작성
      ‼️POST, PUT 방식
      • Header name: Content-Type
      • Parameter value: application/json
      • BODY 탭에 JSON 형식으로 정보 입력

    • Lombok
      : JAVA 프로젝트를 진행하는데 거의 필수적으로 필요한 메소드/생성자 등을 자동생성해줌으로써 코드를 절약할 수 있도록 도와주는 라이브러리
      • Course.java 
        @Getter - Getter 직접 작성 없이 가능
        @NoArgsConstructor - 기본 생성자 직접 작성 없이 가능
      • CourseService.java
        @RequiredArgsConstructor - CourseRepository 받아오는 것 없이 final 선언만으로도 사용 가능
    • DTO(Data Transfer Object)
      : 데이터 교환을 위한 객체로 클래스를 막 건드리지 않고 완충재로 활용하는 것
      • 생성 및 적용
        1. src > main > java > com.sparta.week02 > domain 에 CourseRequestDto 파일 생성
          @NoArgsConstructor
          @Getter
          public class CourseRequestDto {
              private String title;
              private String tutor;
          
              public CourseRequestDto(String title, String tutor) {
                  this.title = title;
                  this.tutor = tutor;
              }
          }
        2. CourseService.java 변경
          @RequiredArgsConstructor
          @Service
          public class CourseService {
              private final CourseRepository courseRepository;
          
              @Transactional
              public Long update(Long id, CourseRequestDto requestDto) {
                  Course course1 = courseRepository.findById(id).orElseThrow(
                          () -> new IllegalArgumentException("해당 아이디가 존재하지 않습니다.")
                  );
                  course1.update(requestDto);
                  return course1.getId();
              }
          }
        3. Course.java 변경
          @Getter
          @NoArgsConstructor
          @Entity
          public class Course extends Timestamped {
          
              @Id
              @GeneratedValue(strategy = GenerationType.AUTO)
              private Long id;
          
              @Column(nullable = false)
              private String title;
          
              @Column(nullable = false)
              private String tutor;
          
              public Course(String title, String tutor) {
                  this.title = title;
                  this.tutor = tutor;
              }
          
              public void update(CourseRequestDto requestDto) {
                  this.title = requestDto.getTitle();
                  this.tutor = requestDto.getTutor();
              }
          }
        4. Week02Application.java 변경
          @EnableJpaAuditing
          @SpringBootApplication
          public class Week02Application {
          
              public static void main(String[] args) {
                  SpringApplication.run(Week02Application.class, args);
              }
          
              @Bean
              public CommandLineRunner demo(CourseRepository courseRepository, CourseService courseService) {
                  return (args) -> {
                      courseRepository.save(new Course("프론트엔드의 꽃, 리액트", "임민영"));
          
                      System.out.println("데이터 인쇄");
                      List<Course> courseList = courseRepository.findAll();
                      for (int i=0; i<courseList.size(); i++) {
                          Course course = courseList.get(i);
                          System.out.println(course.getId());
                          System.out.println(course.getTitle());
                          System.out.println(course.getTutor());
                      }
          
                      CourseRequestDto requestDto = new CourseRequestDto("웹개발의 봄, Spring", "임민영");
                      courseService.update(1L, requestDto);
                      courseList = courseRepository.findAll();
                      for (int i=0; i<courseList.size(); i++) {
                          Course course = courseList.get(i);
                          System.out.println(course.getId());
                          System.out.println(course.getTitle());
                          System.out.println(course.getTutor());
                      }
          
                      courseRepository.deleteAll();
                  };
              }
          }

    • JPA
      : SQL을 사용하지 않고 데이터를 생성, 조회, 수정, 삭제할 수 있도록 해주는 번역기
      🥲JPA가 없다면🥲
      JAVA 짜다가 SQL 짜고, 그걸 잘 맞춰 넣어야해서 매우 복잡
      • Domain - Table, Repository - SQL 로 생각하면 됨

     

    • H2를 이용해 테이블을 생성했던 것을 JAVA로 표현한다면?
      1. src > main > java > com.sparta.week02 에 domain 패키지 생성
      2. Course.java 파일 생성
        @NoArgsConstructor // 기본생성자 대신 생성
        @Entity // 테이블임을 나타냄
        public class Course {
        
            @Id // ID 값, Primary Key로 사용하겠다는 뜻
            @GeneratedValue(strategy = GenerationType.AUTO) // 자동 증가 명령
            private Long id;
        
            @Column(nullable = false) // 컬럼 값이고 반드시 값이 존재해야 함
            private String title;
        
            @Column(nullable = false)
            private String tutor;
            
            public Long getId() {
            	return this.id;
            }
            
            public String getTitle() {
                return this.title;
            }
        
            public String getTutor() {
                return this.tutor;
            }
        
            public Course(String title, String tutor) {
                this.title = title;
                this.tutor = tutor;
            }
        }
      3. CourseRepository.java 인터페이스 생성
        public interface CourseRepository extends JpaRepository<Course, Long> { 
        // Course의 Id는 Long타입
        }
        • Interface
          : Class에서 member가 빠진, method 모음집
          ‼️JPA는 Repository를 통해서만 사용 가능

      4. SQL이 보이도록 application.properties 세팅
        spring.jpa.show-sql=true
        spring.datasource.url=jdbc:h2:mem:testdb;MODE=MYSQL
      5. main 함수 아래에 JPA 실행 코드 추가
        @Bean
        public CommandLineRunner demo(CourseRepository repository) {
            return (args) -> {
        
            };
        }
    • 상속
      : extends - 클래스의 상속이미 만들어둔 걸 가져다 쓴다고 선언하는 것
      • Timestamped
        DB의 기본은 "생성일자"와 "수정일자"를 필드로 가지는 것
        • Timestamped.java 생성
          @MappedSuperclass // 상속했을 때, 컬럼으로 인식하게 합니다.
          @EntityListeners(AuditingEntityListener.class) // 생성/수정 시간을 자동으로 반영하도록 설정
          public class Timestamped {
          
              @CreatedDate // 생성일자임을 나타냅니다.
              private LocalDateTime createdAt;
          
              @LastModifiedDate // 마지막 수정일자임을 나타냅니다.
              private LocalDateTime modifiedAt;
          }
        • Course.javaextends 추가
          class Course extends Timestamped {
        • Week02Application.java@EnableJpaAuditing 추가
          @EnableJpaAuditing // 생성일자, 수정일자 자동으로 업데이트
          @SpringBootApplication
          public class Week02Application {
        • http://localhost:8080/h2-console 접속해서 확인
          SELECT * FROM course;
    • JPA 심화
      • CRUD: 정보관리의 기본 기능으로 Create(생성), Read(조회), Update(변경), Delete(삭제)을 뜻함 
      • Create & Read
        : Repository의 savefindAll 등 이용(main 함수에 작성)
        repository.save(new Course("Spring", "곽두필"));
        
        // 데이터 전부 조회하기(Read)
        List<Course> courseList = repository.findAll();
        for (int i=0; i < courseList.size(); i++) {
            Course course = courseList.get(i);
            System.out.println(course.getId());
            System.out.println(course.getTitle());
            System.out.println(course.getTutor());
        }
        
        // 데이터 하나 조회하기(Read)
        Course course = repository.findById(1L).orElseThrow(
            () -> new IllegalArgumentException("해당 아이디가 존재하지 않습니다.")
        );
      • Service
        ➡️ Update와 Delete 전에 다루어야 하는 개념
        • 스프링의 구조
          1. Controller: 가장 바깥 부분, 요청/응답 처리
          2. Service: 중간 부분, 실제 중요한 작동이 많이 일어나는 부분
          3. Repo: 가장 안쪽 부분, DB와 맞닿아 있음(Repository, Entity)
        • Service 만들기
          1. Course.javaupdate 추가
            public void update(Course course) {
                this.title = course.title;
                this.tutor = course.tutor;
            }
          2. src > main > java > com.sparta.week02 > service 패키지 생성
          3. CourseService.java 생성
            @Service // 스프링에게 이 클래스는 서비스임을 명시
            public class CourseService {
            
            		// final: 서비스에게 꼭 필요한 녀석임을 명시
                private final CourseRepository courseRepository;
            
            		// 생성자를 통해, Service 클래스를 만들 때 꼭 Repository를 넣어주도록
            		// 스프링에게 알려줌
                public CourseService(CourseRepository courseRepository) {
                    this.courseRepository = courseRepository;
                }
            
                @Transactional // SQL 쿼리가 일어나야 함을 스프링에게 알려줌
                public Long update(Long id, Course course) {
                    Course course1 = courseRepository.findById(id).orElseThrow(
                            () -> new IllegalArgumentException("해당 아이디가 존재하지 않습니다.")
                    );
                    course1.update(course);
                    return course1.getId();
                }
            }
      • Update & Delete (main 함수에 작성)
        @Bean
        public CommandLineRunner demo(CourseRepository courseRepository, CourseService courseService) {
            return (args) -> {
                courseRepository.save(new Course("프론트엔드의 꽃, 리액트", "임민영"));
        
                System.out.println("데이터 인쇄");
                List<Course> courseList = courseRepository.findAll();
                for (int i=0; i<courseList.size(); i++) {
                    Course course = courseList.get(i);
                    System.out.println(course.getId());
                    System.out.println(course.getTitle());
                    System.out.println(course.getTutor());
                }
        		
                // Update
                Course new_course = new Course("웹개발의 봄, Spring", "임민영");
                courseService.update(1L, new_course);
                courseList = courseRepository.findAll();
                for (int i=0; i<courseList.size(); i++) {
                    Course course = courseList.get(i);
                    System.out.println(course.getId());
                    System.out.println(course.getTitle());
                    System.out.println(course.getTutor());
                }
        		
                // Delete
                courseRepository.deleteAll();
            };
        }

    'Spring' 카테고리의 다른 글

    [Spring] API  (0) 2024.07.18
    [Spring] Lombok, DTO  (0) 2024.07.17
    [Spring] RDBMS , H2, SQL  (0) 2024.07.17
    [IntelliJ] GitHub 연동 및 프로젝트 업로드  (0) 2024.07.09
    [IntelliJ] 실행 오류  (0) 2024.07.09

    • RDBMS(Relational DataBase Management System)
      : 컴퓨터에 정보를 저장하고 관리하는 기술
      • 종류
        • MySQL
          : spring과 궁합이 좋음
        • PostgreSQL
        • Oracle Database
        • H2
          : In-memory DB의 대표 주자
          In-memory DB: 서버가 작동하는 동안에만 내용을 저장하고, 서버가 작동을 멈추면 데이터가 모두 삭제되는 데이터베이스
    • H2 웹콘솔 띄워보기
      1. src > main > resources > application.properties 파일 열기
      2. h2 웹콘솔 설정 추가
        spring.h2.console.enabled=true
        spring.datasource.url=jdbc:h2:mem:testdb
      3. java 파일 Run
      4. http://localhost:8080/h2-console 접속
        • Connect 버튼 클릭
        • 아래와 같은 화면이 뜨면 성공
    • SQL 기초
      • Structured Query Language 
      • 테이블 생성
        CREATE TABLE IF NOT EXISTS courses (
            id bigint NOT NULL AUTO_INCREMENT, 
            title varchar(255) NOT NULL,
            tutor varchar(255) NOT NULL,
            PRIMARY KEY (id)
        );
         
      • 데이터 삽입
        INSERT INTO courses (title, tutor) VALUES
            ('Spring', '곽두팔'), ('Spring2', '이두식');
      • 데이터 조회
        SELECT * FROM courses;
      • ‼️웹서비스를 만들기 위해 SQL 모두 배울 필요 X‼️
        ➡️ Spring Data JPA: 명령을 내린 JAVA 명령어를 SQL로 번역

    'Spring' 카테고리의 다른 글

    [Spring] Lombok, DTO  (0) 2024.07.17
    [Spring] JPA, CRUD  (0) 2024.07.17
    [IntelliJ] GitHub 연동 및 프로젝트 업로드  (0) 2024.07.09
    [IntelliJ] 실행 오류  (0) 2024.07.09
    [Spring] JAVA 기초 문법(2), Controller, Gradle  (0) 2024.07.05

    + Recent posts