[Spring#24] RestTemplate, Entity 연관 관계, 지연 로딩, 영속성 전이, 고아 Entity

김한준 Hanjun Kim·2023년 11월 13일
0

내일배움캠프

목록 보기
24/70

알고리즘

까먹었다.. 내일은 꼭 9시에 풀어서 올려야지


학습 정리

2주차, RestTemplate

  • 라이브러리 사용만으로는 구현이 힘든 기능들을 사용할 때
    서버에서 다른 서버로 간편하게 요청할 수 있도록 제공하는 기능

  • client입장의 서버(8080)와 server입장의 서버(7070)를 만듬.
    init 세팅 후 커밋

RestTemplate의 Get요청

  • RestTemplate도 어노테이션을 통해 써 올수 있다.
    단, 주입을 통해 받아올껀데 이전것과 조금 생긴 것이 다르다.
private final RestTemplate restTemplate;

    public RestTemplateService(RestTemplateBuilder builder) {
        this.restTemplate = builder.build();
    }
  • UriComponentsBuilder를 통해 Url을 만들 수 있음.

  • queryParam : ?= data 하는 방식

  • postman -> client 서버 -> server 서버
    client 서버 : ResponseEntity responseEntity = restTemplate.getForEntity(uri, ItemDto.class);
    를 통해 server 서버로 보내준 뒤, server 서버에서 받아서 리턴 해 준다.

  • 데이터 여러 개 가져오기
    client 서버에서 gradle에 json 추가
    fromJSONtoItems 추가 : 중첩 json형식으로 넘어온 데이터를 ItemDto로 변환 후 반환
    ResponseDto 변환할 때 생성자에 데이터 넣어서 변환한거처럼 같은 방식으로
    해서 itemJson.getString(key:"title") 이런 방식으로 가져온다.
    Json은 key value 형식이니까!

public List<ItemDto> fromJSONtoItems(String responseEntity) {
        JSONObject jsonObject = new JSONObject(responseEntity);
        JSONArray items  = jsonObject.getJSONArray("items");
        List<ItemDto> itemDtoList = new ArrayList<>();

        for (Object item : items) {
            ItemDto itemDto = new ItemDto((JSONObject) item);
            itemDtoList.add(itemDto);
        }

        return itemDtoList;
    }

RestTemplate Post 요청

  • 이번엔 PathVariable 방식
    ResponseEntity responseEntity = restTemplate.postForEntity(uri, user, ItemDto.class) : 데이터 넘기기

RestTemplate Exchange

  • Jwt 방식은 두가지가 있는데
  1. 서버측에서 직접 쿠키를 만들어서 보내고, request 객체에서 쿠키를 뜯어서 원하는 쿠키를 도출해서 사용
  2. request 헤더랑 response 헤더에 Jwt 토큰을 만들어서 넣을 수 있음 <- 선택
// 클라이언트 서버
public List<ItemDto> exchangeCall(@RequestHeader("Authorization") String token) {
}

이런식으로 받아올 수 있음
보내는 건

RequestEntity<User> requestEntity = RequestEntity
                .post(uri)
                .header("X-Authorization", token)
                .body(user);

서버용 서버에서는

// 서버용 서버
public ItemResponseDto exchangeCall(@RequestHeader("X-Authorization") String token, @RequestBody UserRequestDto requestDto) {
        return itemService.exchangeCall(token, requestDto);
    }

반환하는건

public ItemResponseDto exchangeCall(String token, UserRequestDto requestDto) {
        System.out.println("token = " + token);
        System.out.println("requestDto.getUsername() = " + requestDto.getUsername());
        System.out.println("requestDto.getPassword() = " + requestDto.getPassword());

        return getCallList();
    }

테스트 하는 포스트맨에서는
http://localhost:8080/api/client/exchange-call 이후 Header에 key(Authorization) 와 value를 넣어줘야 한다.

네이버 오픈 API

  • 네이버 Developers에서 ID와 Secret을 발급받은 후, 네이버에서 제시하는 조건에 맞춰서 Header에 ID와 Secret을 보내주면
    네이버에서 실행되고 있는 API에서 데이터를 반환 해 준다.

중간부분이 spring-resttemplet-client 서버 만들것!

public List<ItemDto> searchItems(String query) {
        // 요청 URL 만들기
        URI uri = UriComponentsBuilder
                .fromUriString("https://openapi.naver.com")
                .path("/v1/search/shop.json")
                .queryParam("display", 15)  // default : 10
                .queryParam("query", query)
                .encode()
                .build()
                .toUri();
        log.info("uri = " + uri);

        RequestEntity<Void> requestEntity = RequestEntity
                .get(uri)
                .header("X-Naver-Client-Id", "Client-Id")
                .header("X-Naver-Client-Secret", "Client-Secret")
                .build();

        ResponseEntity<String> responseEntity = restTemplate.exchange(requestEntity, String.class);

        log.info("NAVER API Status Code : " + responseEntity.getStatusCode());

        return fromJSONtoItems(responseEntity.getBody());
    }

Entity 연관 관계

  • DB 테이블끼리의 연관 관계와 Entity끼리의 연관 관계의 차이
  • DB 테이블에서는 테이블 사이의 연관관계를 FK(외래 키)로 맺을 수 있고 방향 상관없이 조회가 가능합니다.
  • Entity에서는 상대 Entity를 참조하여 Entity 사이의 연관관계를 맺을 수 있습니다.
  • 하지만 상대 Entity를 참조하지 않고 있다면 상대 Entity를 조회할 수 있는 방법이 없습니다.

따라서 Entity에서는 DB 테이블에는 없는 방향의 개념이 존재합니다.

서로 상대의 Entity 참조 : 양방향
한쪽이라도 참조하지 못함 : 단방향

1 대 1 관계

  • @OneToOn
    단뱡향 / 양방향 존재
    단방향일 때 외래키의 주인만 상대 Entity의 필드를 가짐(@JoinColumn)

  • jpa에서는 양방향일때 외래키(@JoinColumn)의 주인을 알려줘야함
    외래키의 주인이 아닌 쪽에서 지정해야함(@mappedBy)
    @mappedBy : 상대 Entity의 필드명. 본인 클래스 명이 아니다.

  • save() 안에는
    내부적으로 트랜잭션이 있어서 insert가 가능한 상태.
    변경감지 하려면 @Transaction을 걸어줘야 작동한다! (영속성 컨텍스트)

  • 외래키의 주인만이 외래키를 컨트롤 할 수 있다.
    근데? addFood()라는 메서드를 만들어서 주인이 아닌쪽에서도 저장할 수 있다.(우회)
    = 조회는 가능 하다

N 대 1 관계

  • 데이터베이스 테이블에 적용된다고 생각하면 안됌 ! (Only Entity)
    @mappedBy : 외래키의 주인 설정
    이 때 속성값은 조인되고있는 상대 Entity의 필드 명
@Test
    @Rollback(value = false)
    @DisplayName("N대1 단방향 테스트")
    void test1() {
        User user = new User();
        user.setName("Robbie");

        Food food = new Food();
        food.setName("후라이드 치킨");
        food.setPrice(15000);
        food.setUser(user); // 외래 키(연관 관계) 설정

        Food food2 = new Food();
        food2.setName("양념 치킨");
        food2.setPrice(20000);
        food2.setUser(user); // 외래 키(연관 관계) 설정

        userRepository.save(user);
        foodRepository.save(food);
        foodRepository.save(food2);
    }

1 대 N 관계

  • @OneToMany
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @OneToMany
    @JoinColumn(name = "food_id") // users 테이블에 food_id 컬럼
                                    // user_id 가 아닌것에 주의
    private List<User> userList = new ArrayList<>();
}

N 대 M 관계

  • @ManyToMany

 @ManyToMany
    @JoinTable(name = "orders", // 중간 테이블 생성
    joinColumns = @JoinColumn(name = "food_id"), // 현재 위치인 Food Entity 에서 중간 테이블로 조인할 컬럼 설정
    inverseJoinColumns = @JoinColumn(name = "user_id")) // 반대 위치인 User Entity 에서 중간 테이블로 조인할 컬럼 설정
    private List<User> userList = new ArrayList<>();
  • joinColumns = @JoinColumn(name = "food_id") : 본인과 조인할 컬럼(orders에 있는 food_id)
    Jpa에 의해 만들어진 orders 테이블에 보면 fk만 2개이다.
  • 중간 테이블을 만들어서 컨트롤하면 쉽다.
    서로의 엔티티를 가지고 있기 때문에
    Food <-> Order <-> User 이 가능하다.

One / To / Many

User / order

= 고객주문을 여러번 할 수 있다.
= public class User {
@OneToMany(mappedBy = "user")
private List<Order> orderList = new ArrayList<>();
}
이게 이 뜻이다.

Many / To / One

order / food

= 음식주문을 여러번 될 수 있다.
= public class 음식 {
@ManyToOne
@JoinColumn(name = "food_id")
private Food food;
}
이런식으로

처음에 오는게 현재 엔티디 / 다음에 오는게 상대 엔티티.

  • 주문일 추가 : @EnableJpaAuditing 추가하기

오류 해결 : gradle 세팅 문제였다. intellij 설정에서 빌드 바꿔주니 테스트 잘 실행 된다.

지연 로딩

  • Jpa 에서는 데이터를 가져올 때 FetchType이라는 가져오는 방법이 있는데,

    LAZY : 지연 로딩
    필요한 시점의 정보를 로딩

    EAGER : 즉시 로딩
    데이터를 즉시 로딩

    어노테이션 이름중에 Many가 있으면 LAZY가 디폴트
    반대로 One이라면 즉시 정보를 로딩해도 무리가 없으니 EAGER가 디폴트

  • 영속성 컨텍스트 : 1차 캐시 / 쓰기 지연 저장소 / 변경 감지
    지연로딩도 영속성 컨텍스트의 기능 중 하나.
    Select 할 땐 영속성 컨텍스트가 필수가 아니지만,
    지연 로딩 된 정보를 조회하기 위해서는 영속성 컨텍스트가 있어야 한다.

  • 성공 = 트렌잭션 : 공식!

영속성 전이

  • Cascade Persist
    영속성 배울때 delete 할 때 연관된 데이터 삭제 불가능 했었던 거
    onCascade? 해서 연관된 데이터 삭제 했었다.

  • 지금 하려는건 foodlist에 넣었던 것 전파되서 한번에 저장하려고 한다.( 로비 음식 저장 -> 영속성 전이 저장으로)
    영속성 전이 저장 :
    @OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST)

    영속성 전이 삭제 :
    @OneToMany(mappedBy = "user", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})

고아 Entity 삭제

  • Jpa에서는 연관관계를 지우는 것만으로도 해당 데이터를 지울 수 있다.
    @OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST, orphanRemoval = true)

    이 옵션도 Cascade.REVOCE 처럼 Robbie를 삭제하면 연관된 음식 Entity를 삭제하는 기능도 같이 가지고 있다.
    삭제를 한 후에 다른곳에서 참조되고 있었는지 확인 해주어야 한다. 다른 객체가 있을 수 있기 때문에.

고로 @ManyToOne같은 어노테이션을 사용할 수 없다.

profile
개발이 하고싶은 개발지망생

0개의 댓글