학습 내용 정리
Git SSH 키 생성
private 저장소는 권한이 있는 사람만 접근 가능 - SSH Key 발급 후 GitHub에 Public Key 등록 시 private 저장소 클론 가능
순서
- Git Bash에서 ssh-keygen 입력 후 enter 3회
- cd ~/.ssh 입력 후 enter
- ls -al 입력 후 enter
- cat (key name).pub 입력 (key name에는 .pub 앞 문장을 입력) 후 enter
- 아래의 공개키 복사(ctrl + Inst , 우클릭 paste)
- 깃허브 프로필 사진 클릭 후 Settings 클릭
- SSH and GPG keys에서 New SSH Key 클릭
- Key에 복사한 SSH 키 붙여넣기
- Add SSH key 클릭
- private 저장소 SSH 주소 복사
- git clone 복사한 주소 입력 후 enter → yes 후 enter
프로젝트 생성 Git 연동
- 인텔리제이 프로젝트 생성 시 Create Git repository 선택 시 자동으로 프로젝트에 Git 연동
- Menu → VCS → Create Git Repository → 프로젝트 폴더에 open
Git Commit
- 좌측 Commit 탭 클릭 후 Changes 선택 Commit 메시지 입력 후 Commit 버튼 클릭
- 하단 Git 탭에서 버전 별 변경사항 확인 가능
Gradle 사용
- Build 자동화 시스템(작성한 Java 코드를 설정에 맞게 자동 Build)
build.gradle : Gradle 기반의 빌드 스크립트(소스 코드를 빌드하고 라이브러리들의 의존성을 쉽게 관리)
- 필요한 외부 라이브러리는 dependencies 부분에 작성하면 Gradle이 해당 라이브러리들을 자동으로 다운로드 해옴
- 다른 라이브러리들과의 의존성을 자동으로 관리해 주기 때문에 라이브러리들간의 충돌 걱정없이 개발에만 집중할 수 있음
- 스프링 부트의 버전은 version '3.1.0' 부분에서 수정 가능
- 변경 사항 수정 후 Gradle 아이콘(코끼리 모양)을 클릭해서 변경 사항 갱신 가능
- External Libraries에서 Gradle이 다운로드해온 라이브러리들을 확인 가능(project 폴더 목록 하단에 위치)
서버
Client 와 Server
- 사용자의 요청이 서버에 도달하기 위해서는 해당 서버의 정보가 필요
- 사용자의 요청이 해당 서버에 정확하게 도달할 수 있게 제공되는 정보가 IP 주소
- 네트워크상에서의 데이터 송/수신은 이 주소를 기준으로 이루어지고 있음
웹 서버
- 인터넷을 통해 HTTP를 이용하여 웹상의 클라이언트의 요청을 응답해주는 통신을 하는 일종의 컴퓨터
- 브라우저를 통해 HTTP Request로 웹사이트를 웹서버에 요청
- 웹서버는 요청을 승인하고 HTTP Response를 통해 웹사이트 데이터를 브라우저에 전송
- 브라우저는 서버에서 받아온 데이터를 이용해 웹사이트를 브라우저에 그려냄
- 기본적으로 브라우저가 웹서버에 요청을 할때는 항상 GET method로 요청
- API / Interface
- API(application programming interface)는 다른 소프트웨어 시스템과 통신하기 위해 따라야 하는 규칙을 정의
- 개발자는 다른 애플리케이션이 프로그래밍 방식으로 애플리케이션과 통신할 수 있도록 API를 표시하거나 생성
- 인터페이스(interface)는 서로 다른 두 개의 시스템, 장치 사이에서 정보나 신호를 주고받는 경우의 접점이나 경계면을 의미
- 사용자가 기기를 쉽게 동작시키는데 도움을 주는 시스템
- API(application programming interface)는 다른 소프트웨어 시스템과 통신하기 위해 따라야 하는 규칙을 정의
- RESTful API
- Representational State Transfer(REST)는 API 작동 방식에 대한 조건을 부과하는 소프트웨어 아키텍처로 REST 아키텍처 스타일을 따르는 API를 REST API라고함
- 서버의 api가 적절하게 http를 준수하며 잘 설계되어있으면 RESTful 하게 설계되어 있다고 생각하면 좋음
- Representational State Transfer(REST)는 API 작동 방식에 대한 조건을 부과하는 소프트웨어 아키텍처로 REST 아키텍처 스타일을 따르는 API를 REST API라고함
- Web Server / Web Application Server(WAS)
- 브라우저에서 URL을 입력하여 어떠한 페이지를 요청했을 때 HTTP의 요청을 받아들여 HTML 문서와 같은 정적인 콘텐츠를 사용자에게 전달해주는 역할을 하는 것이 Web Server
- WAS를 사용하면 로그인,회원가입을 처리하거나 게시물을 조회하거나 정렬하는 등의 다양한 로직들을 수행하는 프로그램을 동작시킬 수 있음
HTTP
HTTP(HyperText Transfer Protocol) : 데이터를 주고 받는 양식을 정의한 "통신 규약"중 하나
- 통신 규약이란, 컴퓨터끼리 데이터를 주고 받을 때 정해둔 약속
Request / Response
- 브라우저는 서버에게 자신이 원하는 페이지(URL 등의 정보)를 요구(Request)
- 서버는 브라우저가 원하는 페이지가 있는지 확인하고, 있다면 해당 페이지에 대한 데이터를 실어 응답(Response)
- 없다면 없는 페이지에 대한 데이터를 반환
웹 개발자 도구의 네트워크 탭
- 웹 개발을 진행하면서 문제가 생겼을 때 문제에 대한 분석을 위해 개발자 도구와 네트워크 탭을 사용
- Headers 탭(Name의 목록 중 하나 클릭 시 나옴)
- General : 브라우저에서 서버로 보낸 Request 데이터
- HTTP 상태 코드
- 1xx (Informational) : 요청이 수신 됨, 처리가 계속되고 있음(브라우저와 클라이언트 서버의 연결 상태 확인)
- 2xx (Successful) : 클라이언트의 요청이 성공적으로 처리됨
- 3xx (Redirection) : 클라언트가 추가적인 조치를 취해야 함을 나타냄(페이지 이동, 리다이렉션 등에 사용)
- 4xx (Client Error) : 클라이언트에 오류가 있음(클라이언트의 잘못된 요청, 인증 오류)
- 404 : 요청한 페이지나 리소스를 서버에서 찾을 수 없음
- 5xx (Server Error) : 서버에 오류 발생(서버의 오류, 서버 과부하 등)
- 500 : 서버 내부에 오류 발생
- Method (호출 요청 방식)
- GET, POST, DELETE 등이 존재
- Header (추가 데이터, 메타 데이터)
- 아주 다양한 의사 표현을 위한 데이터를 모두 Header 필드에 넣고 주고 받음
테스트 코드
- 블랙박스 테스팅
- 소프트웨어 내부 구조나 동작원리를 모르는 상태, 웹 서비스의 사용자 입장에서 동작을 검사하는 방법
- 장점 : 누구나 테스트 가능
- 단점 : 기능이 증가될수록 테스의 범위가 증가, 테스트 하는 사람에 따라 테스트 퀄리티가 다를 수 있음
- 소프트웨어 내부 구조나 동작원리를 모르는 상태, 웹 서비스의 사용자 입장에서 동작을 검사하는 방법
- 개발자 테스트
- 개발자가 직접 "본인이 작성한 코드"를 검증하기 위해 "테스트 코드"를 작성
- 장점 : 빠르고 정확한 테스트가 가능(예상 동작 vs 실제 동작), 테스트 자동화 가능, 리팩토링이나 기능 추가가 편함
- 단점 : 개발 시간이 오래 걸림, 테스트 코드를 유지보수하는 비용
- 개발자가 직접 "본인이 작성한 코드"를 검증하기 위해 "테스트 코드"를 작성
- 테스트 파일 생성
- 클래스 파일 내에서 마우스 우클릭 → Generate... 클릭 → Test... 클릭 → 기본 세팅으로 생성(단축키 ctrl + shift + t)
테스트 코드 사용 예제
package com.sparta.springmvc;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sparta.springmvc.response.Star;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class JacksonTest {
@Test
@DisplayName("Object To JSON : get Method 필요")
void test1() throws JsonProcessingException {
Star star = new Star("Robbie", 95);
ObjectMapper objectMapper = new ObjectMapper(); // Jackson 라이브러리의 ObjectMapper
String json = objectMapper.writeValueAsString(star);
System.out.println("json = " + json);
}
@Test
@DisplayName("JSON To Object : 기본 생성자 & (get OR set) Method 필요")
void test2() throws JsonProcessingException {
String json = "{\"name\":\"Robbie\",\"age\":95}"; // JSON 타입의 String
ObjectMapper objectMapper = new ObjectMapper(); // Jackson 라이브러리의 ObjectMapper
Star star = objectMapper.readValue(json, Star.class);
System.out.println("star.getName() = " + star.getName());
System.out.println("star.getAge() = " + star.getAge());
}
}
Lombok
- 자바 프로젝트를 진행하는데 거의 필수적으로 필요한 메서드/ 생성자 등을 자동 생성해줌으로써 코드를 절약할 수 있도록 도와주는 라이브러리
- 환경 설정(crtl + alt + s) → Annotation Processors 입력 → Enable 체크 후 ok → IDE plugins에서 lombok install 후 IDE 재시작
- 사용 @(Annotation) 입력 후 필요한 lombok 입력
- java.lang.NoSuchFieldError 에러 해결방법
- IDE JDK 17로 변경(JDK 17 이후 버전에서는 lombok 사용 불가)
application.properties
- application.properties는 관련된 설정을 할 때 사용되는 파일
- 이 파일 사용 시 자동으로 설정되고 있는 설정 값을 쉽게 수정할 수 있음
- DB 연결 시 DB의 정보를 제공하는 것도 쉽게 가능
Spring MVC
- Spring Web MVC는 Servlet API를 기반으로 구축된 독창적인 웹 프레임워크
- 중앙에 있는 DispatcherServlet이 요청을 처리하기 위한 공유 알고리즘을 제공하는 Front Controller 패턴을 중심으로 설계되어 있으며 이 모델은 유연하고 다양한 워크 플로우를 지원
MVC 디자인 패턴 : Model-View-Controller 의 약자, 소프트웨어 디자인 패턴 중 하나
- MVC 패턴은 소프트웨어 구성 요소들을 Model, View, Contorller로 구분하여 각각의 역할을 분리
Model
- 데이터와 비즈니스 로직을 담당
- DB와 연동하여 데이터를 저장하고 불러오는 등의 작업 수행
View
- 사용자 인터페이스를 담당
- 사용자가 보는 화면과 버튼, 폼 등을 디자인하고 구현
Controller
- Model과 View 사이의 상호작용을 조정하고 제어
- 사용자의 입력을 받아 Model에 전달, Model의 결과를 바탕으로 View를 업데이트
Servlet(서블릿)
- 자바를 사용하여 웹 페이지를 동적으로 생성하는 서버 측 프로그램 혹은 그 사양
Front Controller
- Client(브라우저)에서 HTTP 요청이 들어오면 DispatcherServlet 객체가 요청을 분석
- DispatcherServlet 객체는 분석한 데이터를 토대로 Handler mapping을 통해 Controller를 찾아 요청을 전달
- Controller → DispathcerServlet : Controller는 요청에 대한 처리를 완료 후 처리에 대한 결과 즉, 데이터('Model')와 'View' 정보를 전달
- DispatcherServlet → Client : ViewResolver 통해 View에 Model을 적용하여 View를 Client에게 응답으로 전달
Controller
장점
- API 마다 파일을 만들 필요 없음 - 유사한 성격의 API 를 하나의 Controller 로 관리
- 메서드 이름도 내 마음대로 설정 가능합니다. (단, 클래스 내의 중복메서드명 불가)
컨트롤러 사용 예제
package com.sparta.springmvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/api")
public class HelloController {
@GetMapping("/hello")
@ResponseBody
public String hello() {
return "Hello World!!";
}
@GetMapping("/get")
@ResponseBody
public String get() {
return "GET Method 요청";
}
@PostMapping("/post")
@ResponseBody
public String post() {
return "POST Method 요청";
}
@PutMapping("/put")
@ResponseBody
public String put() {
return "PUT Method 요청";
}
@DeleteMapping("/delete")
@ResponseBody
public String delete() {
return "DELETE Method 요청";
}
}
- 각각의 HTTP Method에 매핑되는 애너테이션 사용
- @RequestMapping은 중복되는 URL를 단축
정적페이지 처리
- SpringBoot 서버에 html 파일을 바로 요청하면 해당 html 파일을 static 폴더에서 찾아서 반환
- 템플릿 엔진을 적용한 상태에서 static 폴더의 html 파일을 Controller를 통해서 처리하고 싶다면 redirect 요청을 문자열로 반환 시 http://localhost:8080/hello.html 요청이 재수행되면서 static 폴더의 파일을 반환할 수 있음
@GetMapping("/html/redirect")
public String htmlStatic() {
return "redirect:/hello.html";
}
- Template engine 에 View 전달 : 브라우저에서 바로 접근하지 못하게 하고 싶거나 특정 상황에 Controller를 통해서 제어하고 싶은 경우, templates 폴더에 해당 정적 html 파일을 추가하고 해당 html 파일명인 "hello" 문자열을 반환하여 처리할 수 있음
@GetMapping("/html/templates")
public String htmlTemplates() {
return "hello";
}
동적페이지 처리
private static long visitCount = 0;
...
@GetMapping("/html/dynamic")
public String htmlDynamic(Model model) {
visitCount++;
model.addAttribute("visits", visitCount);
return "hello-visit";
}
- Client 의 요청을 Controller에서 Model 로 처리
- DB 조회가 필요하다면 DB 작업 후 처리한 데이터를 Model에 저장
- Template engine(Thymeleaf) 에게 View, Model 전달
- View: 동적 HTML 파일 / Model: View 에 적용할 정보들
- Template engine
- View에 Model을 적용 → 동적 웹페이지 생성
- 예) 로그인 성공 시, "로그인된 사용자의 Nickname"을 페이지에 추가
- View에 Model을 적용 → 동적 웹페이지 생성
- Client(브라우저)에게 View(동적 웹 페이지, HTML)를 전달
동적 페이지 처리 예제
package com.sparta.springmvc.html;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HtmlController {
private static long visitCount = 0;
@GetMapping("/static-hello")
public String hello() {
return "hello.html";
}
@GetMapping("/html/redirect")
public String htmlStatic() {
return "redirect:/hello.html";
}
@GetMapping("/html/templates")
public String htmlTemplates() {
return "hello";
}
@GetMapping("/html/dynamic")
public String htmlDynamic(Model model) {
visitCount++;
model.addAttribute("visits", visitCount);
return "hello-visit";
}
}
데이터를 Client 반환
- JSON 데이터 반환하는 방법
- 템플릿 엔진이 적용된 SpringBoot에서는 Controller에서 문자열을 반환하면 templates 폴더에서 해당 문자열의 .html 파일을 찾아서 반환
- html 파일이 아닌 JSON 데이터를 브라우저에 반환하고 싶다면 해당 메서드에 @ResponseBody 애너테이션을 추가
JSON 데이터 반환 예제
package com.sparta.springmvc.response;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/response")
public class ResponseController {
// [Response header]
// Content-Type: text/html
// [Response body]
// {"name":"Robbie","age":95}
@GetMapping("/json/string")
@ResponseBody
public String helloStringJson() {
return "{\"name\":\"Robbie\",\"age\":95}";
}
// [Response header]
// Content-Type: application/json
// [Response body]
// {"name":"Robbie","age":95}
@GetMapping("/json/class")
@ResponseBody
public Star helloClassJson() {
return new Star("Robbie", 95);
}
}
Star 클래스
package com.sparta.springmvc.response;
import lombok.Getter;
@Getter
public class Star {
String name;
int age;
public Star(String name, int age) {
this.name = name;
this.age = age;
}
public Star() {}
}
@RestController
- 해당 클래스의 모든 메서드에 @ResponseBody 애너테이션이 추가되는 효과를 부여할 수 있음
package com.sparta.springmvc.response;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/response/rest")
public class ResponseRestController {
// [Response header]
// Content-Type: text/html
// [Response body]
// {"name":"Robbie","age":95}
@GetMapping("/json/string")
public String helloStringJson() {
return "{\"name\":\"Robbie\",\"age\":95}";
}
// [Response header]
// Content-Type: application/json
// [Response body]
// {"name":"Robbie","age":95}
@GetMapping("/json/class")
public Star helloClassJson() {
return new Star("Robbie", 95);
}
}
Jackson
- Jackson은 JSON 데이터 구조를 처리해주는 라이브러리
- Object를 JSON 타입의 String으로 변환
- JSON 타입의 String을 Object로 변환
Jackson 사용 예제
package com.sparta.springmvc;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sparta.springmvc.response.Star;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class JacksonTest {
@Test
@DisplayName("Object To JSON : get Method 필요")
void test1() throws JsonProcessingException {
Star star = new Star("Robbie", 95);
ObjectMapper objectMapper = new ObjectMapper(); // Jackson 라이브러리의 ObjectMapper
String json = objectMapper.writeValueAsString(star);
System.out.println("json = " + json);
}
@Test
@DisplayName("JSON To Object : 기본 생성자 & (get OR set) Method 필요")
void test2() throws JsonProcessingException {
String json = "{\"name\":\"Robbie\",\"age\":95}"; // JSON 타입의 String
ObjectMapper objectMapper = new ObjectMapper(); // Jackson 라이브러리의 ObjectMapper
Star star = objectMapper.readValue(json, Star.class);
System.out.println("star.getName() = " + star.getName());
System.out.println("star.getAge() = " + star.getAge());
}
}
Path Variable / Request Param
- Path Variable
- 서버에 보내려는 데이터를 URL 경로에 추가할 수 있음
- Request Param
- 서버에 보내려는 데이터를 URL 경로 마지막에 ? 와 & 를 사용하여 추가할 수 있음
사용 예제
package com.sparta.springmvc.request;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/hello/request")
public class RequestController {
@GetMapping("/form/html")
public String helloForm() {
return "hello-request-form";
}
// [Request sample]
// GET http://localhost:8080/hello/request/star/Robbie/age/95
@GetMapping("/star/{name}/age/{age}")
@ResponseBody
public String helloRequestPath(@PathVariable String name, @PathVariable int age)
{
return String.format("Hello, @PathVariable.<br> name = %s, age = %d", name, age);
}
// [Request sample]
// GET http://localhost:8080/hello/request/form/param?name=Robbie&age=95
@GetMapping("/form/param")
@ResponseBody
public String helloGetRequestParam(@RequestParam String name, @RequestParam int age) {
return String.format("Hello, @RequestParam.<br> name = %s, age = %d", name, age);
}
// [Request sample]
// POST http://localhost:8080/hello/request/form/param
// Header
// Content type: application/x-www-form-urlencoded
// Body
// name=Robbie&age=95
@PostMapping("/form/param")
@ResponseBody
public String helloPostRequestParam(@RequestParam String name, @RequestParam int age) {
return String.format("Hello, @RequestParam.<br> name = %s, age = %d", name, age);
}
}
HTTP 데이터 객체로 처리
- HTML의 form 태그를 사용하여 POST 방식으로 HTTP 요청을 보낼 수 있음
- 이때 해당 데이터는 HTTP Body에 name=Robbie&age=95 형태로 담겨져서 서버로 전달
- 해당 데이터를 Java의 객체 형태로 받는 방법은 @ModelAttribute 애너테이션을 사용한 후 Body 데이터를 Star star 받아올 객체를 선언
package com.sparta.springmvc.request;
public class Star {
String name;
int age;
public Star(String name, int age) {
this.name = name;
this.age = age;
}
}
// [Request sample]
// POST http://localhost:8080/hello/request/form/model
// Header
// Content type: application/x-www-form-urlencoded
// Body
// name=Robbie&age=95
@PostMapping("/form/model")
@ResponseBody
public String helloRequestBodyForm(@ModelAttribute Star star) {
return String.format("Hello, @ModelAttribute.<br> (name = %s, age = %d) ", star.name, star.age);
}
// [Request sample]
// GET http://localhost:8080/hello/request/form/param/model?name=Robbie&age=95
@GetMapping("/form/param/model")
@ResponseBody
public String helloRequestParam(@ModelAttribute Star star) {
return String.format("Hello, @ModelAttribute.<br> (name = %s, age = %d) ", star.name, star.age);
}
// [Request sample]
// POST http://localhost:8080/hello/request/form/json
// Header
// Content type: application/json
// Body
// {"name":"Robbie","age":"95"}
@PostMapping("/form/json")
@ResponseBody
public String helloPostRequestJson(@RequestBody Star star) {
return String.format("Hello, @RequestBody.<br> (name = %s, age = %d) ", star.name, star.age);
}
Create, Read
- Create 구현
- 메모 데이터를 저장할 Memo 클래스 생성
- 메모 생성하기 API를 받을 수 있는 Controller와 메서드 생성
- Client에 데이터를 반환할 때 사용할 MemoResponseDto 클래스 생성
- Client의 요청 데이터를 받아줄 MemoRequestDto 클래스 생성
- DB와 연결을 하지 않았기 때문에 메모 데이터를 저장할 Java 컬렉션 생성
- 메모 생성 로직 작성
- Read 구현
- DB 역할을 하는 memoList를 조회하여 List<MemoResponseDto>로 변환한 후 반환
구현 예제
Memo 클래스
package com.sparta.memo.entity;
import com.sparta.memo.dto.MemoRequestDto;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public class Memo {
private Long id;
private String username;
private String contents;
public Memo(MemoRequestDto requestDto) {
this.username = requestDto.getUsername();
this.contents = requestDto.getContents();
}
}
Memo Controller
package com.sparta.memo.controller;
import com.sparta.memo.dto.MemoRequestDto;
import com.sparta.memo.dto.MemoResponseDto;
import com.sparta.memo.entity.Memo;
import org.springframework.web.bind.annotation.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class MemoController {
private final Map<Long, Memo> memoList = new HashMap<>();
@PostMapping("/memos")
public MemoResponseDto createMemo(@RequestBody MemoRequestDto requestDto) {
// RequestDto -> Entity
Memo memo = new Memo(requestDto);
// Memo Max ID Check
Long maxId = memoList.size() > 0 ? Collections.max(memoList.keySet()) + 1 : 1;
memo.setId(maxId);
// DB 저장
memoList.put(memo.getId(), memo);
// Entity -> ResponseDto
MemoResponseDto memoResponseDto = new MemoResponseDto(memo);
return memoResponseDto;
}
@GetMapping("/memos")
public List<MemoResponseDto> getMemos() {
// Map To List
List<MemoResponseDto> responseList = memoList.values().stream()
.map(MemoResponseDto::new).toList();
return responseList;
}
}
DTO
package com.sparta.memo.dto;
import lombok.Getter;
@Getter
public class MemoRequestDto {
private String username;
private String contents;
}
package com.sparta.memo.dto;
import com.sparta.memo.entity.Memo;
import lombok.Getter;
@Getter
public class MemoResponseDto {
private Long id;
private String username;
private String contents;
public MemoResponseDto(Memo memo) {
this.id = memo.getId();
this.username = memo.getUsername();
this.contents = memo.getContents();
}
}
Update, Delete
구현 예제
Memo 클래스 - update 추가
public void update(MemoRequestDto requestDto) {
this.username = requestDto.getUsername();
this.contents = requestDto.getContents();
}
MemoController - update, delete 추가
@PutMapping("/memos/{id}")
public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) {
// 해당 메모가 DB에 존재하는지 확인
if (memoList.containsKey(id)) {
Memo memo = memoList.get(id);
// memo 수정
memo.update(requestDto);
return memo.getId();
} else {
throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
}
}
@DeleteMapping("/memos/{id}")
public Long deleteMemo(@PathVariable Long id) {
// 해당 메모가 DB에 존재하는지 확인
if (memoList.containsKey(id)) {
// 해당 메모를 삭제하기
memoList.remove(id);
return id;
} else {
throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
}
}
JDBC
- JDBC는 Java Database Connectivity로 DB에 접근할 수 있도록 Java에서 제공하는 API
- JDBC에 연결해야하는 DB의 JDBC 드라이버를 제공하면 DB 연결 로직을 변경할 필요없이 DB 변경이 가능
- MySQL 드라이버를 사용해 DB에 연결을 하다 PostgreSQL 서버로 변경이 필요할 때 드라이버만 교체하면 손쉽게 DB 변경이 가능
JDBC Template
- JDBC의 등장으로 손쉽게 DB교체가 가능해졌지만 아직도 DB에 연결하기 위해 여러가지 작업 로직들을 직접 작성해야한다는 불편함이 남음
- 이러한 불편함을 해결하기 위해 커넥션 연결, statement 준비 및 실행, 커넥션 종료 등의 반복적이고 중복되는 작업들을 대신 처리해주는 JdbcTemplate이 등장
사용방법
- application.properties에 DB에 접근하기 위한 정보를 작성
- build.gradle에 JDBC 라이브러리와 MySQL을 등록
- DB연결이 필요한 곳에서 JdbcTemplate을 주입받아와 사용
application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/memo
spring.datasource.username=root
spring.datasource.password=zaq123
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
build.gradle dependencies
// MySQL
implementation 'mysql:mysql-connector-java:8.0.28'
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
MemoController
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.web.bind.annotation.*;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
@RestController
@RequestMapping("/api")
public class MemoController {
private final JdbcTemplate jdbcTemplate;
public MemoController(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@PostMapping("/memos")
public MemoResponseDto createMemo(@RequestBody MemoRequestDto requestDto) {
// RequestDto -> Entity
Memo memo = new Memo(requestDto);
// DB 저장
KeyHolder keyHolder = new GeneratedKeyHolder(); // 기본 키를 반환받기 위한 객체
String sql = "INSERT INTO memo (username, contents) VALUES (?, ?)";
jdbcTemplate.update( con -> {
PreparedStatement preparedStatement = con.prepareStatement(sql,
Statement.RETURN_GENERATED_KEYS);
preparedStatement.setString(1, memo.getUsername());
preparedStatement.setString(2, memo.getContents());
return preparedStatement;
},
keyHolder);
// DB Insert 후 받아온 기본키 확인
Long id = keyHolder.getKey().longValue();
memo.setId(id);
// Entity -> ResponseDto
MemoResponseDto memoResponseDto = new MemoResponseDto(memo);
return memoResponseDto;
}
@GetMapping("/memos")
public List<MemoResponseDto> getMemos() {
// DB 조회
String sql = "SELECT * FROM memo";
return jdbcTemplate.query(sql, new RowMapper<MemoResponseDto>() {
@Override
public MemoResponseDto mapRow(ResultSet rs, int rowNum) throws SQLException {
// SQL 의 결과로 받아온 Memo 데이터들을 MemoResponseDto 타입으로 변환해줄 메서드
Long id = rs.getLong("id");
String username = rs.getString("username");
String contents = rs.getString("contents");
return new MemoResponseDto(id, username, contents);
}
});
}
@PutMapping("/memos/{id}")
public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) {
// 해당 메모가 DB에 존재하는지 확인
Memo memo = findById(id);
if(memo != null) {
// memo 내용 수정
String sql = "UPDATE memo SET username = ?, contents = ? WHERE id = ?";
jdbcTemplate.update(sql, requestDto.getUsername(), requestDto.getContents(), id);
return id;
} else {
throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
}
}
@DeleteMapping("/memos/{id}")
public Long deleteMemo(@PathVariable Long id) {
// 해당 메모가 DB에 존재하는지 확인
Memo memo = findById(id);
if(memo != null) {
// memo 삭제
String sql = "DELETE FROM memo WHERE id = ?";
jdbcTemplate.update(sql, id);
return id;
} else {
throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
}
}
private Memo findById(Long id) {
// DB 조회
String sql = "SELECT * FROM memo WHERE id = ?";
return jdbcTemplate.query(sql, resultSet -> {
if(resultSet.next()) {
Memo memo = new Memo();
memo.setUsername(resultSet.getString("username"));
memo.setContents(resultSet.getString("contents"));
return memo;
} else {
return null;
}
}, id);
}
}
MemoResponseDto
package com.sparta.memo.dto;
import com.sparta.memo.entity.Memo;
import lombok.Getter;
@Getter
public class MemoResponseDto {
private Long id;
private String username;
private String contents;
public MemoResponseDto(Memo memo) {
this.id = memo.getId();
this.username = memo.getUsername();
this.contents = memo.getContents();
}
public MemoResponseDto(Long id, String username, String contents) {
this.id = id;
this.username = username;
this.contents = contents;
}
}
예제 파일
'항해 99 > Spring' 카테고리의 다른 글
Spring - JPA Entity 연관 관계 (1) | 2024.02.27 |
---|---|
Spring - Bean, 로그인/회원가입, Security, Validation (1) | 2024.02.26 |
RESTful API, 관심사 분리, @Setter 지양 (0) | 2024.02.25 |
프로젝트 세팅 - UCD, API 명세서, ERD, Git 연동 (0) | 2024.02.22 |
Spring - 입문 2 (0) | 2024.02.21 |