RestTemplate란
RestTemplate은 간편하게 Rest방식의 API를 호출할 수 있는 Spring 내장 클래스이다. Spring 3.0부터 지원하는 Spring의 HTTP 통신 템플릿이다. Restful의 원칙을 지킬 수 있으며 HTTP 메서드들에 적합한 여러 메서드 제공
RestTemplate & Open API
Client, Server 프로젝트 2개 만들어서 진행
RestTemplate 생성자는 build로 만듬
private final RestTemplate restTemplate;
public RestTemplateService(RestTemplateBuilder builder) {
this.restTemplate = builder.build();
}
요청 받은 검색어를 Query String 방식으로 Server 입장의 서버로 RestTemplate를 사용하여 요청
public ItemDto getCallObject(String query) {
// 요청 URL 만들기
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:7070")
.path("/api/server/get-call-obj")
.queryParam("query", query)
.encode()
.build()
.toUri();
log.info("uri = " + uri);
ResponseEntity<ItemDto> responseEntity = restTemplate.getForEntity(uri, ItemDto.class);
log.info("statusCode = " + responseEntity.getStatusCode());
return responseEntity.getBody();
}
RestTemplate의 getForEntity는 Get 방식으로 해당 URI의 서버에 요청을 진행
첫 번째 파라미터에는 URI,
두 번째 파라미터에는 전달 받은 데이터와 매핑하여 인스턴스화할 클래스의 타입을 주면됨(요청을 한 서버에서 넘어오는 데이터를 받아 줄 Class타입으로 지정해주면 역직렬화가 되어 객체형태로 담김)
ResponseEntity HTTPEntity를 상속받고 있음. HTTP관련 데이터를 Response응답할 때 사용
사진 설명을 입력하세요.
@RequestParam -> ?parameter=값
여러개의 값을 한번에 호출
Json Dependency
// json
implementation 'org.json:json:20230227'
public List<ItemDto> getCallList() {
// 요청 URL 만들기
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:7070")
.path("/api/server/get-call-list")
.encode()
.build()
.toUri();
log.info("uri = " + uri);
ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);
log.info("statusCode = " + responseEntity.getStatusCode());
log.info("Body = " + responseEntity.getBody());
return fromJSONtoItems(responseEntity.getBody());
}
fromJSONtoItems
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);//item->JSON형식으로 캐스팅
itemDtoList.add(itemDto);
}
return itemDtoList;
}
Post 요청
public ItemDto postCall(String query) {
// 요청 URL 만들기
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:7070")
.path("/api/server/post-call/{query}") //PathVariable 방식
.encode()
.build()
.expand(query) //{}에 들어갈 값
.toUri();
log.info("uri = " + uri);
User user = new User("Robbie", "1234");
ResponseEntity<ItemDto> responseEntity = restTemplate.postForEntity(uri, user, ItemDto.class);
log.info("statusCode = " + responseEntity.getStatusCode());
return responseEntity.getBody();
}
restTemplate.postForEntity
postForEntity는 getForEntity와는 다르게 첫 번째 parameter로 uri를 넣고 두 번째에는 HttpBody에 넣어줄 데이터를 넣으면 됨 세번째는 전달 받은 데이터랑 Mapping할 것을 넣으면 됨
private final List<Item> itemList = Arrays.asList(
new Item("Mac", 3_888_000),
new Item("iPad", 1_230_000),
new Item("iPhone", 1_550_000),
new Item("Watch", 450_000),
new Item("AirPods", 350_000)
);
public Item getCallObject(String query) {
for(Item item : itemList){
if(item.getTitle().equals(query)){
return item;
}
}
return null;
}
public Item postCall(String query, UserRequestDto userRequestDto) {
System.out.println("userRequestDto.getUsername() = " + userRequestDto.getUsername());
System.out.println("userRequestDto.getPassword() = " + userRequestDto.getPassword());
return getCallObject(query);
}
postCall -> return getCallObject(query)를 통해 query값 일치하는 것 불러옴
사진 설명을 입력하세요.
RestTemplate의 exchange
Header에 정보를 추가
public List<ItemDto> exchangeCall(String token) {
// 요청 URL 만들기
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:7070")
.path("/api/server/exchange-call")
.encode()
.build()
.toUri();
log.info("uri = " + uri);
User user = new User("Robbie", "1234");
RequestEntity<User> requestEntity = RequestEntity
.post(uri)
.header("X-Authorization", token)
.body(user);
ResponseEntity<String> responseEntity = restTemplate.exchange(requestEntity, String.class);
return fromJSONtoItems(responseEntity.getBody());
}
RestTemplate.exchange 첫번째에 requestEntity타입의 필드를 줄 수 있음 두번째는 받아올 데이터
@GetMapping("/exchange-call")
public List<ItemDto> exchangeCall(@RequestHeader("Authorization") String token) {
return restTemplateService.exchangeCall(token);
}
key,value형태로 특정한 값 토큰을 넣어서 보내면 DispatcherServlet를 타고 Controller로 들어올때 @RequestHeader 사용하여 쉽게 사용 가능
내부에다가 가지고 오고 싶은 key값 입력 뒤에는 원하는 변수명 입력
사진 설명을 입력하세요.
네이버 OPEN API
package com.sparta.springresttemplateclient.naver.controller;
import com.sparta.springresttemplateclient.naver.dto.ItemDto;
import com.sparta.springresttemplateclient.naver.service.NaverApiService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api")
public class NaverApiController {
private final NaverApiService naverApiService;
public NaverApiController(NaverApiService naverApiService) {
this.naverApiService = naverApiService;
}
@GetMapping("/search")
public List<ItemDto> searchItems(@RequestParam String query) {
return naverApiService.searchItems(query);
}
}
package com.sparta.springresttemplateclient.naver.service;
import com.sparta.springresttemplateclient.naver.dto.ItemDto;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
@Slf4j(topic = "NAVER API")
@Service
public class NaverApiService {
private final RestTemplate restTemplate;
public NaverApiService(RestTemplateBuilder builder) {
this.restTemplate = builder.build();
}
public List<ItemDto> searchItems(String query) {
// 요청 URL 만들기
URI uri = UriComponentsBuilder
.fromUriString("https://openapi.naver.com")
.path("/v1/search/shop.json")
.queryParam("display", 15)
.queryParam("query", query)
.encode()
.build()
.toUri();
log.info("uri = " + uri);
RequestEntity<Void> requestEntity = RequestEntity
.get(uri)
.header("X-Naver-Client-Id", "개인id값")
.header("X-Naver-Client-Secret", "개인secret값")
.build();
ResponseEntity<String> responseEntity = restTemplate.exchange(requestEntity, String.class);
log.info("NAVER API Status Code : " + responseEntity.getStatusCode());
return fromJSONtoItems(responseEntity.getBody());
}
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;
}
}
package com.sparta.springresttemplateclient.naver.dto;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.json.JSONObject;
@Getter
@NoArgsConstructor
public class ItemDto {
private String title;
private String link;
private String image;
private int lprice;
public ItemDto(JSONObject itemJson) {
this.title = itemJson.getString("title");
this.link = itemJson.getString("link");
this.image = itemJson.getString("image");
this.lprice = itemJson.getInt("lprice");
}
}
사진 설명을 입력하세요.
Entity 연관 관계
DB연동
spring.datasource.url=jdbc:mysql://localhost:3306/orderapp
spring.datasource.username=root
spring.datasource.password=비밀번호
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
orderapp DB생성
cd /usr/local/mysql/bin -> ./mysql -u root -p -> password -> create database orderapp;
table생성
create table users
(
id bigint not null auto_increment,
name varchar(255),
primary key (id)
);
create table food
(
id bigint not null auto_increment,
name varchar(255),
price float(53) not null ,
primary key (id)
);
주문 음식 정보를 파악하기 위해 food_id 컬럼 추가
alter table users add food_id bigint;
users table value 값
insert into users (name, food_id) VALUE ('Robbie',1);
insert into users (name, food_id) VALUE ('Robbert',1);
insert into users (name, food_id) VALUE ('Robbie',2);
food
음식과 고객은 1:N관계임(여러명의 고객이 하나의 음식을 주문할 수 있음)
주문고객 파악을 위해 user_id컬럼 추가
alter table food add user_id bigint;
food table value 값
INSERT INTO food (name, price, user_id) VALUES ('후라이드 치킨', 15000, 1);
INSERT INTO food (name, price, user_id) VALUES ('후라이드 치킨', 15000, 2);
INSERT INTO food (name, price, user_id) VALUES ('양념 치킨', 20000, 1);
불필요하게 고객의 이름과 메뉴가 중복되는 문제가 발생
주문에 대한 정보를 기록할 orders 테이블을 추가
create table orders
(
id bigint not null auto_increment,
user_id bigint,
food_id bigint,
order_date date,
primary key (id)
)
주문 테이블을 사용하여 테이블들의 연관 관계를 해결
drop table if exists food; //food삭제
drop table if exists users; //users삭제
create table users
(
id bigint not null auto_increment,
name varchar(255),
primary key (id)
);
create table food
(
id bigint not null auto_increment,
name varchar(255),
price float(53) not null,
primary key (id)
);
alter table orders
add constraint orders_user_fk
foreign key (user_id) //orders 테이블 user_id는 users id를 참조
references users (id);
alter table orders
add constraint orders_food_fk
foreign key (food_id) //orders테이블의 food_id는 food의 id를 참조
references food (id);
INSERT INTO users (name) VALUES ('Robbie');
INSERT INTO users (name) VALUES ('Robbert');
INSERT INTO food (name, price) VALUES ('후라이드 치킨', 15000);
INSERT INTO food (name, price) VALUES ('양념 치킨', 20000);
INSERT INTO food (name, price) VALUES ('고구마 피자', 30000);
INSERT INTO food (name, price) VALUES ('아보카도 피자', 50000);
INSERT INTO orders (user_id, food_id, order_date) VALUES (1, 1, SYSDATE());
INSERT INTO orders (user_id, food_id, order_date) VALUES (2, 1, SYSDATE());
INSERT INTO orders (user_id, food_id, order_date) VALUES (2, 2, SYSDATE());
INSERT INTO orders (user_id, food_id, order_date) VALUES (1, 4, SYSDATE());
INSERT INTO orders (user_id, food_id, order_date) VALUES (2, 3, SYSDATE());
고객 1명은 음식 N개를 주문할 수 있음
고객 : 음식 = 1 : N 관계
음식 1개는 고객 N명에게 주문될 수 있음
음식 : 고객 = 1 : N 관계
결론적으로 고객과 음식은 N : M 관계
고객 : 음식 = N : M 관계(다대다)
N:M(다대다)관계인 데이터들의 연관 관계를 해결하기 위해 중간 테이블을 사용할 수 있음
DB 관계에서는 방향의 개념이 있을까?
단방향 : users테이블에서만 food테이블을 참조할 수 있을 때
양방향 : users테이블과 food테이블이 서로를 참조할 수 있을 때
DB 테이블간의 관계에서는 방향의 개념이 없음(DB에서는 어떤 테이블을 기준으로 하든 원하는 정보를 JOIN을 사용하여 조회할 수 있음)
SELECT u.name as username, f.name as foodname, o.order_date as orderdate
FROM users u
INNER JOIN orders o on u.id = o.user_id
INNER JOIN food f on o.food_id = f.id
WHERE o.user_id = 1;
----------------------------------------------------------------------------
SELECT u.name as username, f.name as foodname, o.order_date as orderdate
FROM food f
INNER JOIN orders o on f.id = o.food_id
INNER JOIN users u on o.user_id = u.id
WHERE o.user_id = 1;
JPA Entity에서의 테이블간의 연관 관계 표현
음식 : 고객 = N : 1
음식
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
}
고객
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "user")
private List<Food> foodList = new ArrayList<>(); //존재하지않음, 조회를 위한 표현
}
한명의 고객은 여러번 주문이 가능한 상황
이를 Entity에서 여러번 가능함을 표현하기 위해 Java 컬렉션을 사용하여 List<Food> foodList = new ArrayList<>() 이처럼 표현할 수 있음
DB테이블에 실제 컬럼으로 존재하지는 않지만 Entity상태에서 다른 Entity를 참조하기 위해 이러한 방법을 사용
현재 음식 Entity와 고객 Entity는 서로를 참조하고 있음(N : 1 양방향 관계)
음식
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
}
고객
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
음식Entity에서만 고객Entity를 참조할 수 있음
이러한 관계를(N : 1) 단방향 관계라 부름
고객Entity에는 음식Entity의 정보가 없기 때문에 음식 정보를 조회할 수 없음
정리
DB테이블에서는 테이블 사이의 연관관계를 FK로 맺을 수 있고 방향 상관없이 조회 가능
Entity에서는 상대 Entity를 참조하여 Entity 사이의 연관관계를 맺을 수 있음(양방향)
상대 Entity를 참조하지 않고 있다면 상대 Entity를 조회할 수 있는 방법은 없음(단방향)
따라서 Entity에서는 DB테이블에는 없는 방향의 개념이 존재함
1:1관계(@OneToOne)
단방향 관계
Entity에서 외래 키의 주인은 일반적으로 N(다)의 관계인 Entity이지만 1 대 1 관계에서는 직접 정해줘야함
외래 키 주인은 외래 키를 등록, 수정, 삭제할 수 있으며 주인이 아닌쪽에서는 외래 키를 읽기만 가능
@JoinColumn()은 외래 키의 주인이 활용하는 애너테이션입니다.
컬럼명, null 여부, unique 여부 등을 지정할 수 있습니다.
음식 Entity가 외래 키의 주인인 경우
사진 설명을 입력하세요.
음식
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@OneToOne
@JoinColumn(name = "user_id")
private User user;
}
------------------------------------------------------------
고객
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
반대인 경우(고객 Entity가 외래키의 주인인 경우)
사진 설명을 입력하세요.
음식
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
}
----------------------------------------------------------
고객
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToOne
@JoinColumn(name = "food_id")
private Food food;
}
양방향 관계
JPA에서 양방향일때 직접 외래키의 주인은 누구이다 라고 알려줘야함(mappedBy)
단방향이라면 외래 키의 주인만 상대 Entity 타입의 필드를 가지면서 @JoinColumn()을 활용하여 속성 설정
양방향이라면 외래 키의 주인의 상대 Entity에 mappedBy 설정(즉 @JoinColumn() 반대)
음식Entity가 외래 키의 주인인 경우
음식
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@OneToOne
@JoinColumn(name = "user_id")
private User user;
}
-----------------------------------------------------------
고객
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToOne(mappedBy = "user") //user(속성의값)는 외래 키의 주인(음식) user을 의미함
private Food food;
}
고객 Entity가 외래 키의 주인인 경우
음식
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@OneToOne(mappedBy = "food")
private User user;
}
------------------------------------------------------------
고객
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToOne
@JoinColumn(name = "food_id")
private Food food;
}
단방향 테스트코드
package com.sparta.jpaadvance.relation;
import com.sparta.jpaadvance.entity.Food;
import com.sparta.jpaadvance.entity.User;
import com.sparta.jpaadvance.repository.FoodRepository;
import com.sparta.jpaadvance.repository.UserRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
@SpringBootTest
@Transactional//insert,delete,update..
public class OneToOneTest {
@Autowired
FoodRepository foodRepository;
@Autowired
UserRepository userRepository;
@Test
@Rollback(value = false) //test에서는 Transactional에 의해 자동 rollback 됨으로 false설정
@DisplayName("1대1 단방향 테스트")
void test1(){
User user = new User();
user.setName("Robbie");
Food food = new Food();
food.setName("후라이드 치킨");
food.setPrice(15000);
food.setUser(user); //외래키(연관 관계)설정
userRepository.save(user);
foodRepository.save(food);
}
}
양방향 테스트코드
package com.sparta.jpaadvance.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter
@Setter
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToOne(mappedBy = "user")
private Food food;
public void addFood(Food food){
this.food = food;
food.setUser(this);
}
}
@Test
@Rollback(value = false)
@DisplayName("1대1 양방향 테스트 : 외래 키 저장 실패")
void test2() {
Food food = new Food();
food.setName("고구마 피자");
food.setPrice(30000);
// 외래 키의 주인이 아닌 User 에서 Food 를 저장해보겠습니다.
User user = new User();
user.setName("Robbie");
user.setFood(food);
userRepository.save(user);
foodRepository.save(food);
// 확인해 보시면 user_id 값이 들어가 있지 않은 것을 확인하실 수 있습니다.
}
@Test
@Rollback(value = false)
@DisplayName("1대1 양방향 테스트 : 외래 키 저장 실패 -> 성공")
void test3() {
Food food = new Food();
food.setName("고구마 피자");
food.setPrice(30000);
// 외래 키의 주인이 아닌 User 에서 Food 를 저장하기 위해 addFood() 메서드 추가
// 외래 키(연관 관계) 설정 food.setUser(this); 추가
User user = new User();
user.setName("Robbie");
user.addFood(food);
userRepository.save(user);
foodRepository.save(food);
}
@Test
@Rollback(value = false)
@DisplayName("1대1 양방향 테스트")
void test4() {
User user = new User();
user.setName("Robbert");
Food food = new Food();
food.setName("고구마 피자");
food.setPrice(30000);
food.setUser(user); // 외래 키(연관 관계) 설정
userRepository.save(user);
foodRepository.save(food);
}
외래키의 주인만이 외래키를 컨트롤할 수 있다
외래키의 주인이 아닌데 컨트롤하기 위해서는 위코드 ex) add를 따로 만들어서 설정해야 함
외래키의 주인이 아니라면 조회만 가능함
조회테스트
@Test
@DisplayName("1대1 조회 : Food 기준 user 정보 조회")
void test5() {
Food food = foodRepository.findById(1L).orElseThrow(NullPointerException::new);
// 음식 정보 조회
System.out.println("food.getName() = " + food.getName());
// 음식을 주문한 고객 정보 조회
System.out.println("food.getUser().getName() = " + food.getUser().getName());
}
@Test
@DisplayName("1대1 조회 : User 기준 food 정보 조회")
void test6() {
User user = userRepository.findById(1L).orElseThrow(NullPointerException::new);
// 고객 정보 조회
System.out.println("user.getName() = " + user.getName());
// 해당 고객이 주문한 음식 정보 조회
Food food = user.getFood();
System.out.println("food.getName() = " + food.getName());
System.out.println("food.getPrice() = " + food.getPrice());
}
⚠️ 주의!
1. 외래 키의 주인 Entity에서 @JoinColumn() 애너테이션을 사용하지 않아도 default 옵션이 적용되기 때문에 생략이 가능합니다.
- 다만 1 대 N 관계에서 외래 키의 주인 Entity가 @JoinColumn() 애너테이션을 생략한다면 JPA가 외래 키를 저장할 컬럼을 파악할 수가 없어서 의도하지 않은 중간 테이블이 생성됩니다.
- 따라서 외래 키의 주인 Entity에서 @JoinColumn() 애너테이션을 활용하시는게 좋습니다.
2. 양방향 관계에서 mappedBy 옵션을 생략할 경우 JPA가 외래 키의 주인 Entity를 파악할 수가 없어 의도하지 않은 중간 테이블이 생성되기 때문에 반드시 설정해주시는게 좋습니다.
@ManyToOne N:1관계(다대일)
단방향 관계
음식
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
}
-------------------------------------------------------------
고객
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
양방향관계
양방향 참조를 위해 고객 Entity에서 Java 컬렌션을 사용하여 음식 Entity 참조
음식
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
}
---------------------------------------------------------
고객
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "user")
private List<Food> foodList = new ArrayList<>();
}
테스트코드
@Test
@Rollback(value = false)
@DisplayName("N대1 양방향 테스트 : 외래 키 저장 실패")
void test2() {
Food food = new Food();
food.setName("후라이드 치킨");
food.setPrice(15000);
Food food2 = new Food();
food2.setName("양념 치킨");
food2.setPrice(20000);
// 외래 키의 주인이 아닌 User 에서 Food 를 저장해보겠습니다.
User user = new User();
user.setName("Robbie");
user.getFoodList().add(food);
user.getFoodList().add(food2);
userRepository.save(user);
foodRepository.save(food);
foodRepository.save(food2);
// 확인해 보시면 user_id 값이 들어가 있지 않은 것을 확인하실 수 있습니다.(Null)
}
@Test
@Rollback(value = false)
@DisplayName("N대1 양방향 테스트 : 외래 키 저장 실패 -> 성공")
void test3() {
Food food = new Food();
food.setName("후라이드 치킨");
food.setPrice(15000);
Food food2 = new Food();
food2.setName("양념 치킨");
food2.setPrice(20000);
// 외래 키의 주인이 아닌 User 에서 Food 를 쉽게 저장하기 위해 addFoodList() 메서드 생성하고
// 해당 메서드에 외래 키(연관 관계) 설정 food.setUser(this); 추가
User user = new User();
user.setName("Robbie");
user.addFoodList(food);
user.addFoodList(food2);
userRepository.save(user);
foodRepository.save(food);
foodRepository.save(food2);
}
@Test
@Rollback(value = false)
@DisplayName("N대1 양방향 테스트")
void test4() {
User user = new User();
user.setName("Robbert");
Food food = new Food();
food.setName("고구마 피자");
food.setPrice(30000);
food.setUser(user); // 외래 키(연관 관계) 설정
Food food2 = new Food();
food2.setName("아보카도 피자");
food2.setPrice(50000);
food2.setUser(user); // 외래 키(연관 관계) 설정
userRepository.save(user);
foodRepository.save(food);
foodRepository.save(food2);
}
}
조회
@Test
@DisplayName("N대1 조회 : Food 기준 user 정보 조회")
void test5() {
Food food = foodRepository.findById(1L).orElseThrow(NullPointerException::new);
// 음식 정보 조회
System.out.println("food.getName() = " + food.getName());
// 음식을 주문한 고객 정보 조회
System.out.println("food.getUser().getName() = " + food.getUser().getName());
}
@Test
@DisplayName("N대1 조회 : User 기준 food 정보 조회")
void test6() {
User user = userRepository.findById(1L).orElseThrow(NullPointerException::new);
// 고객 정보 조회
System.out.println("user.getName() = " + user.getName());
// 해당 고객이 주문한 음식 정보 조회
List<Food> foodList = user.getFoodList();
for (Food food : foodList) {
System.out.println("food.getName() = " + food.getName());
System.out.println("food.getPrice() = " + food.getPrice());
}
}
@OneToMany 1대다(1:N) 관계
외래 키를 관리하는 주인은 음식 Entity이지만 실제 외래 키는 고객 Entity가 가지고 있음
단방향
음식
@Entity
@Table(name = "food")
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 컬럼(N:1 -> 1)
private List<User> userList = new ArrayList<>();
}
----------------------------------------------------------------
고객
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
외래 키를 음식 Entity가 직접 가질 수 있다면 INSERT 발생 시 한번에 처리할 수 있지만, 실제 DB에서 외래 키를 고객 테이블이 가지고 있기 때문에 추가적인 UPDATE가 발생된다는 단점이 존재(1:N 관계)
양방향 관계
1 대 N 관계에서는 일반적으로 양방향 관계가 존재하지 않음
1 대 N 관계에서 양방향 관계를 맺으려면 음식 Entity를 외래 키의 주인으로 정해주기 위해 고객 Entity에서 mappedBy 옵션을 사용해야 하지만 @ManyToOne 애너테이션은 mappedBy 속성을 제공하지 않음
N 관계의 Entity인 고객 Entity에서 @JoinColum의 insertable 과 updatable 옵션을 false로 설정하여 양쪽으로 JOIN 설정을 하면 양방향처럼 설정할 수는 있음
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "food_id", insertable = false, updatable = false)
private Food food;
}
단방향 테스트
package com.sparta.jpaadvance.relation;
import com.sparta.jpaadvance.entity.Food;
import com.sparta.jpaadvance.entity.User;
import com.sparta.jpaadvance.repository.FoodRepository;
import com.sparta.jpaadvance.repository.UserRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@SpringBootTest
public class OneToManyTest {
@Autowired
FoodRepository foodRepository;
@Autowired
UserRepository userRepository;
@Test
@DisplayName("1:N 단방향")
void test1(){
User user = new User();
user.setName("Robbie");
User user2 = new User();
user2.setName("Robbert");
Food food = new Food();
food.setName("후라이드치킨");
food.setPrice(15000);
food.getUserList().add(user);
food.getUserList().add(user2);
userRepository.save(user);
userRepository.save(user2);
foodRepository.save(food);
}
}
다대일에서는 setUser로 연관관계를 설정하지만 일대다에서는 List형식이기 때문에 add로 실행
UPDATA 쿼리가 발생함
외래키는 users테이블이 가지고 있음
사진 설명을 입력하세요.
조회
@Test
@DisplayName("1대N 조회테스트")
void test2(){
Food food = foodRepository.findById(2L).orElseThrow(NullPointerException::new);
System.out.println("food.getName() = " + food.getName());
//해당 음식을 주문한 고객 정보
List<User> users = food.getUserList();
for(User user : users){
System.out.println("user.getName() = " + user.getName());
}
}
@ManyToMany N:M관계(다대다 관계)
다대다관계는 중간테이블을 생성하여 사용함
단방향
음식
package com.sparta.jpaadvance.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter
@Setter
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@ManyToMany
@JoinTable(name = "orders", //중간테이블 생성
joinColumns = @JoinColumn(name = "food_id"), //현재 위치에서 중간 테이블 조인
inverseJoinColumns = @JoinColumn(name = "user_id")) //반대 위치에서 중간 테이블 조인
private List<User> userList = new ArrayList<>();
}
---------------------------------------------------------------------------------
고객
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
중간테이블을 컨트롤하기 어렵기 때문에 추후에 중간 테이블의 변경이 발생할 경우 문제가 발생할 가능성이 있음
JPA에 의해서 만들어진 테이블은 컨트롤이 어려움
양방향
음식
package com.sparta.jpaadvance.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter
@Setter
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@ManyToMany
@JoinTable(name = "orders", //중간테이블 생성
joinColumns = @JoinColumn(name = "food_id"), //현재 위치에서 중간 테이블 조인
inverseJoinColumns = @JoinColumn(name = "user_id")) //반대 위치에서 중간 테이블 조인
private List<User> userList = new ArrayList<>();
}
------------------------------------------------------------------------------------
고객
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany(mappedBy = "userList")
private List<Food> foodList = new ArrayList<>();
}
test
package com.sparta.jpaadvance.relation;
import com.sparta.jpaadvance.entity.Food;
import com.sparta.jpaadvance.entity.User;
import com.sparta.jpaadvance.repository.FoodRepository;
import com.sparta.jpaadvance.repository.UserRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@SpringBootTest
public class ManyToManyTest {
@Autowired
FoodRepository foodRepository;
@Autowired
UserRepository userRepository;
@Test
@DisplayName("N:M 단방향 테스트")
void test1(){
User user = new User();
user.setName("Robbie");
User user2 = new User();
user2.setName("Robbert");
Food food = new Food();
food.setName("후라이드 치킨");
food.setPrice(15000);
food.getUserList().add(user);
food.getUserList().add(user2);
userRepository.save(user);
userRepository.save(user2);
foodRepository.save(food);
}
}
양방향
음식
package com.sparta.jpaadvance.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter
@Setter
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@ManyToMany
@JoinTable(name = "orders", // 중간 테이블 생성
joinColumns = @JoinColumn(name = "food_id"), // 현재 위치인 Food Entity 에서 중간 테이블로 조인할 컬럼 설정
inverseJoinColumns = @JoinColumn(name = "user_id")) // 반대 위치인 User Entity 에서 중간 테이블로 조인할 컬럼 설정
private List<User> userList = new ArrayList<>();
public void addUserList(User user) {
this.userList.add(user); // 외래 키(연관 관계) 설정
user.getFoodList().add(this);
}
}
-------------------------------------------------------------------------------------
고객
package com.sparta.jpaadvance.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter
@Setter
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany(mappedBy = "userList")
private List<Food> foodList = new ArrayList<>();
public void addFoodList(Food food) {
this.foodList.add(food);
food.getUserList().add(this); // 외래 키(연관 관계) 설정
}
}
test
@Test
@Rollback(value = false)
@DisplayName("N대M 양방향 테스트 : 외래 키 저장 실패")
void test2() {
Food food = new Food();
food.setName("후라이드 치킨");
food.setPrice(15000);
Food food2 = new Food();
food2.setName("양념 치킨");
food2.setPrice(20000);
// 외래 키의 주인이 아닌 User 에서 Food 를 저장해보겠습니다.
User user = new User();
user.setName("Robbie");
user.getFoodList().add(food);
user.getFoodList().add(food2);
userRepository.save(user);
foodRepository.save(food);
foodRepository.save(food2);
// 확인해 보시면 orders 테이블에 food_id, user_id 값이 들어가 있지 않은 것을 확인하실 수 있습니다.
}
@Test
@Rollback(value = false)
@DisplayName("N대M 양방향 테스트 : 외래 키 저장 실패 -> 성공")
void test3() {
Food food = new Food();
food.setName("후라이드 치킨");
food.setPrice(15000);
Food food2 = new Food();
food2.setName("양념 치킨");
food2.setPrice(20000);
// 외래 키의 주인이 아닌 User 에서 Food 를 쉽게 저장하기 위해 addFoodList() 메서드를 생성해서 사용합니다.
// 외래 키(연관 관계) 설정을 위해 Food 에서 userList 를 호출해 user 객체 자신을 add 합니다.
User user = new User();
user.setName("Robbie");
user.addFoodList(food);
user.addFoodList(food2);
userRepository.save(user);
foodRepository.save(food);
foodRepository.save(food2);
}
@Test
@Rollback(value = false)
@DisplayName("N대M 양방향 테스트")
void test4() {
User user = new User();
user.setName("Robbie");
User user2 = new User();
user2.setName("Robbert");
Food food = new Food();
food.setName("아보카도 피자");
food.setPrice(50000);
food.getUserList().add(user); // 외래 키(연관 관계) 설정
food.getUserList().add(user2); // 외래 키(연관 관계) 설정
Food food2 = new Food();
food2.setName("고구마 피자");
food2.setPrice(30000);
food2.getUserList().add(user); // 외래 키(연관 관계) 설정
userRepository.save(user);
userRepository.save(user2);
foodRepository.save(food);
foodRepository.save(food2);
// User 를 통해 food 의 정보 조회
System.out.println("user.getName() = " + user.getName());
List<Food> foodList = user.getFoodList();
for (Food f : foodList) {
System.out.println("f.getName() = " + f.getName());
System.out.println("f.getPrice() = " + f.getPrice());
}
// 외래 키의 주인이 아닌 User 객체에 Food 의 정보를 넣어주지 않아도 DB 저장에는 문제가 없지만
// 이처럼 User 를 사용하여 food 의 정보를 조회할 수는 없습니다.
}
@Test
@Rollback(value = false)
@DisplayName("N대M 양방향 테스트 : 객체와 양방향의 장점 활용")
void test5() {
User user = new User();
user.setName("Robbie");
User user2 = new User();
user2.setName("Robbert");
// addUserList() 메서드를 생성해 user 정보를 추가하고
// 해당 메서드에 객체 활용을 위해 user 객체에 food 정보를 추가하는 코드를 추가합니다. user.getFoodList().add(this);
Food food = new Food();
food.setName("아보카도 피자");
food.setPrice(50000);
food.addUserList(user);
food.addUserList(user2);
Food food2 = new Food();
food2.setName("고구마 피자");
food2.setPrice(30000);
food2.addUserList(user);
userRepository.save(user);
userRepository.save(user2);
foodRepository.save(food);
foodRepository.save(food2);
// User 를 통해 food 의 정보 조회
System.out.println("user.getName() = " + user.getName());
List<Food> foodList = user.getFoodList();
for (Food f : foodList) {
System.out.println("f.getName() = " + f.getName());
System.out.println("f.getPrice() = " + f.getPrice());
}
}
조회
@Test
@DisplayName("N대M 조회 : Food 기준 user 정보 조회")
void test6() {
Food food = foodRepository.findById(1L).orElseThrow(NullPointerException::new);
// 음식 정보 조회
System.out.println("food.getName() = " + food.getName());
// 음식을 주문한 고객 정보 조회
List<User> userList = food.getUserList();
for (User user : userList) {
System.out.println("user.getName() = " + user.getName());
}
}
@Test
@DisplayName("N대M 조회 : User 기준 food 정보 조회")
void test7() {
User user = userRepository.findById(1L).orElseThrow(NullPointerException::new);
// 고객 정보 조회
System.out.println("user.getName() = " + user.getName());
// 해당 고객이 주문한 음식 정보 조회
List<Food> foodList = user.getFoodList();
for (Food food : foodList) {
System.out.println("food.getName() = " + food.getName());
System.out.println("food.getPrice() = " + food.getPrice());
}
}
중간테이블(orders) 컨트롤하기
중간테이블 직접 Entity로 만듬
package com.sparta.jpaadvance.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "food_id")
private Food food;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
}
Food, User Entity도 변경
음식
package com.sparta.jpaadvance.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter
@Setter
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@OneToMany(mappedBy = "food") //order를 통해서 food
private List<Order> orderList = new ArrayList<>();
}
-----------------------------------------------------------------
고객
package com.sparta.jpaadvance.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter
@Setter
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "user")
private List<Order> orderList = new ArrayList<>();
}
테스트코드
package com.sparta.jpaadvance.relation;
import com.sparta.jpaadvance.entity.Food;
import com.sparta.jpaadvance.entity.Order;
import com.sparta.jpaadvance.entity.User;
import com.sparta.jpaadvance.repository.FoodRepository;
import com.sparta.jpaadvance.repository.OrderRepository;
import com.sparta.jpaadvance.repository.UserRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@SpringBootTest
public class OrderTest {
@Autowired
UserRepository userRepository;
@Autowired
FoodRepository foodRepository;
@Autowired
OrderRepository orderRepository;
@Test
@Rollback(value = false)
@DisplayName("중간 테이블 Order Entity 테스트")
void test1() {
User user = new User();
user.setName("Robbie");
Food food = new Food();
food.setName("후라이드 치킨");
food.setPrice(15000);
// 주문 저장
Order order = new Order();
order.setUser(user); // 외래 키(연관 관계) 설정
order.setFood(food); // 외래 키(연관 관계) 설정
userRepository.save(user);
foodRepository.save(food);
orderRepository.save(order);
}
@Test
@DisplayName("중간 테이블 Order Entity 조회")
void test2() {
// 1번 주문 조회
Order order = orderRepository.findById(1L).orElseThrow(NullPointerException::new);
// order 객체를 사용하여 고객 정보 조회
User user = order.getUser();
System.out.println("user.getName() = " + user.getName());
// order 객체를 사용하여 음식 정보 조회
Food food = order.getFood();
System.out.println("food.getName() = " + food.getName());
System.out.println("food.getPrice() = " + food.getPrice());
}
}
주문일 추가
@Entity
@Getter
@Setter
@Table(name = "orders")
@EntityListeners(AuditingEntityListener.class)
public class Order {
@CreatedDate
@Temporal(TemporalType.TIMESTAMP)
private LocalDateTime orderDate;
}
//Application에 추가
@EnableJpaAuditing
지연로딩, 즉시로딩
JPA는 연관관계가 설정된 Entity의 정보를 바로 가져올지, 필요할 때 가져올지 정할 수 있음
즉, 가져오는 방법을 정하게되는데 JPA에서는 Fetch Type이라 부름.
Fetch Type에는 2가지 종류가 있는데 하나는 LAZY, 다른 하나는 EAGER
- LAZY는 지연 로딩으로 필요한 시점에 정보를 가져옴
- EAGER는 즉시 로딩으로 이름의 뜻처럼 조회할 때 연관된 모든 Entity의 정보를 즉시 가져옴
기본적으로 @OneToMany 애너테이션은 Fetch Type의 default 값이 LAZY로 지정되어있고 반대로 @ManyToOne 애너테이션은 EAGER임
@ManyToOne -> EAGER
@OneToMany -> LAZY (@OneToMany collection(ex. List<Food> food)이기 때문에 효율적으로 정보를 읽기 위해서 지연로딩 방식이 Defautl임)
fetchType 변경
@ManyToOne(fetch = FetchType.LAZY)
지연로딩도 영속성 컨텍스트의 기능 중 하나임.
-> 지연 로딩이 된 Entity를 조회할려고 할때 영속성 컨텍스트가 존재해야함. 즉 트랜잭션이 적용되어야있어야함.
영속성 전의
Cascade -> SQL에서 사용하는 방법을 JPA에서 지원해줌
영속성 전이를 적용하여 해당 Entity를 저장할 때 연관된 Entity까지 자동으로 저장하기 위해서는 자동으로 저장하려고 하는 연관된 Entity에 추가한 연관관계 애너테이션에 CASCADE의 PERSIST 옵션을 설정
주인 반대편에 설정해주면 됨
일대다 관계라면 보통 다 쪽이 주인이니 @OnaToMany쪽에 입력
@OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST)
private List<Food> foodList = new ArrayList<>();
userRepository.save(user);
foodRepository.save(food);
foodRepository.save(food1);
------------------------------------------
userRepository.save(user);
삭제
@Test
@Transactional
@Rollback(value = false)
@DisplayName("Robbie 탈퇴")
void test3() {
// 고객 Robbie 를 조회합니다.
User user = userRepository.findByName("Robbie");
System.out.println("user.getName() = " + user.getName());
// Robbie 가 주문한 음식 조회
for (Food food : user.getFoodList()) {
System.out.println("food.getName() = " + food.getName());
}
// 주문한 음식 데이터 삭제
foodRepository.deleteAll(user.getFoodList());
// Robbie 탈퇴
userRepository.delete(user);
}
영속성 전이 삭제
중첩적용
중괄호안에 설정
@OneToMany(mappedBy = "user", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private List<Food> foodList = new ArrayList<>();
연관된 데이터 삭제
Robbie 고객 Entity 객체를 조회한 후 해당 객체를 delete 하자 자동으로 연관된 음식 데이터들이 삭제
@Test
@Transactional
@Rollback(value = false)
@DisplayName("영속성 전이 삭제")
void test4() {
// 고객 Robbie 를 조회합니다.
User user = userRepository.findByName("Robbie");
System.out.println("user.getName() = " + user.getName());
// Robbie 가 주문한 음식 조회
for (Food food : user.getFoodList()) {
System.out.println("food.getName() = " + food.getName());
}
// Robbie 탈퇴
userRepository.delete(user);
}
고아 Entity
연관관계 제거
실제 DB에서는 제거되지 않고 연관관계만 제거
@Test
@Transactional
@Rollback(value = false)
@DisplayName("연관관계 제거")
void test1() {
// 고객 Robbie 를 조회합니다.
User user = userRepository.findByName("Robbie");
System.out.println("user.getName() = " + user.getName());
// 연관된 음식 Entity 제거 : 후라이드 치킨
Food chicken = null;
for (Food food : user.getFoodList()) {
if(food.getName().equals("후라이드 치킨")) {
chicken = food;
}
}
if(chicken != null) {
user.getFoodList().remove(chicken);
}
// 연관관계 제거 확인
for (Food food : user.getFoodList()) {
System.out.println("food.getName() = " + food.getName());
}
}
연관관계를 제거하는 것 만으로도 해당 Entity를 삭제할 수 있음(후라이드 치킨 삭제)
추가로 orphanRemoval 옵션도 REMOVE 옵션과 마찬가지로 해당 Entity 즉, Robbie Entity 객체를 삭제하면 연관된 음식 Entity들이 자동으로 삭제
@OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST, orphanRemoval = true)
private List<Food> foodList = new ArrayList<>();
⚠️ 주의!
orphanRemoval이나 REMOVE 옵션을 사용할 때 삭제하려고 하는 연관된 Entity를 다른 곳에서 참조하고 있는지 아닌지를 꼭 확인해야함
A와 B에 참조되고 있던 C를 B를 삭제하면서 같이 삭제하게 되면 A는 참조하고 있던 C가 사라졌기 때문에 문제가 발생
따라서 orphanRemoval 같은 경우 @ManyToOne 같은 애너테이션에서는 사용할 수 없음
ManyToOne이 설정된 Entity는 해당 Entity 객체를 참조하는 다른 Entity 객체들이 있을 수 있기 때문에 속성으로 orphanRemoval를 가지고 있지 않음
My Select Shop 구축
naver api이용해서 필요 항목 검색
Scheduler기능 구현
특정 시간 마다업데이트를 위해서 설정
@Slf4j(topic = "Scheduler")
@Component
@RequiredArgsConstructor
public class Scheduler {
private final NaverApiService naverApiService;
private final ProductService productService;
private final ProductRepository productRepository;
// 초,분,시,일,월,주 순서
@Scheduled(cron = "0 0 1 * * *") // 매일 새벽 1시 메서드 작동
public void updatePrice() throws InterruptedException {
log.info("가격 업데이트 실행");
List<Product> productList = productRepository.findAll();
for (Product product : productList) {
// 1초에 한 상품 씩 조회합니다 (NAVER 제한)
TimeUnit.SECONDS.sleep(1);
// i 번째 관심 상품의 제목으로 검색을 실행합니다.
String title = product.getTitle();
List<ItemDto> itemDtoList = naverApiService.searchItems(title);
if (itemDtoList.size() > 0) {
ItemDto itemDto = itemDtoList.get(0);
// i 번째 관심 상품 정보를 업데이트합니다.
Long id = product.getId();
try {
productService.updateBySearch(id, itemDto);
} catch (Exception e) {
log.error(id + " : " + e.getMessage());
}
}
}
}
}
Auditiong처럼 @EnableScheduling 설정
@SpringBootApplication
@EnableJpaAuditing
@EnableScheduling
public class MyselectshopApplication {
public static void main(String[] args) {
SpringApplication.run(MyselectshopApplication.class, args);
}
}
'K-Digital Training > 내일배움캠프' 카테고리의 다른 글
숙련 1주차/Spring (2) | 2023.11.21 |
---|---|
입문 2주차/Spring (2) | 2023.11.21 |
입문 1주차/Spring (1) | 2023.11.21 |
자바 5주차 (1) | 2023.11.01 |
자바 4주차 (1) | 2023.11.01 |