- Bean 객체를 수동등록 하는법
필요한 이유 : 기술적인 문제나 공통적인 관심사를 처리하는 사용하는 객체들은 수동으로 등록할 필요생김,
기술 지원 Bean이라 불림, 수동으로 등록된 Bean에서 문제발생시 위치파악 쉬움
- Bean 객체를 반환하는 메서드 선언
- 해당 메서드 위에 @Bean 추가
- 메서드 정의된 해당클래스위에 @Configuration 추가
== 같은타입의 Bean이 두개이상 있다면 ==
구현체클래스들(다수)을 Bean으로 등록하면, 해당하는 인터페이스 역시 Bean으로 등록된다
등록은 문제가 없음
이후 인터페이스타입 필드를 AutoWired할시, 구현체가 자동으로 주입된다
이때, 인터페이스 구현체 클래스가 2개 이상이고 둘다 Bean객체로 등록되어있다면 자동등록에서 오류
public interface Food {
void eat();
}
@Component
public class Chicken implements Food {
@Override
public void eat() {
System.out.println("치킨을 먹습니다.");
}
}
@Component
public class Pizza implements Food {
@Override
public void eat() {
System.out.println("피자를 먹습니다.");
}
}
// 위 2개의 구현체중 어떤것을 자동으로 연결?
@SpringBootTest
public class BeanTest {
@Autowired
Food food;
}
- 해결방법
- 구현체클래스 이름을 명명 (Autowired는 기본적으로 타입으로 먼저 찾는다는걸 알수있다)
- @Primary 애너테이션을 구현체 클래스에 사용, 우선순위를 정해줌
- @Qualifier("이름") 애너테이션을 구현체 클래스에 사용후 @Autowired한 필드에 @Qualifier("이름") 애너테이션
@Autowired
Food pizza;
@Autowired
Food chicken;
@Component
@Primary
public class Chicken implements Food {
...
}
@Component
@Qualifier("pizza")
public class Pizza implements Food {
...
}
결론 : 같은타입의 Bean객체가 여러개일때 범용적으로 사용되는것 = @Primary 사용
지역적(특정) 으로 사용될땐 @Qualifier 사용
- RestTemplate 이란??
서버 to 서버를 대상으로 편리하게 동작하기 위한것
서비스 개발을 진행하다보면 기본 라이브러리만으로는 구현이 힘든 기능이 무수히 많음 (ex 지도관련, 주소찾기 등)
이럴때는 다른서버 (ex 카카오) 에서 구현한 api를 가져와 써야함 이때 서버 to 서버의 요청과 응답이 방식
클라이언트입장(서버) - 서버입장(서버) 작업
순서 흐름 : 포스트맨(브라우저) -> 클라서버쪽 컨트롤러 -> 클라서비스 -> 서버서버쪽 컨트롤러 -> 서버서비스 -> 역방향
RestTemplate 주입받기
// 기본 틀
@Slf4j
@Service
public class RestTemplateService {
public ItemDto getCallObject(String query) {
return null;
}
public List<ItemDto> getCallList() {
return null;
}
public ItemDto postCall(String query) {
return null;
}
public List<ItemDto> exchangeCall(String token) {
return null;
}
}
/// 이후 주입코드
private final RestTemplate restTemplate;
// RestTemplateBuilder의 build()를 사용하여 RestTemplate을 생성합니다.
public RestTemplateService(RestTemplateBuilder builder) {
this.restTemplate = builder.build();
}
- RestTemplate의 Get 요청
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);
//get 방식으로 요청을 보내면서 요청응답으로 받은 JSON을 알아서 클래스타입으로 역직렬화 해준후
//반환하는 메서드
log.info("statusCode = " + responseEntity.getStatusCode());
return responseEntity.getBody();
}
여러개의 객체 요청시 (Json build)
// json
implementation 'org.json:json:20230227' << gradle에 적용
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());
}
//////////////////////////////////////
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;
}
// 이게 통짜 String으로 전달됨
{
"items":[
{"title":"Mac","price":3888000},
{"title":"iPad","price":1230000},
{"title":"iPhone","price":1550000},
{"title":"Watch","price":450000},
{"title":"AirPods","price":350000}
]
}
//JSONArray : JSON 안의 JSON 형식(위와같은) 의 String 값을 변환해줄때 사용
//JSONObject : 통짜 String값을 JSONArray를 통해 변환한 배열안의 하나의 객체
-RestTemplate의 Post 요청
public ItemDto postCall(String query) {
// 요청 URL 만들기
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:7070")
.path("/api/server/post-call/{query}")
.encode()
.build()
.expand(query)
.toUri();
log.info("uri = " + uri);
User user = new User("Robbie", "1234"); // Body에 해당하는 값
ResponseEntity<ItemDto> responseEntity = restTemplate.postForEntity(uri, user, ItemDto.class);
// 받을때 뿐 아니라 보낼때 user 객체도 직렬화해서 보내줌
log.info("statusCode = " + responseEntity.getStatusCode());
return responseEntity.getBody();
}
-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);
// RequistEntity를 사용해서 uri, header, body를 하나의 entity로 묶어 보냄
ResponseEntity<String> responseEntity = restTemplate.exchange(requestEntity, String.class);
return fromJSONtoItems(responseEntity.getBody());
}
-JPA entity 연관관계
여러개의 테이블을 사용하게 되었을때 (고객, 음식)
고객-음식 = 1대N (한명의 고객이 여러음식 주문)
음식-고객 = 1대N (하나의 음식은 여러 고객에게 주문될수 있음)
최종적으로 고객-음식 = N대M, N대M 테이블의 연관관계에서 각 테이블내에 이름 중복문제를 해결하기 위한 중간테이블이 필요(orders)
JPA에서 Entity로 연관성 DB 표현
- DB에서 연관테이블간에는 방향이 존재 X (Join을 통해 무조건 양방향 가능), 하지만 JPA entity 클래스에선 참조에 따른 방향존재
- @OneToOne : 1대1 관계표현 애너테이션
단방향 (외래키의 주인은 Food)
@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;
}
⚠️ 주의!
1. 외래 키의 주인 Entity에서 @JoinColumn() 애너테이션을 사용하지 않아도 default 옵션이 적용되기 때문에 생략이 가능합니다. - 다만 1 대 N 관계에서 외래 키의 주인 Entity가 @JoinColumn() 애너테이션을 생략한다면 JPA가 외래 키를 저장할 컬럼을 파악할 수가 없어서 의도하지 않은 중간 테이블이 생성됩니다. - 따라서 외래 키의 주인 Entity에서 @JoinColumn() 애너테이션을 활용하시는게 좋습니다.
2. 양방향 관계에서 mappedBy 옵션을 생략할 경우 JPA가 외래 키의 주인 Entity를 파악할 수가 없어 의도하지 않은 중간 테이블이 생성되기 때문에 반드시 설정해주시는게 좋습니다.
양방향
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToOne(mappedBy = "user") //@OneToOne(mappedBy = 외래키주인 참조필드명) 추가
private Food food;
}
- @ManyToOne : N대1 관계표현 애너테이션
단방향 (Food가 N, User가 1)
public class Food {
...
@ManyToOne // 이부분만 수정
@JoinColumn(name = "user_id")
private User user;
}
양방향
public class User {
...
@OneToMany(mappedBy = "user") // 애노테이션 수정, List 형식 (N개의 Food 저장)
private List<Food> foodList = new ArrayList<>();
}
이와 별개로 실제 JPA 메서드를 통한 Entity 작업시 (save 메서드 등 사용)
외래키 주인이 아닌 Entity는 set을 통해 단순 저장시 DB에 재대로 반영이 안된다
Food food = new Food();
food.setName("고구마 피자");
food.setPrice(30000);
User user = new User();
user.setName("Robbie");
user.addFood(food); // user의 경우 단순setter로 저장하면 DB에 재대로 반영x 따로메서드 만들어줘야함
userRepository.save(user);
foodRepository.save(food);
// food는 setter로 외래 키 객체 바로 받기가능
User user = new User();
user.setName("Robbert");
Food food = new Food();
food.setName("고구마 피자");
food.setPrice(30000);
food.setUser(user); // 외래 키(연관 관계) 설정
=================================
public void addFood(Food food) {
this.food = food;
food.setUser(this); // 외래키 주인객체 food를 받고
//그 food에서 자기자신을 외래키 등록해주는 코드를 추가로넣어줌
}
}
- @OneToMany : 1대N 관계표현 애너테이션
단방향
@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 컬럼
private List<User> userList = new ArrayList<>();
}
거의 사용되지 않는 경우지만.. 일반적으로 FK는 N 관계의 테이블에 위치한다, 하지만 외래키의 주인을 구지 1에 해당하는
food로 둘 경우, @JoinColumn()은 외래키 컬럼을 상대 테이블에(N) 생성해준다
고로 위 같은경우 상대 테이블에 컬럼이 생성되므로 컬럼명은 food_id로 하는게 맞다
@Test
@Rollback(value = false)
@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);
// 추가적인 UPDATE 쿼리 발생을 확인할 수 있습니다.
}
양방향
1 대 N 관계에서는 일반적으로 양방향 관계가 존재하지 않습니다.
@ManyToOne 애너테이션은 mappedBy 속성을 제공하지 않습니다.
- @ManyToMany : N대M 관계표현 애너테이션
단방향
public class Food {
@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 class User {
@ManyToMany(mappedBy = "userList")
private List<Food> foodList = new ArrayList<>();
}
위와 같은경우 @JoinTable로 인해 자동으로 생성되는 중간테이블의 경우 따로 관리하기 까다로움
-> 직접 만들어 줄수있다
중간 테이블(Order)
@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;
}
=============================
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@OneToMany(mappedBy = "food")
private List<Order> orderList = new ArrayList<>();
}
=============================
고객
@Entity
@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<>();
}
각각의 테이블 기준 주문테이블 = N, 각 테이블 = 1의 관계
'TIL > Web Back' 카테고리의 다른 글
[Sparta] 내일배움캠프 TIL 24일차 (0) | 2024.05.27 |
---|---|
[Sparta] 내일배움캠프 TIL 23일차 (0) | 2024.05.24 |
[Sparta] 내일배움캠프 TIL 19일차 (0) | 2024.05.17 |
[Sparta] 내일배움캠프 TIL 18일차 (0) | 2024.05.16 |
[Sparta] 내일배움캠프 TIL 17일차 (1) | 2024.05.14 |