이 내용은 MSA에서 DB가 분리된 상황에서 어떻게 join 없이 연관관계가 가능한지에 대한 의문점에서 시작된 내용입니다.
1. MSA에서 JOIN
MSA에서는 되도록이면 조인을 하지 않는 것이 좋다.
하나의 서비스가 죽으면 다른 서비스는 작동해야 하는데 조인된 상태에서는 연관 테이블을 얻어오지 못하기 때문에 같이 죽는 경우가 발생한다. 이러한 상황은 MSA 장점을 살리지 못한 상황이다.
하지만,, 만약에 조인이 불가피한 상황인 경우 join 없이 작동하는 방식을 사용한다.
실제 MSA에서는 getOrderByUserId와 같은 코드를 Spring Fegin을 사용하여 다른 DB에서 연관테이블에 있는 데이터을 가져오는부분에 해당하는 코드로 교체한다.
아래 소스코드는 JAVA로 먼저 Join 없는 연관관계를 이해하기 위한 실습이다.
소스코드
1. User
package entity;
public class User {
private int userId;
private String name;
private int age;
public User(int userId, String name, int age) {
this.userId = userId;
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", name='" + name + '\'' +
", age=" + age +
'}';
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
2. Order
package entity;
public class Order {
private int userId;
private String itemName;
private int count;
public Order(int userId, String itemName, int count) {
this.userId = userId;
this.itemName = itemName;
this.count = count;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
@Override
public String toString() {
return "Order{" +
"userId=" + userId +
", itemName='" + itemName + '\'' +
", count=" + count +
'}';
}
}
3.UserOrderDto
package dto;
import entity.Order;
import java.util.List;
// Getter , Setter 필수
// 필드를 합친 DTO를 만들어두고
public class UserOrderDto {
private int userId;
private String name;
private int age;
// 연관관계를 가져갈 수 있는 Order목록
private List<Order> orderList;
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public List<Order> getOrderList() {
return orderList;
}
public void setOrderList(List<Order> orderList) {
this.orderList = orderList;
}
@Override
public String toString() {
return "UserOrderDto{" +
"userId=" + userId +
", name='" + name + '\'' +
", age=" + age +
", orderList=" + orderList +
'}';
}
}
4.OrderRepository
package repository;
import entity.Order;
import java.util.List;
public interface OrderRepository {
// 특정 유저의 전체 구매 내역 얻기
public List<Order> getOrderByUserId(int userId);
}
5. OrderMapRepository
package repository;
import entity.Order;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class OrderMapRepository implements OrderRepository{
private Map<Integer,Order> orderList;
public OrderMapRepository(){
// 초기화
this.orderList = new HashMap<Integer,Order>();
// 더미데이터를 Map 형태로 넣는다
Order order1= new Order(1,"감자",3);
Order order2= new Order(1,"고구마",4);
Order order3= new Order(1,"맥북",1);
Order order4= new Order(2,"감자튀김",4);
orderList.put(1,order1);
orderList.put(2,order2);
orderList.put(3,order3);
orderList.put(4,order4);
}
@Override
public List<Order> getOrderByUserId(int userId) {
List<Order> userOrderList = new ArrayList<>();
// 조회하는 유저의 주문리스트면 리스트에 저장, 아니면 그냥 넘어가는 로직
for(int i=1; i<5; i++){
if(orderList.get(i).getUserId() == userId){
userOrderList.add(orderList.get(i));
}
}
return userOrderList;
}
}
6.UserRepository
package repository;
import entity.User;
import java.util.List;
public interface UserRepository {
// 레파지토리의 종류를 가리지 않고 클라이언트들이 메서드만 호출하면 같은 정보를 얻을 수 있다는 보장이 된다.
public List<User> getAllUser();
public User getUserByUserId(int userId);
}
7.UserMapRepository
package repository;
import entity.User;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class UserMapRepository implements UserRepository{
private Map<Integer,User> userMap;
public UserMapRepository(){
this.userMap = new HashMap<Integer, User>();
User user1 = new User(1,"태양인",20);
User user2 = new User(2,"찌디",21);
User user3 = new User(3,"자이언틱",25);
userMap.put(1,user1);
userMap.put(2,user2);
userMap.put(3,user3);
}
@Override
public List<User> getAllUser() {
// Map을 List로 변환 필요
List<User> userList = new ArrayList<>();
for(int i =1; i<4; i++){
userList.add(userMap.get(i));
}
return userList;
}
@Override
public User getUserByUserId(int userId) {
for(int i =1; i<4; i++){
if(userMap.get(i).getUserId() == userId){
return userMap.get(i);
}
}
return null;
}
}
8.Main
package main;
import dto.UserOrderDto;
import entity.Order;
import entity.User;
import repository.OrderMapRepository;
import repository.OrderRepository;
import repository.UserMapRepository;
import repository.UserRepository;
import java.util.List;
// 내부에서 조인 형식으로 User가 가지고 있는 구매내역을 가져와보겠습니다.
public class Main {
public static void main(String[] args) {
// 맵 형태 자료를 저장소에서 꺼낼 수 있도록 레포지토리 객체 생성
OrderRepository orderRepository = new OrderMapRepository();
UserRepository userRepository = new UserMapRepository();
// 구매 내역 있음
User user1 = userRepository.getUserByUserId(1);
// 구매 내역 없음
User user3 = userRepository.getUserByUserId(3);
System.out.println(user1);
System.out.println(user3);
// 조인처럼 동작시키기 위해서 특정 유저의 구매 내역을 다 가져오기
List<Order> orderList1 = orderRepository.getOrderByUserId(user1.getUserId());
List<Order> orderList3 = orderRepository.getOrderByUserId(user3.getUserId());
System.out.println(orderList1);
System.out.println(orderList3);
// user1과 order1 조인 형식으로 객체에 저장하기.
UserOrderDto userOrderDto1 = new UserOrderDto();
userOrderDto1.setUserId(user1.getUserId());
userOrderDto1.setAge(user1.getAge());
userOrderDto1.setName(user1.getName());
userOrderDto1.setOrderList(orderList1);
// 조인 여부 체크
System.out.println(userOrderDto1);
// user3과 order3 조인 형식으로 객체에 저장하기
UserOrderDto userOrderDto3 = new UserOrderDto();
userOrderDto1.setUserId(user3.getUserId());
userOrderDto1.setAge(user3.getAge());
userOrderDto1.setName(user3.getName());
userOrderDto1.setOrderList(orderList3);
System.out.println(userOrderDto3);
}
}
실행결과
실행결과를 보면, JPA 연관관계 join과 같은 형식으로 출력되는 것을 확인할 수 있습니다.
# toString 미작성 시
아래와 같이 나오는 경우 User와 Order에서 toString 메소드를 미작성한 경우임으로 작성해주자.
"C:\Program Files\Java\jdk-17\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2023.2.3\lib\idea_rt.jar=51833:C:\Program Files\JetBrains\IntelliJ IDEA 2023.2.3\bin" -Dfile.encoding=UTF-8 -classpath C:\DevSoo\MSABackend\msaJoin\out\production\msaJoin main.Main
entity.User@3d075dc0
entity.User@214c265e
'MSA' 카테고리의 다른 글
Spring Cloud Config 서버를 활용한 DB 정보관리 (0) | 2023.11.01 |
---|---|
Spring Cloud Config 서버를 활용한 연동 (1) | 2023.11.01 |
디스커버리 서버와 유레카 적용 사례 (0) | 2023.10.25 |
기존 마이크로서비스를 Eureka 클라이언트로 등록 (0) | 2023.10.24 |
스프링 디스커버리를 위한 Eureka 클라이언트 생성 (1) | 2023.10.24 |