본문 바로가기

MSA

JAVA 객체로 먼저 보는Join 없는 연관 관계 이해하기

이 내용은 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