관리 메뉴

CASSIE'S BLOG

93강 web layered 살펴보기 + postman 실습 very impo 본문

PROGRAMMING/슈퍼코딩 강의 정리

93강 web layered 살펴보기 + postman 실습 very impo

ITSCASSIE1107 2024. 1. 2. 17:59


3 layered 매우중요한 개념임

제일 윗 계층인

Presentation Layer

Spring Boot에서 @RequestMapping 어노테이션은 특정 메서드나 컨트롤러 클래스에 HTTP 요청을 매핑하는 데 사용됩니다. 주로 RESTful API 엔드포인트를 정의할 때 활용되며, 요청의 경로, 메서드, 헤더 등을 기반으로 어떤 메서드가 실행될지를 결정합니다.

@RestController
컨트롤러 클래스는 웹 애플리케이션에서 사용자의 요청을 받아서 적절한 응답을 생성하는 역할을 하는 것이에요.

RestController 어노테이션은 Spring에서 RESTful 웹 서비스의 컨트롤러 클래스를 정의할 때 사용됩니다. 이 어노테이션을 사용하면 해당 클래스의 모든 메서드의 반환 값이 HTTP 응답으로 처리되고, 각 메서드에 @RequestMapping 등의 어노테이션을 사용하여 엔드포인트를 매핑할 수 있습니다. 결국, @RestController는 @Controller와 @ResponseBody를 합쳐놓은 것으로 볼 수 있습니다.

@Controller vs @RestController

@Controller는 HTML 페이지를 반환하는 데 사용되고, @RestController는 주로 JSON이나 XML과 같은 데이터를 반환합니다. 즉, @RestController는 데이터 전용이고, @Controller는 웹 페이지를 보여줄 때 사용됩니다.
 


🎈RestController는 직렬화/역직렬화 라이브러리까지 사용한다. 이게 중요개념이래
 

 

 
DTO가 외부에서만 통신하는게 아니라
내부에서도 계층을 Web Layer, Service Layer, Repository Layer 이렇게 3가지가 있는데
Layer끼리도 돌아가는거를 DTOP라고도 한단다.
 
🎈DTO 
-외부 통신 DTO 
-내부 통신 DTO
 
실습은 Dispatcher Servlet과 소통하는 외부통신DTO를 살펴볼 예정
 
 

 
🎈 역직렬화
Dispatcher Servlet에서 json이 왔을 때 그 요청을 다시 Java Class로 만드는 것 이건 역직렬화
 
DTO 대표적인 구현의 느낌 이 두가지를 만족을 해야 DTO다.
 
🎈DTO요건 
빈생성자 (아무 매개변수도 없는 빈생성자가 있어야함)
각각의 필드들은 Getter 가 있어야한다.
 
 

chapter_94로 postman으로 컬렉션 만들어서 진행함
포트번호 다 맞춰서 요청하면 됨
http://localhost:8080/api/items

처음에 get_all 하면 빈배열 밖에 안 나옴
new ArrayList<>();

이것만 했으니까

Dto로 json으로 들어오는 것 -> @RequestBody 어노테이션으로 받는다는데? 이 어노테이션 뒤에 dto를 정의하면 됨
들어오는건 요청 dto고 응답 되는건 응답 dto라고 함

Item은 지금 item까지 포함임
들어오는건 name부터 시작임
id가 없기때문에 따로 정의할 필요가 있다.
dto폴더에서 ItemBody 클래스를 정의한다.
dto는 constructor가 빈 constructor라고함
그리고 필드별로 getter 있어야함

Return값이 “id = 7” 이거니까

@PostMapping(“/items”)
public String register(@RequestBody ItemBody itemBody ){
    Item item = new Item(serialItemId, itemBody
}
그러면 RequestBody에 금방 추가한 ItemBody 클래스 추가하면 됨

그러면 json이 들어오면 직렬화해서 받는거라고 함

이걸 또 추가
private static int SerialItemId = 1;


Item의 생성자도 추가하는데

Integer Id 이것도 매개변수에 추가
*get필드값 이런 식으로 생성자

public Item (Integer Id, Itembody itembody) {
this.id = String.valueOf(Id);
this.name= itemBody.getName();
this.type= itemBody.getType();
this.price= itemBody.getPrice();
this.spec= itemBody.getSpec();
}

그러면 id 들어온것도 받아서 만들어주나본데?

ItemBody랑 Item이랑 다름


Post로 추가한 뒤에

GetMapping(“items”)
public List<Item> findAllItems(){
    return items
}

Id Path로 id 조회하기

Url Path로 할때 mapping 주소에 중괄호 넣어주기, url 주소에서 id 가져올거라서 @PathVariable



@GetMapping("/items/{id}")
public Item findItemByPathId(@PathVariable String id) {
    Item itemFounded = Items.stream()
                            .filter(item -> item.getId().equals(id))
                            .findFirst()
                            .orElseThrow(() -> new RuntimeException("Item not found"));
    return itemFounded;
}



@GetMapping("/items/{id}"): 이 어노테이션은 HTTP GET 요청을 처리하는 메서드임을 나타냅니다. {id}는 경로 변수로, 실제 요청 시 제공된 값으로 대체됩니다.
public Item findItemByPathId(@PathVariable String id): 이 메서드는 경로 변수로 전달된 id를 받아들이고, 해당 ID를 사용하여 아이템을 찾는 역할을 합니다.
Item itemFounded = Items.stream()...: Items라는 컬렉션(아이템 목록)을 스트림으로 변환합니다. 이를 통해 함수형 프로그래밍의 장점을 활용할 수 있습니다.
.filter(item -> item.getId().equals(id)): 스트림에서 필터링을 수행하여 주어진 id와 일치하는 아이템을 찾습니다.
.findFirst(): 필터링된 결과 중 첫 번째 아이템을 찾습니다.
.orElseThrow(() -> new RuntimeException("Item not found")): 만약 찾은 아이템이 없으면, RuntimeException을 던져서 예외를 발생시킵니다. 이때 메시지로 "Item not found"를 포함한 예외가 생성됩니다.
return itemFounded;: 최종적으로 찾은 아이템을 반환합니다.

filter 함수를 사용하기 전에 데이터를 stream()으로 변환합니다. stream()은 컬렉션을 스트림으로 바꾸어 다양한 중간 연산을 적용할 수 있는 기반을 제공합니다.


stream()을 빼면 기존의 리스트나 컬렉션에서 일반적인 반복문을 사용하여 원하는 아이템을 찾아야 합니다.


@GetMapping("/items/{id}")
public Item findItemByPathId(@PathVariable String id) {
    for (Item item : Items) {
        if (item.getId().equals(id)) {
            return item;
        }
    }
    throw new RuntimeException("Item not found");
}

Arrays.asList 로 바꿔서 하면 쉽대

25:25부분 다 다시 Item객체
만드니까 보면서 실습

그냥

new RuntimeException(); 이것만 해도 되긴 됨

get.query:
물음표가 쿼리 파라미터임

Get은 물음표를 치고 특정 쿼리를 날리는 게 된다함

이건 RequestParam이란 어노테이션씀

만약에 HTTP요청이
GET /api/items-query?id=1

@GetMapping(“items-query”)
public Item findItemByQueryId(@RequestParam(“id”) String id){

Item itemFounded = Items.stream()
                            .filter(item -> item.getId().equals(id))
                            .findFirst()
                            .orElseThrow(() -> new RuntimeException("Item not found"));
    return itemFounded;

}

@RequestParam 어노테이션 넣고 “id” 이런 식으로 쌍따옴표 넣어서 쿼리
넣으면 된다함 그리고 그 안에 또 String id 넣어야함
다른 건 받는 방식 다 똑같음




실습내용


 

 

 
Java 인메모리 List - > 마치 그냥 db 흉내내는 것처럼 해본다고함.
 
 
 
92강 프로젝트 셋팅하는거해야지
93강으로 넘어갈 수 있음

자동으로 만들어준다. 실행클래스는
만약에 프로젝트 이름이
super-coding-textBook이면
자동으로 실행 클래스를 만들어준다.

SuperCodingTextBookApplication을 만들어준다.

Spring boot에서 실행은 스프링을 실행시키기 보다 내장 톰캣을 실행하는 것에 가깝다.

옛날에 스프링에서는 스프링과 톰캣이 따로 있어서 두번 실행 버튼을 눌렀어야했는데
스프링부트는 내장 톰캣이 있어서 실행할 때 톰캣이 실행되면서 스프링 컨테이너가 자동으로 실행된다.

properties파일을 yaml파일로 바꼈는데
이제부터 yaml파일만 쓸거라고 함
가독성과 Profile 사용하기 용이하다함

실제에서는 개발단계에서 Profile 분리 필요함

local-dev-prod

각각 yaml 설정을 다르게 할 수 있나봐

application-dev.yaml

application-local.yaml

application-prod.yaml

우리는 주로 local를 쓴다.

여기서 설정을 다 따로 하면 각각 다른 포트로 돌아가나봐

망치 옆에 edit configurations 하면

여기에 active profiles 쪽에서

거기에 local이라고 수기에 적고 apply하면

prod라고 수기로 적으면 prod 에 설정한 port 번호로 돌아간다.

파일 날짜별 정리 & 파일 주제별 정리처럼 파일 정리 방법이 다른 것처럼 코드도 아키텍쳐 짜는게 다름

우리는 3 Layer Architecture 식으로 코드 짤거라고 함

3 Layer Architecture (= 3 tier Architecture)

Web Layer (= Presentation Layer)
특징: 외부 요청/응답 접점 위치


Service Layer
특징: 실제 비즈니스 로직 내부 처리 담당

Repository Layer
특징: 데이터베이스와 상호작용하는 위치

쓰는 이유:
외부 요청 수정 시,
Web Layer 하위 코드만 수정
(ex) Http Url 변경

중요한 비즈니스 로직 변경 시
Service Layer 하위 코드 수정
(ex) 티켓 할인률 변경

DB 관련 요청 시
Repository Layer 하위 코드 수정

web 패키지 언더에 dto, controller 패키지 따로 만들어줌.
 
 
Rest API는 api라고 테스트를 하고 시작하는게 좋다고해서
RequestMapping("/api") 까지 어노테이션을 따로 붙인다. 


 
 Get all 부터 시작
 
 

 
이거 응답본문 보면 [] 안에 다 item이잖아
그래서 클래스를 만들어야함. - > 이건 dto 만들예정이고 이것들은 다 json으로 바껴야함.
 
필드안에 또 새로운 brace를 여는 필드가 있으면 dto를 또 따로 만들어줘야한다고함. 
 

package com.github.supercoding.web.dto;

public class Item {


private String id;
private String name;
private String type;
private Integer price;
private Spec spec;

}

 

package com.github.supercoding.web.dto;

public class Spec {
private String cpu;
private String capacity;
}

 

 
 

 
json으로 직렬화가 되는 dto잖아요
그래서 constructor가 있어야함. (빈 constructor) 
정의한 각각의 field에 대한 getter가 있어야함.
 

package com.github.supercoding.web.dto;

public class Item {


private String id;
private String name;
private String type;
private Integer price;
private Spec spec;

public Item() {
}

public String getId() {
return id;
}

public String getName() {
return name;
}

public String getType() {
return type;
}

public Integer getPrice() {
return price;
}

public Spec getSpec() {
return spec;
}
}

 
1related problem클릭하면 어떤 문제인지 따라갈 수 있다.
 
Spec도 dto안에 dto이기 때문에 빈 constructor, 필드별 getter 정의해줘야함.
 
지금은 db랑 연결이 안되어있어서
위에 db처럼 List를 만들어놓고
db로 사용해본다고함. 
 

 

 
 

 
여러 요청을 많이 만들거라서 postman 컬렉션을 따로 별도로 만들예정
 

 

 

 
이제 생성하는거 post를 해본다고함.
 
지금 post 보면 return 값이 "ID: 7" 이런 식으로 String으로 반환됨 
 

 
여기서가 중요한데 post잖아요
어노테이션을 postMapping을 해줘야함 
 
request값이 빨갛게 표시한 부분인데 이게 dto로 json으로 요청이 들어오는건데
이런거는 어떻게 받냐 RequestBody라는 어노테이션을 사용하고 그 뒤에 dto를 정의하면 된다.
 
findAllitem에서는 여기서 갖고온거는 외부통신에서 응답 dto라고 하기도한다. 
registerItem에서 들어오는거는 요청 dto라고한다. 
 
그래서 우리는 dto를 새로 정의해야한단다.
왜냐면 item 클래스를 가보면 id까지 포함해서 name, type, price, spec이 있는데
지금 들어오는거에는 id가 없잖아 name부터 시작하잖아
 

 
그래서 클래스를 따로 만들어야한다 ItemBody 클래스로 또 만듬

 
🎈@RequestBody 어노테이션은 HTTP 요청의 본문(body)을 자바 객체로 변환해주는 역할을 합니다. 🎈

메서드 선언에서 @RequestBody ItemBody itemBody라고 명시하는 것은, 🎈HTTP 요청의 본문을 받아와서 이를 ItemBody 타입의 자바 객체로 변환하여 itemBody 변수에 할당🎈하겠다는 의미입니다. 여기서 itemBody는 개발자가 정한 변수 이름으로, 이후 메서드 내에서 이 변수를 활용할 수 있게 됩니다.
 
아이템필드들이 json이 들어오면 역직렬화해서 우리가 받는거다. 
 

 
db에는 Autoincrement 그런게 있을텐데 지금 여기에는 그런게 없으니까 (뭐 추적하는 필드 하나 있어야한다고했잖아) 
 

package com.github.supercoding.web.controller;

import com.github.supercoding.web.dto.Item;
import com.github.supercoding.web.dto.ItemBody;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;


@RestController
@RequestMapping("/api")
public class ElectronicStoreController {

private static int serialItemId = 1;
private List<Item> items = new ArrayList<>();


@GetMapping("/items")
public List<Item> findAllItem (){
return items;
}

@PostMapping("/items")
public String registerItem(@RequestBody ItemBody){

}
}

 
itemBody를 받아서 Item를 만들어서 저장을 해야한다.
 

package com.github.supercoding.web.controller;

import com.github.supercoding.web.dto.Item;
import com.github.supercoding.web.dto.ItemBody;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;


@RestController
@RequestMapping("/api")
public class ElectronicStoreController {

private static int serialItemId = 1;
private List<Item> items = new ArrayList<>();


@GetMapping("/items")
public List<Item> findAllItem (){
return items;
}

@PostMapping("/items")
public String registerItem(@RequestBody ItemBody){
Item item = new Item (serialItemId++, )

}
}

 
여기서 지금 Item에서 필드들 들어가는 constructor를 추가를 해야한다
id, name, type, price,spec 필드 다 적용함.
 
근데 이런 식으로 만든다. 
 
 

public Item (Integer id, ItemBody itemBody) {
this.id = String.valueOf(id);
this.name = itemBody.getName();
this.type = itemBody.getType();
this.price = itemBody.getPrice();
this.spec = itemBody.getSpec();
}

 
 
serialItemIdint 타입이기 때문에 Integer로 받아오게 되었습니다. intInteger는 Java에서 기본 데이터 타입과 그에 대응하는 래퍼(wrapper) 클래스입니다.
 
Item 클래스의 생성자에서 Integer id로 선언한 이유는 주로 null을 허용하거나 기본값을 설정하기 위함입니다.
🎈만약 int id로 선언한다면 null을 받을 수 없으며, 초기값을 설정할 수 없습니다. 그리고 기본값으로 0이 들어가게 됩니다. 이런 경우, 0은 유효한 ID 값으로 인식되기 때문에 null과 유효한 ID를 구분하기가 어려워집니다.🎈
따라서 Integer를 사용하면 null을 받을 수 있기 때문에 ID 값이 없는 경우를 나타내기에 유용합니다. 이런 선택은 프로그램의 요구사항과 설계에 따라 다르며, 가장 적절한 선택을 하는 것이 중요합니다.
 
 
🎈 Wrapper 클래스는 그러면 null이면 null pointer exception을 만들어내는거지?🎈
 
Wrapper 클래스의 인스턴스가 null인 상태에서 해당 인스턴스의 메서드나 필드에 접근하려고 시도하면, NullPointerException이 발생할 수 있습니다. 이는 기본 자료형(primitive type)과는 다르게 객체 참조 변수가 null을 가질 수 있기 때문입니다.
예를 들어, Integer 객체가 null일 때 해당 객체의 메서드나 필드에 접근하면 NullPointerException이 발생할 수 있습니다. 따라서 Wrapper 클래스를 사용할 때는 반드시 null 여부를 체크하고 안전하게 처리하는 것이 중요합니다.
 
 

package com.github.supercoding.web.dto;

public class Item {

private String id;
private String name;
private String type;
private Integer price;
private Spec spec;

public Item() {
}

public Item (Integer id, ItemBody itemBody) {
this.id = String.valueOf(id);
this.name = itemBody.getName();
this.type = itemBody.getType();
this.price = itemBody.getPrice();
this.spec = itemBody.getSpec();
}

public String getId() {
return id;
}

public String getName() {
return name;
}

public String getType() {
return type;
}

public Integer getPrice() {
return price;
}

public Spec getSpec() {
return spec;
}
}

 
🎈이 Item 안에 컨스럭터는 만들어주면 드디어 serialItemId랑 ItemBody를 쓸 수 있게 된다. 🎈
 
 

 
    public Item (Integer id, ItemBody itemBody) {
        this.id = String.valueOf(id);
        this.name = itemBody.getName();
        this.type = itemBody.getType();
        this.price = itemBody.getPrice();
        this.spec = itemBody.getSpec();
    }
이렇게 했잖아 그러면 String.valueOf(id) 여기서 id는 Wrapper 클래스인 Integer id가 들어가도 되고 int id가 들어가
 
 

네, 맞습니다. String.valueOf(id)에서 id는 Wrapper 클래스인 Integer일수도 있고, 기본 자료형인 int일 수도 있습니다. String.valueOf 메서드는 매개변수로 받은 값을 문자열로 변환하여 반환하는데, 이때 기본 자료형이나 Wrapper 클래스 모두 처리할 수 있습니다. 반환형은 언제나 String입니다.

Java의 오토박싱(auto-boxing)과 오토언박싱(auto-unboxing) 개념에 따라 기본 자료형과 그에 대응하는 Wrapper 클래스 간에 자동으로 변환되기 때문에 편리하게 사용할 수 있습니다. 따라서 String.valueOf(id)에서 id가 Integer나 int 중 어느 것이든 상관없이 동작합니다.

 
🎈오토박싱과 오토언박싱
 

package com.github.supercoding.web.controller;

import com.github.supercoding.web.dto.Item;
import com.github.supercoding.web.dto.ItemBody;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;


@RestController
@RequestMapping("/api")
public class ElectronicStoreController {

private static int serialItemId = 1;
private List<Item> items = new ArrayList<>();


@GetMapping("/items")
public List<Item> findAllItem (){
return items;
}

@PostMapping("/items")
public String registerItem(@RequestBody ItemBody itemBody){
Item newItem = new Item (serialItemId++, itemBody );
items.add(newItem);
return "ID: " + newItem.getId();
}
}

 
 

 
 

 

 
요청 내용:

{
    "name": "Apple iPhone 12 Pro",
    "type": "스마트폰",
    "price": 1490000,
    "spec": {
        "cpu": "A14 Bionic",
        "capacity": "512GB"
    }


}

 
 

 
 
🎈 요청 보낼 때 raw 체크해서 json으로 보낼 것 🎈

 
 

 
itemPath로 아이템을 조회하는 것

 
return값이 그대로 item과 똑같아 보여서
 
여기서중요한데 값이 1일수도 있고 2일수도 있어서 {} 이렇게 해서 변수를 받아준다. 
 
그리고 매개변수에 anotation도 PathVariable 쓴다. 
 
그냥 items 에서 얻어오면 되는거잖아 
 
했던거라는데 stream으로 먼저 빼는 이유가 뭐야? (이 부분 강의 안 들은 것 같은데 넘긴 듯)
 
findFirst 찾으면은  이게 optional로 return이 되서 OrelseThrow()
못찾으면은 에러를 던져야한다고함 new RuntimeException()
 
🎈 stream메소드 사용하는 이유 🎈
 
stream을 사용하면 메소드를 연속적으로 호출할 수 있어서 코드의 가독성이 높아집니다. 몇 가지 주요 장점은 다음과 같습니다:

  1. 가독성 향상: stream을 사용하면 연산을 체이닝하여 사용할 수 있어 코드가 간결해지고 가독성이 향상됩니다. 각 단계의 연산이 한 줄에 표현되므로 코드를 더 쉽게 이해할 수 있습니다.
  2. 코드 중복 감소: 스트림을 사용하면 루프나 조건문 등을 명시적으로 작성할 필요가 없어져서 코드 중복이 감소합니다.
  3. 함수형 프로그래밍 지원: 스트림은 함수형 프로그래밍의 개념을 지원하며, 불변성(immutability)을 유지하고 병렬 처리를 쉽게 구현할 수 있습니다.
  4. 코드 재사용성: 스트림을 사용하면 다양한 연산을 적용하여 재사용성이 높은 코드를 작성할 수 있습니다.
  5. 병렬 처리 지원: 스트림은 내부적으로 병렬 처리를 지원하므로 대용량 데이터의 처리에서 성능을 향상시킬 수 있습니다.

한편, 반복문은 직접적으로 루프를 제어하고 명시적으로 작성해야 하므로 가독성이 떨어질 수 있습니다. 또한, 코드의 길이가 길어지고 중복이 발생할 가능성이 높습니다. 함수형 프로그래밍의 아이디어를 활용하여 더
 

🎈메소드 체이닝🎈
"체이닝"은 메소드 호출을 연이어서 이어가는 것을 의미합니다. 스트림 API에서는 여러 메소드를 연달아 호출하여 사용할 수 있는데, 각 메소드는 새로운 스트림을 반환합니다. 그리고 이렇게 반환된 스트림을 이용해 또 다른 메소드를 호출하는 방식입니다.
 
 

package com.github.supercoding.web.controller;

import com.github.supercoding.web.dto.Item;
import com.github.supercoding.web.dto.ItemBody;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;


@RestController
@RequestMapping("/api")
public class ElectronicStoreController {

private static int serialItemId = 1;
private List<Item> items = new ArrayList<>();


@GetMapping("/items")
public List<Item> findAllItem (){
return items;
}

@PostMapping("/items")
public String registerItem(@RequestBody ItemBody itemBody){
Item newItem = new Item (serialItemId++, itemBody );
items.add(newItem);
return "ID: " + newItem.getId();
}

@GetMapping("/items/{id}")
public Item fineItemByPathId(@PathVariable String id ){
Item itemfounded = items.stream()
.filter((item) -> item.getId().equals(id))
.findFirst()
.orElseThrow(()-> new RuntimeException());
return itemfounded;
}
}

 
🎈Optional은 null를 대비한 것
 
findFirst() 메소드는 스트림에서 첫 번째로 발견되는 요소를 Optional으로 감싼 채 반환합니다. 이는 찾은 요소가 없을 경우를 대비하여 안전하게 처리하기 위한 방법 중 하나입니다.

Optional은 Java에서 Null 값에 대한 대안으로 도입된 클래스로, 값이 있을 수도 있고 없을 수도 있는 상황을 나타냅니다. 이를 통해 코드에서 명시적으로 처리를 하게 되므로, Null에 의한 예상치 못한 오류를 방지할 수 있습니다.

따라서 findFirst()가 반환하는 Optional을 직접 사용하거나, .orElse() 메소드 등을 이용하여 기본값을 설정하는 등의 방법으로 안전하게 처리할 수 있습니다.

 
자바 스트림API의 filter함수는 람다표현식을 기대한다. 
람다표현식은 함수형 인터페이스 (하나의 추상메소드를 가진 인터페이스)의 인스턴스를 생성하기 위해 축약된 문법이다. 

filter가 받는 인터페이스는 "하나의 추상메소드를 가져와야한다"
람다표현식은 이 추상메소드를 구현하여 함수형 인터페이스의 인스턴스를 생성한다. 따라서 filter함수는 해당 인터페이스의 구현체를 받아서 사용한다. 


함수형 인터페이스인 Predicate는 하나의 추상 메소드 test를 가지고 있습니다. 람다 표현식 item -> item.getId().equals(id)에서 item -> 부분은 매개변수를 나타내고, item.getId().equals(id) 부분은 test 메소드의 구현체가 됩니다.

람다 표현식에서 ->의 왼쪽은 매개변수 목록이고, 오른쪽은 해당 메소드의 본문입니다. 이 경우에는 item이라는 매개변수를 받아 item.getId().equals(id)를 반환하는 test 메소드의 구현체가 되는 것이죠. 이것이 함수형 인터페이스의 추상 메소드를 구현하는 부분입니다.

간단히 말해서, Predicate 인터페이스의 추상 메소드 test를 item을 이용하여 구현한 것이 람다 표현식 item -> item.getId().equals(id)입니다. 이것이 함수형 인터페이스의 인스턴스를 생성하는 과정입니다. 이렇게 생성된 인스턴스가 filter 메소드에 전달되어 사용됩니다
 
 
🎈총정리 🎈
예를 들어, items.stream().filter(item -> item.getId().equals(id))에서 filter 메소드는 Predicate를 필요로 하며, 람다 표현식 item -> item.getId().equals(id)가 이를 구현합니다.
따라서 명시적으로 Predicate를 불러오지 않아도, 람다 표현식을 통해 필요한 함수형 인터페이스의 인스턴스를 생성하여 사용할 수 있습니다. 이는 Java 8 이후에 도입된 함수형 프로그래밍 기능 중 하나입니다.
 
스트림(Java 플랫폼 SE 8) (oracle.com)

Stream (Java Platform SE 8 )

A sequence of elements supporting sequential and parallel aggregate operations. The following example illustrates an aggregate operation using Stream and IntStream: int sum = widgets.stream() .filter(w -> w.getColor() == RED) .mapToInt(w -> w.getWeight())

docs.oracle.com

 

 
🎈내가 내방식으로 짧게 다시 정리🎈

element -> condition 그냥 쉽고 간단하게 이해하면 이 부분이 함수를 반환하면 함수형 인터페이스가 내장되어있는 추상메소드를 구현하게되어서 작동을 한다는건가?
 
 
 

네, 맞습니다. 람다 표현식에서 element -> condition은 Java에서의 함수형 인터페이스에 대한 구현입니다. 여기서 element는 함수에 전달되는 매개변수이며, condition은 해당 함수가 수행하는 조건을 나타냅니다. 이 람다 표현식은 함수형 인터페이스에 해당하는 메소드(일반적으로 하나의 추상 메소드)를 구현한 것으로 간주됩니다.

함수형 인터페이스는 하나의 추상 메소드만을 갖는 인터페이스로, 람다 표현식을 통해 이 추상 메소드를 구현할 수 있습니다. Java의 Stream API에서 많이 사용되는 함수형 인터페이스 중 일부는 이미 내장되어 있습니다. 예를 들면 Predicate<T>(조건을 검사하는 인터페이스)이나 Function<T, R>(하나의 값을 다른 값으로 변환하는 인터페이스) 등이 있습니다.

따라서 element -> condition은 함수형 인터페이스에 대한 람다 표현식으로, 해당 함수형 인터페이스에 정의된 메소드를 구현하는 코드입니다. Stream API의 메소드들은 이러한 함수형 인터페이스를 인자로 받아 해당 동작을 수행하게 됩니다.

 

 

옛날에 배워서 기억 안나는데 인터페이스가 추상메소드만 갖고있고 구현을 해야 작동을 하는거지?
 
 
 

네, 맞습니다. 인터페이스는 메소드의 선언만 있고 구현이 없는 추상 메소드를 가질 수 있습니다. 이러한 인터페이스에서는 해당 메소드를 구현하는 클래스가 필요하며, 그 클래스에서는 추상 메소드에 대한 실제 구현을 제공해야 합니다.

Java 8부터는 디폴트 메소드(default method)라는 기능이 도입되어 인터페이스에 메소드의 기본 구현을 제공할 수 있게 되었습니다. 이렇게하면 인터페이스를 구현하는 클래스에서는 해당 메소드를 구현할 필요가 없습니다. 하지만 여전히 추상 메소드는 구현이 필요합니다.

 

람다 표현식은 주로 함수형 인터페이스를 구현할 때 간편하게 사용됩니다. 함수형 인터페이스는 하나의 추상 메소드만을 가지므로 람다 표현식을 통해 이를 구현할 수 있습니다. Java 8에서는 함수형 프로그래밍을 지원하기 위해 람다 표현식과 스트림 API 등이 도입되었습니다.

 
 
🎈인터페이스 VS 추상클래스
 
인터페이스와 추상 클래스는 모두 추상화를 통해 다형성을 제공하는 자바의 특징을 나타내는 요소입니다. 그러나 두 개의 주요 차이점이 있습니다.

  1. 추상 메소드와 일반 메소드:
    • 인터페이스: 오직 추상 메소드만을 가질 수 있습니다. Java 8부터는 디폴트 메소드와 정적 메소드를 가질 수 있게 되었습니다.
    • 추상 클래스: 추상 메소드뿐만 아니라, 구현이 있는 일반 메소드도 가질 수 있습니다. 또한 생성자도 가질 수 있습니다.
  2. 다중 상속:
    • 인터페이스: 다중 상속을 지원합니다. 클래스가 여러 개의 인터페이스를 구현할 수 있습니다.
    • 추상 클래스: 단일 상속만을 지원합니다. 클래스는 하나의 추상 클래스만을 상속할 수 있습니다.

그 외에도 인터페이스는 상수와 관련된 필드만을 가질 수 있고, 추상 클래스는 일반 필드도 가질 수 있습니다. 또한, 인터페이스는 다양한 클래스와 관련된 동작을 정의하고, 추상 클래스는 클래스들 간에 공통된 특징을 묶어주는 역할을 하는 경향이 있습니다.
 
findFirst에서 끝남. 만약에 뭐가 있으면 
아무것도 없으면 그떄 Runtime...그거 내뱉는거임. 
 

 
자바를 다시 재가동하는 순간 post했던 내용이 다 사라진다고함. 
다시 빈 List부터 시작한다고함. 
 
그래서 미리 추가를 한다고함.
 
before
 

package com.github.supercoding.web.controller;

import com.github.supercoding.web.dto.Item;
import com.github.supercoding.web.dto.ItemBody;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;


@RestController
@RequestMapping("/api")
public class ElectronicStoreController {

private static int serialItemId = 1;
private List<Item> items = new ArrayList<>();


@GetMapping("/items")
public List<Item> findAllItem (){
return items;
}

@PostMapping("/items")
public String registerItem(@RequestBody ItemBody itemBody){
Item newItem = new Item (serialItemId++, itemBody );
items.add(newItem);
return "ID: " + newItem.getId();
}

@GetMapping("/items/{id}")
public Item fineItemByPathId(@PathVariable String id ){
Item itemfounded = items.stream()
.filter((item) -> item.getId().equals(id))
.findFirst()
.orElseThrow(()-> new RuntimeException());
return itemfounded;
}
}

 
after
이렇게 가면 또 Item 클래스가서 constructor 만들어줘야함.

 
 

 
 

 


Array있는거를 List로 바꾼 후에 ArrayList에 넣을 수 있다함 (자바는 좀 넣는게 원래 복잡하다고 함)

쿼리로 id를 불러오는걸 해본다고 함

/api/items-query?id=1

Getmapping은 쿼리파라미터 “?” 이걸 써서 쿼리를 날리는 방법을 많이 쓴다고 함

id를 받는 형식이 다른거지 내부 로직은 똑같다.

@GetMapping
public Item findItemByQueryItem(@RequestParam(“id”) String id ) {
내부 로직 똑같다
}

그리고 postman에 이렇게 요청하면 됨

http://localhost:8080/api/items-query?id=1

여러개의 id로 조회하는 것도 많이 쓰인다고 함


http://localhost:8080/api/items-query?id=1&id=2&id=3

찾는게 2개일수도 있고 3개일수도 있고 되게 많을 수도 있잖아

근데 다행히 @RequestParam 이 부분이 복수를 다 받는다고 함


@GetMapping
public Item findItemByQueryItem(@RequestParam(“id”) List<String> id ) {
내부 로직 똑같다
}

String -> List<String> ids로만 바꾸면 된다.

String 타입이 list인거지
그러면 내부로직이 바뀌지

@GetMapping
public Item findItemByQueryItem(@RequestParam(“id”)  List<String> ids) {

Set<String> idSet = ids.stream().collect(Collectors.toSet());

List<Item> itemsFound = items.stream().filter((item -> idSet.contains(item.getId()))).collect(Collectors.toList());

}



}

내부로직이 id를 Set으로 만들고…
stream에서 collect함수는 또 어떤 기능을 하는 함수야?


Stream의 collect 함수는 요소들을 수집하여 원하는 형태로 변환하는 역할을 합니다. 예를 들어, 리스트를 만들거나 맵으로 변환하는 등의 작업을 할 수 있습니다. Collectors 클래스를 사용하여 다양한 수집 작업을 수행할 수 있습니다.

Stream의 collect 함수를 사용하면 스트림의 요소를 수집하여 리스트나 맵으로 변환할 수 있습니다. 예를 들어, Collectors.toList()를 사용하면 요소들을 리스트로 수집할 수 있고, Collectors.toMap()을 사용하면 요소들을 맵으로 변환할 수 있습니다.

안에 id를 비교할 때는 set으로 바꾸는 게 좋다함

해쉬 이야기할 때 set으로 비교하면 왜 이득인지 설명했다고 함

Impo

collect(Collectors.toList())를 사용하면 스트림의 결과를 리스트로 수집할 수 있습니다

그러니까 소스코드 Set으로 변환하고 filter로 해당 아이디를 찾고 다시 list로 변환하는 거 맞지?

ids 리스트를 Set으로 변환하여 중복된 값들을 제거하고 유일한 값으로 구성된 idSet을 얻습니다.
items.stream()을 통해 items 리스트를 스트림으로 변환합니다.
filter 연산을 사용하여 스트림에서 필요한 조건을 만족하는 요소만을 남깁니다. 여기서는 idSet.contains(item.getId())를 통해 Item 객체의 id가 idSet에 포함되어 있는지 확인합니다.
collect(Collectors.toList())를 사용하여 필터링된 결과를 리스트로 수집하여 반환합니다.
이렇게 함으로써 중복을 제거하고 원하는 조건에 맞는 요소들을 리스트로 수집하는 기능을 구현하게 됩니다.
 
 

 
 
 
 

 

 

@GetMapping("/items-query")
public Item fineItemByQueryId(@RequestParam("id") String id ){
Item itemfounded = items.stream()
.filter((item) -> item.getId().equals(id))
.findFirst()
.orElseThrow(()-> new RuntimeException());
return itemfounded;
}

 

 
 

 

package com.github.supercoding.web.controller;

import com.github.supercoding.web.dto.Item;
import com.github.supercoding.web.dto.ItemBody;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;


@RestController
@RequestMapping("/api")
public class ElectronicStoreController {

private static int serialItemId = 1;
private List<Item> items = new ArrayList<>(Arrays.asList(
new Item (String.valueOf(serialItemId++), "Apple iPhone12", "Smartphone", 1490000, "A14 Bionic", "512GB"),
new Item (String.valueOf(serialItemId++), "Galaxy S12 Ultra", "Smartphone", 1490000, "A14 Bionic", "512GB")
));


@GetMapping("/items")
public List<Item> findAllItem (){
return items;
}

@PostMapping("/items")
public String registerItem(@RequestBody ItemBody itemBody){
Item newItem = new Item (serialItemId++, itemBody );
items.add(newItem);
return "ID: " + newItem.getId();
}

@GetMapping("/items/{id}")
public Item fineItemByPathId(@PathVariable String id ){
Item itemfounded = items.stream()
.filter((item) -> item.getId().equals(id))
.findFirst()
.orElseThrow(()-> new RuntimeException());
return itemfounded;
}

@GetMapping("/items-query")
public Item fineItemByQueryId(@RequestParam("id") String id ){
Item itemfounded = items.stream()
.filter((item) -> item.getId().equals(id))
.findFirst()
.orElseThrow(()-> new RuntimeException());
return itemfounded;
}

@GetMapping("/items-queries")
public List<Item> fineItemByQueryId(@RequestParam("id") List<String> ids ){

Set<String> IdSet = ids.stream().collect(Collectors.toSet());

List<Item> itemsfounded = items.stream().filter((item -> IdSet.contains(item.getId()))).collect(Collectors.toList());

return itemsfounded;
}
}

 
 

 
delete는 path를 많이 사용한다고 함.
path는  {} 이런 식으로 넣으니까 그 전에도 했듯이 
items에서 우리가 생각하는 item을 빼야함. 
만약items가 없다면 error를 내고
일단 근데 id를 찾아야함.
findItemByPathId 내부로직 먼저 쓰고
 
그 다음 없애는 로직
그냥 remove 메소드 쓰면 됨 
 
, the remove() method in ArrayList is used to remove an element from the list based on the object reference or the index. There are two overloaded versions of the remove method:
 

@DeleteMapping("/items/{id}")
public String deleteItemByPathId(@PathVariable String id){

Item itemfounded = items.stream()
.filter((item) -> item.getId().equals(id))
.findFirst()
.orElseThrow(()-> new RuntimeException());

items.remove(itemfounded);
return "Object with id = " + itemfounded.getId() + "has been deleted";
}

 
근데 이런 식으로 하면 잘 안될 수가 있다고함. 
 
 
그 이유는 Item 클래스에 equals랑 해시코드를 구현하지 않아서 그렇다고 함 구현해줘야함. 
 
id를 기준으로 같은거라서 id 만 체크를 하고 만든다고함. 
 
🎈Java에서 객체를 비교하려면 반드시 hashCode와 equals 메소드를 적절히 구현하는 것이 좋습니다. 🎈 이 두 메소드는 다음과 같은 이유로 중요합니다:

  1. equals 메소드:
    • equals 메소드는 두 객체가 동등한지를 비교합니다. 즉, 내용이 같은지 여부를 확인합니다.
    • 컬렉션 프레임워크에서 객체의 동등성을 판단할 때 사용됩니다. 예를 들어, ArrayList의 remove 메소드나 HashSet의 중복 제거 등에 영향을 미칩니다.
  2. hashCode 메소드:
    • hashCode 메소드는 객체의 해시 코드를 반환합니다. 이 메소드는 주로 해시 기반의 컬렉션에서 객체를 식별하는 데 사용됩니다. 예를 들어, HashMap이나 HashSet에서 효율적인 검색 및 저장을 위해 필요합니다.
    • 동일한 객체에 대해서는 반드시 동일한 해시 코드를 반환해야 합니다.

 
 

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Item item = (Item) o;
return Objects.equals(id, item.id);
}

@Override
public int hashCode() {
return Objects.hash(id);
}

 
 
이렇게 구현을 해주면 id로 똑같은거를 찾아서 remove를 할거라고 함 
 
api

http://localhost:9090/api/items/2
 

 
특이한게 put은 id pathvariable로 들어오고 어떻게 바뀔건지도 같이 들어온다.
2개가 들어오는거임. 
 
RequestBody로 실제 json도 ItemBody로 들어오고, Putmapping 어노테이션
 
이 매개변수에 2개 그냥 어노테이션 넣어줘도 상관없다 
 
 

@PutMapping("/items/{id")
public Item updateItem (@PathVariable String id, @RequestBody ItemBody itemBody) {

};

 
기존에 있는걸 찾아서 수정해줘도되고
기존에 있는걸 없애고 다시 만드는 과정도 할 수 있다는데
 
2번쨰 방식 해볼 예정

 

@PutMapping("/items/{id}")
public Item updateItem (@PathVariable String id, @RequestBody ItemBody itemBody) {


Item itemfounded = items.stream()
.filter((item) -> item.getId().equals(id))
.findFirst()
.orElseThrow(()-> new RuntimeException());

items.remove(itemfounded);

Item itemUpdated = new Item(Integer.valueOf(id), itemBody);
items.add(itemUpdated);

return itemUpdated;
};

 
자바내부에 있는 객체를 고칠 떄는 없애고 다시하는 식의 업데이트가 권장됨 
 

 
요청이 잘못됐다는 거구나
 

 


스프링부트에는 Jackson json이라는 라이브러리가 있어서 dto 만들어주면 자동으로 json으로 만들어주는 거임

이 라이브러리는 동작원리가 getter 바라보고 한다고 함

Getter 없이도 직렬화되는지 확인 실습

Getter 일부 주석처리하면 json에서 그 주석처리한 해당 필드가 json이 안 만들어진다.

다음 강 preview
자바 내부 List 로 관리되는 Data MySql로 옮기고, DB연동 Repository로 구현하기

반응형