ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • ORM - ORM과 문제 해결3 (객체 그래프 탐색, 비교)
    FrameWork/ORM 2024. 1. 22. 11:10

    ORM과 문제 해결3

    객체 그래프 탐색

    객체에서 회원이 소속된 팀을 조회할 대는 다음처럼 참조를 사용해서 연관된 팀을 찾으면 되는데, 이것을 객체 그래프 탐색이라 한다.

    Team team = member.getTeam();

     

    그림 1.5 객체 연관관계

     

    위 객체 그래프를 탐색하는 코드다.

    member.getOrder().getOrderItem()... //자유로운 객체 그래프 탐색

     

    객체는 마음껏 객체 그래프를 탐색할 수 있거야 한다. 그런데 마음껏 객체 그래프를 탐색할 수 있을까?

    SELECT M.*,T.*
        FROM MEMBER M
        JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID

     

    member.getTeam();  //성공
    member.getOrder(); //실패(null)

     

    MemberDAO에서 member 객체를 조회할 때 위와 같은 SQL을 실행해서 회원과 팀에 대한 데이터만 조회했다면 member.getTeam()의 조회는 성공하지만 밑의 member.getOrder()그래프는 데이터가 없으므로 탐색할 수 없다.

     

    즉 SQL에 의존해서 객체의 필드를 조회하면 처음 실행하는 SQL에 따랄 객체 그래프를 어디까지 탐색할 수 있는지가 정해진다. 이것은 객체지향 개발자에겐 너무 큰 제약이다. 왜냐하면 비지니스 로직에 따라 사용하는 객체 그래프가 최조의 SQL의 JOIN 범위에 따라 언제 끊어질지 모르기 때문에 객체 그래프를 함부로 탐색할 수 없기 때문이다.

     

    예제 1.11 회원 조회 비지니스 로직

    class MemberService{
        ...
        public void process(){
            
            Member member = memberDAO.find(memberId);
            member.getTeam(); //member->team 객체 그래프 탐색이 가능한가?
            member.getOrder().getDelivery(); //??
        }
    }

     

    예제 1.11의 MemberService는 memberDAO를 통해서 member 객체를 조회했지만, 이 객체와 연관된 Team, Order, Delivery 방향으로 객체 그래프를 탐색할 수 있을지 없을지 이 코드만 보고는 전혀 예측할 수 없다. 결국, 어디까지 객체 그래프 탐색이 가능한지 알아보려면 데이터 접근 계층인 DAO를 열어서 SQL을 직접 확인해야 한다. 이것은 엔티티가 SQL에 논리적으로 종속되어 발생하는 문제다. 그렇다고 member와 연관된 모든 객체 그래프를 데이터베이스에서 조회해서 애플리케이션 메모리에 올려두는 것은 현실성이 없다. 결국 다음 코드와 같이 MemberDAO에 회원 조회하는 메소드를 상황에 따라 여러 개 만들어서 사용해야 한다.

    memberDAO.getMember();   //Member만 조회
    memberDAO.getMemberWithTeam();    //Member와 Team 조회
    memberDAO.getMemberWithOrderWithDelivery();   //Member와 Order와 Delivery 조회
    ...

     

    객체 그래프를 신뢰하고 사용할 수 있으면 이런 문제를 어느 정도 해소할 수 있다. JPA는 이 문제를 어떻게 해결할까?

     

    JPA와 객체 그래프 탐색

    JPA를 사용하면 객체 그래프를 마음껏 탐색할 수 있다.

    member.getOrder().getOrderItem()... //자유로운 객체 그래프 탐색

     

    JPA는 연관된  객체를 사용하는 시점에 적절한 SELECT SQL을 실행한다. 따라서 JPA를 사용하면 연관된 객체를 신뢰하고 마음껏 조회할 수 있다. 이 기능은 실제 객체를 사용하는 시점까지 데이터베이스 조회를 미룬다고 해서 지연로딩(lazy loading)이라고 한다.

     

    예제 1.12 투명한 엔티티

    class Member{
        private Order order;
        
        public Order getOrder(){
            return order;
        }
    }

     

     

    이런 기능을 사용하려면 객체에 JPA와 관련된 어떤 코드들을 심어야 하는 것은 아닐까? JPA는 지연 로딩을 투명(transparent)하게 처리한다. 예제 1.12의 Member 객체를 보면 getOrder() 메소드의 구현 부분에 JPA와 관련된 어떤 코드도 직접 사용하지 않는다.

     

    예제 1.13은 지연로딩 사용

    //처음 조회 시점에 SELECT MEMBER SQL
    Member member = jpa.find(Member.class,memberId);
    
    Order order = member.getOrder();
    order.getOrderDate(); //Order를 사용하는 시점에 SELECT ORDER SQL

     

    위 예제는 지연 로딩을 사용하는 코드다. 마지막 줄의 order.getOrderDate() 같이 실제 Order 객체를 사용하는 시점에 JPA는 데이터베이스에서 ORDER 테이블을 조회한다.

     

    Member를 사용할 때마다 Order를 함께 사용하면, 이렇게 한 테이블씩 조회하는 것보다는 Member를 조회하는 시점에 SQL 조인을 사용해서 Member Order를 함께 조회하는 것이 효과적이다.

     

    SELECT M.*,O.*
        FROM `MEMBER` M
        JOIN ORDER O ON M.MEMBER_ID = O.MEMBER_ID

     

    JPA는 연관된 객치를 즉시 함께 조회할지 아니면 실제 사용되는 시점에 지연해서 조회할지를 간단한 설정으로 정의할 수 있다. 만약 Member와 Order를 즉시 함께 조회하겠다고 설정하면 JPA는 Member를 조회할 때 다음 위 SQL을 실행해서 연관된 Order도 함께 조회한다.


    비교

    데이터베이스는 기본 키 값으로 각 로우(row)를 구분한다. 반면에 객체는 동일성(identity)비교와 동등성(equality)비교라는 두가지 비교 방법이 있다.

    • 동등성 비교는 == 비교다. 객체 인스턴스의 주소 값을 비교한다.(물리적 동일성)
    • 동등성 비교는 equals() 메소드를 사용해서 객체 내부의 값을 비교한다.(논리적 동일성)

    따라서 테이블 로우 구분하는 방법과 객체를 구분하는 방법에는 차이가 있다. 

     

    예제1.14 MemberDAO 코드

    class MemberDAO{
        
        public Member getMember(String memberId){
            String sql = "SELECT * FROM MEMBER WHERE MEMBER_ID = ?";
            ...
            //JDBC API, SQL 실행
            return new Member(...);
        }
    }

     

    예제1.15 조회한 회원 비교하기

    String memberId = "100";
    Member member1 = memberDAO.getMember(memberId);
    Member member2 = memberDAO.getMember(memberId);
    
    member1 == member2;  //다르다.

     

    위 코드에서 member1과 member2는 동일성(identity)측면에서는 다른 객체이지만 동등성(equality)측면에서는 같은 객체이다. 

     

    따라서 데이터베이스의 같은 로우를 조회했지만 객체의 동일성 비교에는 실패한다. 만약 객체를 컬렉션에 보관했다면 다음과 같이 동일서 비교에 성공했을 것이다.

    Member member1 = list.get(0);
    Member member2 = list.get(0);
    
    member1 == member2 //같다.

     

    이런 패러다임의 불일치 문제를 해결하기 위해 데이터베이스의 같은 로우를 조회할 때마다 같은 인스턴스를 반환하도록 구현하는 것은 쉽지 않다. 여기에 여러 트랜잭션이 동시에 실행되는 상황까지 고여하면 문제는 더 어려워진다.

     

    JPA와 비교

    JPA는 같은 트랜잭션일 때 같은 객체가 조회되는 것을 보장한다. 그러므로 다음 코드에서 member1과 member2는 동일성 비교에 성공한다.

    String memberId = "100";
    Member member1 = jpa.find(Member.class, memberId);
    Member member2 = jpa.find(Member.class, memberId);
    
    member1 == member2; //같다.

     

    사실 객체 비교하기는 분산 환경이나 트랜잭션이 다른 상황까지 고려하면 더 복잡해 진다.

     

     

    출처 - [자바 ORM 표준 JPA 프로그래밍 - 저, 김영한]

    http://www.acornpub.co.kr/book/jpa-programmig\

     

    자바 ORM 표준 JPA 프로그래밍

    JPA 기초 이론과 핵심 원리, 그리고 실무에 필요한 성능 최적화 방법까지 JPA에 대한 모든 것

    www.acornpub.co.kr

     

    'FrameWork > ORM' 카테고리의 다른 글

    ORM - JPA설정  (1) 2024.02.14
    ORM - JPA란 무엇인가?  (0) 2024.01.22
    ORM - ORM과 문제 해결2(연관관계)  (1) 2024.01.22
    ORM - ORM과 문제 해결1(상속)  (0) 2024.01.22
    ORM- SQL을 직접 다룰 때 발생하는 문제점  (0) 2024.01.20

    댓글

Designed by Tistory.