Bactoria 황준오

JPA00. ORM과 RDB

2019-06-06
bactoria
JPA

현재 팀프로젝트를 하면서 JPA를 사용하기로 했다. 공부는 인프런에서 백기선님의 JPA강의를 보면서 공부하기로 했다. 6월달은 15% 할인이라 만원정도 싸게 구매했다. 이로써 백기선님의 강좌만 4개 째 듣게 되었다.

블로깅의 상당수는 강좌 내용이 될 것 같다. 최근 유튜브 스트리밍 방송에서 개인 블로그에 포스팅은 얼마든지 괜찮다고 말씀해주셔서 마음 편히 정리하면서 공부하고자 한다. (몇일 뒤 인프런에도 포스팅 관련 공지가 떴다.

   

ORM (Object-Relation Mapping) ?

ORM : 객체를 테이블로 매핑하는 기술이다? 는 ORM의 아주 일부 기능임

ORM의 구현체인 jpa, hibernate의 특징들이 있음. 이 특징들을 알아야 코딩을 할 때 비효율적인 쿼리를 방지할 수 있음.

Class와 Table 사이의 매핑 정보를 기술한 메타데이터를 사용하여, 객체를 테이블에 자동으로 영속화해주는 기술

 

전통적인 사용 방식 (JDBC)

(java7 이상)

try(Connection connection = DriverManager.getConnection(url, username, password)) {
    String sql = "INSERT INTO ACCOUNT VALUES(1, "keesun", "pass");";
    try(PreparedStatement statement = connection.prepareStatement(sql)){
        statement.execute();
}

비즈니스와 관계없는(침투적인) 코드가 많음. (자바7 이전에는 리소스 반납 처리를 해야 해서 코드가 더 길었다고.. )

 

Domain Model 방식

Account account = new Account("keesun", "pass");
accountReository.save(account);

JDBC와 관련된(침투적) 코드가 없어서 비즈니스에 집중할 수 있다. (유지보수성 좋다.) => 자신의 코드를 숨기려 하는 비침투적인 스프링의 철학과 비슷

생산성 굿. DDL도 알아서 만들어줌. CRUD도 뚝딱뚝딱!

그럼 성능은? => JPA에 대해서 충분한 학습 없이 사용하면 구릴 수 있음. 많은 학습이 필요함.

   

Java와 RDB의 패러다임 불일치를 ORM이 해결해준다!

JAVA <–> RDB 는 패러다임이 서로 다른 부분이 존재함. 어떠한 차이점이 있는지 확인해보자. ORM이 어떤 일을 하는건지를 알 수 있을거임.

RDB: 데이터를 어떻게 하면 잘 저장하고 정규화하고 할 수 있을까

객체: 상속,

 

1) 밀도(Granularity)

Public class User {
    private String address;
}

=> 이건 User 테이블에 address 필드 (VARCHAR(45)) 넣어주면 되겠구먼!

 

Public class User {
    private Address address;
}

=> Address와 같은 커스텀한 타입(객체)를 RDB에서는 표현 못함. 이걸 어떻게 매핑시킬꺼냐?

 

2) 서브타입(Subtype)

객체는 상속하기 쉽다. (다형성) 테이블은 상속이 없다. (다형성 없음)

 

유저는 여러가지 결제 수단중에서 한가지의 결제수단를 가짐.

public class User {
    private BillingAccount billingAccount;

}

BillingAccount : 사용자가 사용하는 결제수단 (인터페이스)

BillingAccount의 구현체 : BankAccount.class, CreditCard.class

RDB에서는 USER 테이블에서 두 결제수단 테이블의 PK를 FK로 가질 수 있을까?? 아니 없다.

테이블 간에 FK는 한 테이블이 다른 테이블의 PK를 FK로 사용하는 것임. 다른 두 테이블에 있는 외래키를 설정할 수는 없음. (다른 두 테이블간의 외래키가 겹칠수도 있고..)

 

3) 식별성 (Identity)

Java에서는 인스턴스가 같은지 확인할 때 2가지 방법이 있음.

  1. 레퍼런스의 동일성 체크 (==)
  2. 인스턴스의 동일성 체크 (.equals())

자바에서 권장하는 동일성 체크는 equals이나, RDB로 변환시키기에는 equals()로 충분하지 않음.

어떤 한 테이블에서 두 레코드(로우) 간의 동일성은 PK로 확인함. PK가 같냐 다르냐에 따라 레코드가 같은지 아닌지를 판단함. 하지만 Java 패러다임에는 PK 개념이 없음.

엥? equals 재정의하면 pk처럼 쓸수있는거 아닌가..?

 

4) 연관 관계 (Association)

Java에서 관계는 레퍼런스로 표현

public class Study {
    private User owner;
    private Set<User> users;
    // ...
}

Study에 User를 레퍼런스로 등록함으로써 두 클래스의 관계가 생김. 이 관계에는 방향이 있다. (Study에서 User를 참조.)

User에서 Study를 참조하고 싶다면 User 클래스에 Set를 추가하면 됨.

RDB에서의 관계는 오로지 FK로만 표현함. Study의 FK에는 User의 PK가 있음. RDB는 단방향이 없음. 관계 맺으면 단방향임.

물리적 모델링에서의 카디널리티는 무조건 1:1 / 1:N 임. N:N가 없음. 허나, 자바 패러다임은 N:N를 허용함 (ORM이 객체에서 릴레이션으로 표현할 때 N:N을 해소해줌.)

 

데이터 네비게이션 (Navigation)

레퍼런스를 타고 원하는대로 타고 타고 가서 원하는 데이터를 갖는거.

릴레이션에서는 이런 기능 없음. 원하는 데이터를 매번 조인해서 가져와야 함.

ex) 어떤 한 스터디의 스터디장이 참여하는 스터디들의 스터디장 정보를 원해.

// Study.class 일부
getOwner().getMyStudy().stream().forEach(s -> s.getOwner());
  • 어떤 한 스터디의 데이터 받아오기 (Study 테이블 쿼리)
  • 그 스터디의 스터디장 정보(FK)를 사용해서 유저 정보 받아오기 (USER 테이블 쿼리)
  • 그 유저의 USER 정보를 사용해서 참여하고 있는 스터디 정보 받아오기 (스터디 이력 테이블 쿼리)
  • 각 스터디들의 오더정보들을 쿼리 (Study 테이블 쿼리)

=> 다수의 쿼리가 발생함. 성능 매우 안좋아져!!

 

한 트랜잭션 내에서 많은 조인을 하는 방법이 있지만, 이것도 문제임. 데이터 왕창 메모리에 올려놓는 것은 비효율적(DB 부하, 앱 메모리부하 ) 정말 그 많은 데이터를 쓰는지… 고민을 해봐야함.

하이버네이트에서 lazyLoading을 사용할 때, 주의해서 사용해야 함. N+1 Problem이 있음. (이 문제는 이동욱님 블로그에도 있음. 근데 아직 이해가 안됨.ㅠ)

getMyStudy() 할 때 조인하지 않고 스터디 목록만 읽어와서 스터디 목록을 화면에 표시할 때 마다 셀렉트한다면 이 때 N+1 문제가 발생

=> 스터디 목록 처음에 읽어올 때 1번 읽음(1) + 그 다음에 스터디 목록을 순회하면서 n번의 쿼리가 발생함(N) 성능 구데기임.

이를 해결하려면

  • 애초에 스터디를 쿼리할때 조인해서 오너정보를 가져오는 방법
  • LazyLoading 할 때 한번에 큰 뭉텅이로 (ex. 50개씩 가져와라) 가져오는 방법 (N/50 + 1)

 

이러한 패러다임의 차이를 줄이고 줄여서 상용화할 수 있도록 호환되도록 노력하는게 ORM 임. 어렵고, 러닝커브가 크다는 것이 이런 패러다임의 차이 때문임.

   

번외

ORM의 사실과 오해 - OKKY

4년 전, OKKY에서 ORM과 Mapper에 대한 토론글이다. 양이 매우 방대하다. 읽다가 말았음.

 

Alternatives to JPA

쿠팡의 배성혁님이 네이버 D2에서 발표하신 강연이다. 강연의 초반 20분 정도는 JPA의 장단점, 어떻게 대해야 하는지에 대한 설명이 있어서 좋았다. 대충 끄적여보면, 자신의 프로젝트에 ORM, MyBatis 선택은 시스템 성격상 달라져야 한다.(솔루션 업체는 고객이 원하는 DB에 맞춰서 개발해야 하니까 ORM 쓰면 좋음. 서비스 업체는 DB를 깊숙한 곳까지 최적화 해야 하는데 굳이 ORM을 써야할까) 어쨌든 JPA쓸꺼면 제대로 알고 써야 하며, 러닝커브가 큼. JPA 정말 잘 알고 써야하고, JPA 쓴다고 해서 SQL를 몰라서도 안됨. (@NatualID, @DynamicInsert, @LazyCollection 등을 제대로 활용 안하면서 JPA 꾸지다는 사람은 공부를 깊이 안한걸로..)

이 영상에 좋은 내용이 아주 많음. 앞으로 영상을 반복해서 볼 것 같음.


다음글 currentUser

황준오 (Bactoria) 황준오 (Bactoria)

.

Comments

Content