JPA

정지원·2022년 2월 4일
0

배경

객체지향 언어와 관계형 DB를 사용할때, 관계형 DB의 언어 SQL 중심적으로 개발 하게 되는 상황이 발생하였다.
이는

  1. CRUD 의 무한반복 및 지루한 코드
    -> 더하여 필드가 추가 되었을때 , 쿼리에 한땀한땀 삽입 해야하기 때문에 확장성 또한 좋지 않다.

  2. 관계형 데이터베이스의 패러다임 : 데이터를 정규화하여 보관 ,
    객체 지향프로그래밍의 패러다임 : 프로그래밍에서 필요한 데이터를 추상화시켜 상태와 행위를 가진 객체를 만들고 그 객체들 간의 유기적인 상호작용 [캡슐화, 상속 , 다형성 등을 통한].
    이처럼 애초에 가지고 있는 패러다임 자체가 다르기 때문에 객체를 관계형 데이터베이스 안에 넣으려고 하면 여러가지 문제가 생길 수 있다.

객체 -> 관계형 데이터베이스 넣으려고 할때의 문제점

  1. 상속관계
    SQL 에서 정확한 상속의 기능은 없지만 유사한 슈퍼타입과 서브타입이 존재한다.


https://velog.io/@cco2416/Database%EC%A0%95%EB%A6%AC3참조

위 사진은 객체의 상속 관계를 나타낸것이고 밑에는 SQL의 슈퍼타입과 서브타입 관계를 나타낸것이다.

객체를 그림의 테이블 안에 저장 하기 위해서는 슈퍼타입의 테이블과 서브타입의 테이블에 각각 insert 쿼리를 삽입해주어야한다.

더하여 조회 할때에는 영화 테이블과 코메디 테이블을 Join 하고, Join한 데이터를 객체에 맞게 다시 분배해주어야 한다.

액션 일때에는 액션과 영화 테이블의 Join 쿼리를 만들어 객체에 맞게 다시 분배해주고 , 테이블이 여러개일수록 굉장히 복잡해질수 밖에 없다.

만약 SQL이 아닌 자바 컬렉션에 저장을 하게 된다면 ??

저장도 list .add 를 통해 간편하게 조회할 수 있고 ,
조회 또한 get(unique id[예시]) 를 통해 간단히 구현 할 수 있다.
더하여 부모타입[Movie]으로 조회[list.get(unique id)] 하여 다형성을 활용 할 수도 있다.

  1. 연관관계

객체는 참조를 사용. ex) Movie.getMovieName()
SQL 은 foreign key [외래키]를 사용한다.

객체는 단방향으로 참조가 가능하지만 SQL은 외래키를 통해 양방향으로 (join) 흐를수 있다.

위의 연관관계에서 쿼리를 작성한다고 가정 했을때 [객체지향적으로 TEAM 클래스 참조 형식]

INSERT INTO MEMBER (MEMBER_ID, TEAM_ID, USERNAME) VALUES ...

여기서 TEAM_ID (외래키) 를 member.getTeam.getId() 를 통해 키값을 받아와 INSERT 문을 완성 할 수 있다.

하지만 문제는 .. 조회 할때 ..
Member 테이블, Team 테이블의 데이터를 join 해서 합쳐진 데이터들을 객체의 필드에 맞게 넣어주고,
마지막으로 member.setTeam(team)을 통해 연관 관계를 직접 넣어줘야한다.

이또한 자바 컬렉션으로 관리 한다면??

Member member = list.get(memberId);
Team team = member.getTeam();

이 두줄이면 끝난다.

  1. 비교하기

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

// member1 != member2
// DAO 클래스에서 getMember 메소드를 통해 같은 아이디라도 서로 다른 객체를 생성하여 비교하기 때문이다.

자바 컬렉션으로 관리 할 경우??

String memberId = "100"
Member member1 = list.get(memberId);
Member member1 = list.get(memberId);

//member1 == member2

결과적으로 , 객체답게 모델링 할수록 매핑작업이 늘어난다. 객체를 자바 컬렉션에 저장 하듯이 관계형 데이터베이스에 저장 할수는 없을까??

JPA [Java Persistence API]

  • 자바 진영의 ORM 기술 표준 [ORM : 객체는 객체대로, RDB 는 RDB 대로 설계를 하고 ORM 프레임워크가 중간에서 매핑]

JPA를 사용해야하는 이유

  1. SQL 중심적인 개발에서 객체 중심으로 개발 할 수 있다. [ORM]

  2. 생산성
    -> 복잡한 CRUD 쿼리문을 직접 작성하지 않고도, jpa.persist() - 저장 , jpa.find() - 조회, member.setName() - 수정, jpa.remove() - 삭제 등의 메소드를 통해 구현 가능하다.

  3. 유지보수
    -> 기존 jpa를 쓰지 않았을 경우, Member 클래스 필드에 한가지를 추가 하려면 모든 SQL 문을 수정 했어야 했다. INSERT , SELECT , UPDATE 등등 .. JPA 는 컬럼만 추가 하면 된다. [쿼리문 SQL 문을 수정하지 않아도 된다.]

  4. 패러다임의 불일치 해결

  • 상속관계
    jpa.persist() 호출하면 JPA 에서 알아서 슈퍼타입 , 서브타입 두개의 테이블에 INSERT 문을 각각 만든다.

조회 할때에도 jpa.find(ActionMovie.class, pk value) 로 ActionMovie 를 가지고 오고 싶다. JPA 에서 Movie 와 ActionMovie를 조인해서 가지고 온다.

-연관관계 저장
member.setTeam(team);
jpa.persist(member);

-저장한 객체 그래프 탐색
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam();
// 자바 컬렉션 처럼 이용가능

-비교하기
jpa.find()는 동일한 트랜잭션에서 조회한 엔티티는 같음을 보장한다.

JPA의 성능 최적화 기능

  1. 1차 캐시와 동일성 보장
    -> 패러다임의 불일치 해결의 비교하기 부분에서 jpa는 동일한 트랜잭션에서 조회한 엔티티의 같음을 보장한다고 하였다.

String memberId = "100";
Member member1 = jpa.find(Member.class,memberId); //SQL
Member member2 = jpa.find(Member.class,memberId); //캐시 member1 반환

member1은 첫 조회이기 때문에 jpa 에서 SQL 문을 생성하여 조회하지만 동일한 트랜잭션에서 member2 를 조회하면 새로 SQL문을 생성하지 않고 member1을 바로 리턴 해준다.

약간의 조회 성능 향상.

  1. 트랜잭션을 지원하는 쓰기 지연

jpa.persist(memberA);
jpa.persist(memberB);
jpa.persist(memberC);

이 로직이 각자 따로따로 수행되면, DB와 통신하기 위해 네트워크 연결을 했다가 끊었다가 쓸데없는 비용이 발생하고 , 이는 성능 측면에서도 좋지 않은 결과를 초래한다.

이를 개선하기위해 JDBC BATCH 라는 기능을 지원하여 transaction.begin() [트랜잭션의 시작점] 에서 부터 transaction.commit() 하기 전까지의 SQL을 모아서 한번에 보낸다. [네트워크 비용 감소]

하지만 JDBC BATCH 는 코드가 더워진다는 단점이 있다. JPA 에서는 옵션하나로 이러한 기능을 끄고 킬수 있다.

  1. 지연로딩 , 즉시로딩

-지연로딩 : 객체가 실제 사용될 때 로딩

Member member = memberDAO.find(memberId); //SELECT * from Member
Team team = member.getTeam();
String teamName = team.getName(); //SELECT * from Team

지연로딩은 처음에는 Member table의 데이터만 가지고 오고 추후에 Team table 이 필요하다면 그때 쿼리문을 통해 데이터를 받아온다.

-즉시로딩 : JOIN SQL로 한번에 연관된 객체까지 미리 조회

Member member = memberDAO.find(memberId);
//SELECT M. ,T. FROM Member JOIN Team ...

Team team = member.getTeam();
String teamName = team.getName();

정리하자면, 지연로딩은 서브타입을 미리 가지고 오지 않고 , 필요할때 쿼리문을 통해 받아오는 형식이고 즉시로딩은 처음부터 슈퍼타입과 서브타입 둘다 JOIN 하여 데이터를 받아온다.

예제에서 Team table에 대한 데이터가 항상 필요하다면 즉시로딩이 유리할 것이고 , 필요하지 않을때가 많다면 지연로딩이 유리할 것이다.

지연로딩 ,즉시로딩은 JPA 옵션으로 설정 할 수 있다.

profile
지속적인 발전, 태도

0개의 댓글