안녕하세요! delay100입니다. 예전부터 이 주제로 포스팅을 하고 싶었어요!
왜냐하면,, CRUD만큼은 여러번 구현해보았고..
머릿속에서 완벽히 정리할겸,
개발자를 시작하는 분들은 여러번 따라해보는 것이 실력이 잘 는다고 생각하기 때문에,
지식을 공유할겸 작성해봅니다.
그런고로 따라만 하면 만들어질 수 있도록 쉽게 만들어보려고 합니다!
그리고 따라만 하면 이해가 되지 않기 때문에, 최대한 상세히 설명(주석)도 적을 예정이니 겁먹지 마시고 같이 만들어봐요!!
자, 서론이 길었죠? 이제 시작해봅시다. 따라하면 만들어지는 메모장 + AWS 배포까지 1탄입니다.
github(https://github.com/delay-100/memo)에 코드를 챕터별로 정리해두었습니다.
chapter0. 최종 결과
아래의 URL에서 확인 가능합니다.
chapter1. 환경 설치
먼저, 코드를 작성하기 위해 환경 세팅을 해줘야 합니다.
설치해야할 것 (*필수)
자신의 운영체제(Windows, Linux..)에 맞게 각각의 사이트에서 다운로드 해주세요.
필자 - Windows 64bit
[언어] _필자의 버전
- java* (https://www.java.com/ko/) _(version 17)
- mysql* (https://dev.mysql.com/downloads/installer/) _(version 8.0.33)
- git Bash* (https://git-scm.com/downloads) _(version 2.41.0)
다운로드 시에 까먹지 말아야 할 것은 mysql 설치 시 username과 password는 잊어버리면 안된다는 것입니다. 이 점만 유의해주세요!
[사용할 툴]
- intelliJ IDEA* (https://www.jetbrains.com/idea/)
- DBeaver*(https://dbeaver.io/download/)
- git fork (https://git-fork.com/)
필자는 intelliJ Ultimate(유료 버전)을 이용합니다. intelliJ Community(무료 버전)을 사용하셔도 무방합니다.
필수(*)표시가 되어있지 않은 git fork는 다운받지 않아도 되나, git상태를 확인할 것이기에 설치해주시면 git의 이해에 도움이 더 잘 될겁니다.
다운로드 방법과 초기 세팅은 타 블로그에 잘 나와있기 때문에 여기서는 생략합니다.
chapter2. SpringBoot 프로젝트 생성
2-1. 프로젝트 생성
기초적인 환경 세팅은 마쳤으니 정말 프로젝트를 만들러 출발해봅시다.
https://start.spring.io/ 에 접속하여 프로젝트를 만들어줄겁니다.
Project, Language, Spring Boot, Packaging, Java는 건드릴 것이 없고(아래 사진참고),
Project Metadata를 수정한 후, Dependencies를 추가해줍니다.
Project Metadata는 마음대로 적어도 되지만, Dependencies는 꼭 4가지를 추가해주어야 합니다.
사실 Project Metadata를 마음대로 적으시면 저와 폴더명이 달라지기 때문에 똑같이 적어주시는게 좋을겁니다.(아래 사진 참고)
[Add Dependencies]
- Spring Web*
- Lombok*
- Spring Data JPA*
- MySQL Driver*
여러 프로젝트에서 잘 쓰는 thymeleaf를 이용하지 않는 이유는 ModelAttribute로 값을 이동하는 대신,
프론트엔드와 직접 소통하는 것처럼 json을 이용해 값을 전달하기 위함입니다.
설정을 마쳤으면 GENERATE를 클릭하여 폴더를 다운받습니다.
원하는 위치에 폴더압축을 풀어주고 IntelliJ에서 해당 폴더를 열어줍니다.
OK를 클릭해줍니다.
Trust Project를 클릭해줍니다.
아래의 사진처럼 왼쪽에 프로젝트가 뜬다면, 프로젝트 생성 성공입니다!
2-2. 추가 세팅
1. (File -> Settings) Build Tools의 Gradle -> IntelliJ로 변경
2. MemoApplicationTest.java의 @SpringBootTest 주석처리
해당 Test어노테이션을 주석처리하지않으면 에러가 발생할 수 있습니다. 꼭 주석처리 해주세요!
chapter3. 데이터베이스 생성 및 연결
[chapter3. https://github.com/delay-100/memo/tree/chapter3]
3-1. DB 설정
먼저, resources/application.properties를 resources/application.yml으로 변경해줄겁니다.
properties를 그대로 두어도 상관없지만, 개인적으로 중복 코드가 줄어드는 yml을 선호하기 때문에 먼저 바꿔주도록 하겠습니다.
// 변경 전 - application.properties
spring.application.name: memo
// 변경 후 - application.yml
spring:
application:
name: memo
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/memo
username: [1번의 username]
password: [1번의 password]
jpa:
hibernate:
ddl-auto: update
properties:
hibernate:
format_sql: true
show_sql: true
highlight_sql: true
DBeaver에 접속해서 database를 생성해줍니다. "memo"를 만들어주면 됩니다.
위에서 application.yml에 url에 입력한 memo가 이 데이터베이스를 가리키게 됩니다.
만드는 방법은 1. DBeaver로 새 데이터베이스 연결하기(DBeaver에 MariaDB 연결)에서 해결방안 1을 참고해주세요!
링크에서는 MariaDB를 이용했지만 MySQL도 동일하게 하면 됩니다.
* 저와 DB 그림이 다르다고요!?
-> 괜찮습니다. 저는 MariaDB로 설정해두어서 다른겁니다. MariaDB와 Mysql은 대부분 다 호환이 되기 때문에 그냥 사용하면 됩니다.
Mysql을 최초로 연결할 때 Driver 설치가 있을 수 있습니다.
기다려주시면 됩니다.
3-2. Entity 만들기
이제 Memo Entity를 만들어줍시다. Entity는 데이터베이스와 통신할 때 사용되는 객체입니다.
Entity는 entity폴더를 생성한 후 Memo.java 클래스를 만들면 됩니다. 상세 위치는 아래 사진을 참고해주세요.
package com.whitedelay.memo.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.LocalDateTime;
@Getter
@Setter
@Entity // Entity임을 명시, 기본 생성자 만들어줌
@NoArgsConstructor(access = AccessLevel.PROTECTED) // 매개변수가 없는 기본 생성자를 사용할 수 없게 함
public class Memo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 1, 2, 3, 4와 같이 순차적으로 id값을 자동으로 증가시킴
private Long id;
private String contents;
private LocalDateTime createdAt;
}
그리고 MemoApplication.java에서 main문을 실행해봅시다.
아래와 같이 실행 콘솔에서 create table문을 확인할 수 있습니다. 이렇게 실행 시 query문으로 테이블 생성을 확인할 수 있는 이유는 3-1에서 application.yml에 jpa properties를 주었기 때문입니다.
이렇게 테이블이 만들어진 것은 DBeaver에서 직관적으로 확인할 수 있습니다.
chapter4. 화면 만들기
[chapter4. https://github.com/delay-100/memo/tree/chapter4]
우리는 thymeleaf를 이용하지 않고 오직 html, css, js로만 프론트엔드를 구현할겁니다.
왜냐하면 thymeleaf를 이용하면 협업 시 프론트엔드와 json으로 데이터를 주고받는 것을 이해하기 어렵기 때문입니다.
또한 여기서는 프론트엔드보다는 백엔드를 중점적으로 다룰 것이기 때문에 이 방법을 이용합니다.
따라서 springboot에서 기본으로 실행하는 resources/static/index.html을 생성하겠습니다.
그리고 static내부에 css/index.css, js/index.js도 만들어주었습니다.
화면 코드는 너무 길어서 github를 참고해주세요!!!!
최종적으로 여기서는 폴더 2개, 파일 3개를 만들어줘야합니다.
static/index.html
https://github.com/delay-100/memo/blob/chapter4/src/main/resources/static/index.html
static/js/index.js
https://github.com/delay-100/memo/blob/chapter4/src/main/resources/static/js/index.js
static/css/index.css
https://github.com/delay-100/memo/blob/chapter4/src/main/resources/static/css/index.css
css를 만드는건 정말 힘듭니다.. 퍼블리셔분들 존경해요... gpt의 도움을 일부 받았습니다..
chapter5. 핵심 기능 구현
이제, 테이블도 준비되었으니 본격적으로 기능을 만들어봅시다.
5-1. Dto
그런데 기능을 만들기 전에, 우리는 위에서 만든 Entity를 가지고 사용자(클라이언트)와 서버가 소통하지 않을겁니다.
대신 DTO(Data Transfer Object)를 만들어 데이터를 전달할겁니다.
- Entity - 데이터베이스에 직접 접근할 때 사용
- DTO - 서버, 클라이언트간, spring내부 동작에서 데이터를 주고 받을 때 사용
총 2개의 Dto를 만들건데, 하나는 요청에 관한 Dto,하나는 응답과 관련된 Dto입니다.
5-1-1. MemoRequestDto
package com.whitedelay.memo.dto;
import lombok.Getter;
@Getter
public class MemoRequestDto {
public String contents;
}
5-1-2. MemoResponseDto
package com.whitedelay.memo.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.time.LocalDateTime;
@Getter
@AllArgsConstructor
public class MemoResponseDto {
private long id;
private String contents;
private LocalDateTime createdAt;
}
5-2. Controller
controller에서는 Annotation(@)으로 @RestController을 명시해주었습니다. RestController은 Spring MVC패턴에서 json으로 값을 반환할 때 사용합니다.
만약 Annotation이 @RestController인 것 마음에 들지 않는다면 @Controller을 써도 됩니다. 그러나 json으로 반환해야하는 메소드에 @ResponseBody를 계속 붙혀줘야합니다.
즉, @RestController = @Controller + @ResponseBody 라고 보면 됩니다.
// controller/MemoController.java
package com.whitedelay.memo.controller;
import com.whitedelay.memo.dto.MemoRequestDto;
import com.whitedelay.memo.dto.MemoResponseDto;
import com.whitedelay.memo.service.MemoService;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController // 모두 json으로 반환할 것이기 때문에, @Controller대신 @RestController을 이용
@RequestMapping("/memo")
public class MemoController {
private final MemoService memoService;
public MemoController(MemoService memoService) { // 생성자 주입
this.memoService = memoService;
}
/**
* getMemoList 메모 리스트 출력
* @return List<MemoResponseDto> 메모 정보가 담긴 리스트
*/
@GetMapping("")
public List<MemoResponseDto> getMemoList() {
return memoService.getMemoList();
}
/**
* postMemo 메모 작성
* @param memoRequestDto 메모에 적을 내용
* @return long 작성된 메모 아이디
*/
@PostMapping("")
public long postMemo(MemoRequestDto memoRequestDto) {
return memoService.postMemo(memoRequestDto);
}
/**
* updateMemo 메모 수정
* @param id 수정할 메모 아이디
* @param memoRequestDto 수정할 내용
* @return long 수정된 메모 아이디
*/
@PutMapping("/{id}")
public long updateMemo(@PathVariable long id, MemoRequestDto memoRequestDto) {
return memoService.updateMemo(id, memoRequestDto);
}
/**
* deleteMemo 메모 삭제
* @param id 삭제할 메모 아이디
* @return long 삭제된 메모 아이디
*/
@DeleteMapping("/{id}")
public long deleteMemo(@PathVariable long id) {
return memoService.deleteMemo(id);
}
}
5-3. Service
package com.whitedelay.memo.service;
import com.whitedelay.memo.dto.MemoRequestDto;
import com.whitedelay.memo.dto.MemoResponseDto;
import com.whitedelay.memo.entity.Memo;
import com.whitedelay.memo.repository.MemoRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Service
public class MemoService {
private final MemoRepository memoRepository;
public MemoService(MemoRepository memoRepository) { // 생성자 주입
this.memoRepository = memoRepository;
}
/**
* getMemoList 메모 리스트 출력
* @return List<MemoResponseDto> 메모 정보가 담긴 리스트
*/
public List<MemoResponseDto> getMemoList() {
List<MemoResponseDto> memoResponseDtoList = new ArrayList<>();
List<Memo> memos = memoRepository.findAllByOrderByCreatedAtDesc();
memos.stream().toList().forEach(memo -> {
memoResponseDtoList.add(new MemoResponseDto(memo.getId(), memo.getContents(), memo.getCreatedAt()));
});
return memoResponseDtoList;
}
/**
* searchMemo 메모 내용 검색
* @param keyword 검색할 내용
* @return 검색된 메모 정보가 담긴 리스트
*/
public List<MemoResponseDto> searchMemo(String keyword) {
List<MemoResponseDto> memoResponseDtoList = new ArrayList<>();
List<Memo> memos = memoRepository.findByContentsContainingOrderByCreatedAtDesc(keyword);
memos.stream().toList().forEach(memo -> {
memoResponseDtoList.add(new MemoResponseDto(memo.getId(), memo.getContents(), memo.getCreatedAt()));
});
return memoResponseDtoList;
}
/**
* postMemo 메모 작성
* @param memoRequestDto 메모에 적을 내용
* @return long 작성된 메모 아이디
*/
public Long postMemo(MemoRequestDto memoRequestDto) {
Memo memo = memoRepository.save(new Memo(memoRequestDto.getContents(), LocalDateTime.now())); // memo만들 때 id를 넣지 않아도 되는 이유: Memo Entity에 @GeneratedValue를 선언해주었기 때문!
return memo.getId();
}
/**
* updateMemo 메모 수정
* @param id 수정할 메모 아이디
* @param memoRequestDto 수정할 내용
* @return long 수정된 메모 아이디
*/
@Transactional
public Long updateMemo(Long id, MemoRequestDto memoRequestDto) {
Memo memo = memoRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("해당 메모가 없습니다."));
memo.setContents(memoRequestDto.getContents());
return memo.getId();
}
/**
* deleteMemo 메모 삭제
* @param id 삭제할 메모 아이디
* @return long 삭제된 메모 아이디
*/
public Long deleteMemo(Long id) {
Memo memo = memoRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("해당 메모가 없습니다."));
memoRepository.delete(memo);
return memo.getId();
}
}
5-4. Repository
원래 5-4를 Dao로 넣을 생각이었는데, 이번 구현에서는 ORM으로 Spring Data JPA를 사용할 것이므로 해당 구조는 빠져버렸습니다. (그냥 ORM을 Mybatis로 할껄그랬나 싶기도하네요)
왜냐하면 SpringDataJPA를 사용할때는 SpringDataJPA를 상속(extends) 하는데,
구현 메소드(SpringDataJPA)에 데이터에 접근할 수 있게 하는 (제네릭)메소드들이 명시 되어있다고 이해하고 넘어가면 됩니다.
extends하는 JPARepository의 내부적인 부분을 잠깐 보자면 JpaRepository.class에 접근해보면 아래와 같습니다.
java의 제네릭클래스인 T, S로 타입에 구애받지 않고 메소드를 만들어줍니다.
만약, MemoRepository를 선언했는데, MemoService에 Could not autowire. No beans of 'MemoRepository' type found. 에러가 발생하는 경우
IntelliJ를 껐다가 다시 켜보세요! 캐시 문제일 수 있습니다.
package com.whitedelay.memo.repository;
import com.whitedelay.memo.entity.Memo;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface MemoRepository extends JpaRepository<Memo, Long> {
List<Memo> findAllByOrderByCreatedAtDesc();
List<Memo> findByContentsContainingOrderByCreatedAtDesc(String keyword);
}
이렇게 간단한 메모장을 만들어보았습니다!!
이제, 배포를 해봐야겠죠? 배포는 2탄으로 돌아오겠습니다.
'Study > SpringBoot' 카테고리의 다른 글
[e-commerce 프로젝트] 4. 마이페이지 정보 업데이트 (4) | 2024.07.25 |
---|---|
[e-commerce 프로젝트] 3. 로그인(SpringSecurity + JWT 로그인 및 인가) (7) | 2024.07.24 |
[e-commerce 프로젝트] 2. 회원가입(네이버 이메일 인증, 개인정보·비밀번호 암호화) (6) | 2024.07.24 |
[e-commerce 프로젝트] 1. 프로젝트 간단 소개 및 ERD 설계 (1) | 2024.07.17 |
[SpringBoot] Spring프로젝트 Postman에서 확인하기 (0) | 2024.05.13 |