JPA와 JPA의 장단점

·2021년 4월 23일
6

1. JPA의 필요성

우선 JPA가 왜 필요할까?
1. 현재는 SQL 중심적인 개발을 하고 있기 때문이다.
2. JAVA는 객체지향이고 데이터베이스는 보통 RDB(관계형 데이터베이스) 를 쓰기 때문이다.

SQL 중심적인 개발

CRUD의 무한반복이다.
자바객체를 SQL로 만들고 SQL을 자바 객체로 만들고..
테이블 하나를 생성해도 CRUD를 다 만들어야 하고
컬럼하나를 추가하려고 해도 관련 SQL을 전부 수정해야한다. 그렇기 때문에 실수할 가능성도 높아진다.

객체와 관계형 데이터 베이스의 차이

관계형 DB랑 객체는 패러다임이 너무 다르다.
다른 둘을 합치려고 하니..
개발자는 SQL매퍼의 역할을 할뿐 ㅎㅎ..

1. 상속

  • 객체에는 상속관계가 있다.

  • 관계형 DB는 상속관계가 없다.
    (있긴한데 객체랑 다른개념)

  • 아이템을 상속받는 ALBUM, MOVIE, BOOK 이 있다.

  1. 앨범을 추가하려면?
    • 객체가 두개.. 객체를 분리한다
    • ITEM INSERT 쿼리와 ALBUM INSERT 두 쿼리를 만들어야한다.
  2. 앨범을 조회하려면?
    • ITEM과 ALBUM을 조인한 쿼리를 작성한다.
    • ITEM과 ALBUM 객체를 생성한다.
    • 데이터에 맞게 아이템과 앨범에 각각 넣는다

너무 번거롭고 귀찮으니까 db에 저장할 객체에는 상속관계를 안쓴다

  • Collection이라고 생각해보자..
  1. 추가를 하려면?
    • list.add(album)
  2. 조회를 하려면?
    Album album = list.get(albumId)
    혹은 다형성을 활용해
    Item item = list.get(albumId)
    쉽다쉬워!!

2. 연관관계

  • 객체는 참조를 사용한다 member.getTeam()
    * 객체는 Member에서 Team으로 갈 수가 없다. (Tema -> Member만 가능하다.

  • 테이블은 외래키를 사용한다. JOIN ON M.TEAM_ID = T.TEAM_ID
    * 테이블은 외래키를 통해 Team으로 갈 수 있다. (Team, Member를 Join 해서 찾는다)

  • 객체지향적으로 객체를 만든다면

class Member {
 	String id; //MEMBER_ID 컬럼 사용
 	Team team; //참조로 연관관계를 맺는다. //**
	 String username;//USERNAME 컬럼 사용
 
 	Team getTeam() {
 
		return team;
 	}
}
class Team {
 	Long id; //TEAM_ID PK 사용
 	String name; //NAME 컬럼 사용
}
  1. 이걸 저장하려고 한다면?
    INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAME) VALUES... 에서 TEAM_ID
    member.getTeam().getId() 로 값을 가져와 Team_ID를 저장한다

  2. 이걸 조회하려고 한다면?
    데이터를 전부 가져온 후 Member에서 set하고 Team도 set하고 Team을 Member에 set해야한다

public Member find(String memberId) {
 
	//SQL 실행 ...
 	Member member = new Member();
 
	//데이터베이스에서 조회한 회원 관련 정보를 모두 입력
 	Team team = new Team();
 
	//데이터베이스에서 조회한 팀 관련 정보를 모두 입력
 
	//회원과 팀 관계 설정
 	member.setTeam(team); //**
 
	return member;
}

역시나 번거롭다

  • Collection이라고 생각해보자..
    list.add(member)
    Member member = list.get(memberId)
    Team team = member.getTeam()

손쉽게 member와 team을 가져올 수 있다

3. 데이터 타입

  • 객체는 자유롭게 객체그래프를 탐색할 수 있어야한다.
    하지만 처음 실행하는 SQL에 따라 탐색의 범위가 결정나게 되버린다

    이런 그래프가 있다고 가정하면
SELECT M.*, T.*
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID 
...
member.getTeam(); //OK
member.getOrder(); //null

처음에 SQL에서 TEAM과 MEMBER만 가져왔기 때문에 member와 연결되있는 order여도 값을 가져올 수가 없다.
그래서 엔티티의 신뢰에 문제가 발생한다.

class MemberService {
 
	public void process() {
 		Member member = memberDAO.find(memberId);
 		member.getTeam(); //???
 		member.getOrder().getDelivery(); // ???
 	}
}

위에서 DAO를 들어가보지 않는 이상 getTeam과 getDelivery를 가져올 수 있는지 확인 할 수 없다.

그렇기 때문에 계층형 아키텍처에서 진정한 의미의 계층분할이 어렵게 된다

4. 데이터 식별 방법

  • 같은 MemberId를 조회하지만 비교를 하면 다르다고 뜬다! (다른 객체니까..)
String memberId = "100";
...
Member member1 = memberDAO.getMember(memberId);
Member member2 = memberDAO.getMember(memberId);
member1 == member2; //다르다!!

class MemberDAO {
  
	public Member getMember(String memberId) {
 		String sql = "SELECT * FROM MEMBER WHERE MEMBER_ID = ?";
 		...
 
		//JDBC API, SQL 실행
 
		return new Member(...);
	 }
}
  • Collection이라고 생각해보자
String memberId = "100";
...
Member member1 = list.get(memberId);
Member member2 = list.get(memberId);
member1 == member2; //같다!

결론은! 객체지향언어, RDB 너무너무 안맞아
객체지향적으로 모델링할수록 일만 더 번거로워져..
Collection에 저장하듯이 DB를 사용할 순 없는걸까? => JPA의 등장!

2. JPA의 등장

JPA란 Java Persistence API로 자바진영의 ORM 기술 표준이다

ORM이란?

  • Object relational mapping : 객체관계 매핑
  • 객체는 객체대로 설계하고 RDB는 RDB대로 설계할수 있도록 도와주는 것이다.
  • ORM 프레임워크가 중간에서 매핑을 해주는 것이다.

JPA의 동작방식

  • JPA는 애플리케이션과 JDBC 사이에서 동작한다

  • JPA로 명령하면 JDBC API를 이용해서 호출하는 것이다.

  • 쿼리를 개발자가 안만들고 JPA가 대신만들어서 호출해준다.

    • 패러다임의 불일치 해소
    • jdbc api를 사용하는건 똑같다. 다만 sql문을 개발자가 만드느냐 안만드냐의 차이

    저장

  • MemberDAO에서 객체를 저장할 때

    • 개발자는 JPA에 Member 객체를 넘김
    • JPA는
      Member Entity 분석
      InsertQuery 생성
      JDBC API를 사용해 SQL을 DB에 날린다

    조회

  • Member 객체를 조회하고 싶을 때

    • 개발자는 member의 pk값인 id를 JPA에 넘김
    • JPA는
      엔티티의 매핑정보를 바탕으로 쿼리문 생성
      JDBC API를 사용해 sql을 db에 날림
      db로부터 결과를 받아와 결과를 객체에 매핑

JPA와 Hibernate

  • 옛날 옛적에는 EJB(Enterprise JavaBeans)라고 있었다. 나름 개발을 돕기위해 나타난 친구고 자바 표준이었다.
    • 하지만 너무 복잡하고 불편했다
  • EJB를 사용하던 개발자 개빈 킹(Gavin King)이라는 사람이 빡쳐서 Hibernate를 만들게 되었다.
  • JAVA에서 개빈 킹과 함께 JAVA표준을 만들게 되었는데 그게 JPA다

여담이지만 EJB가 복잡하고 어려워서 EJB쓰지말자 그냥 순수하게 자바를 이용하자라는 여론이 나오면서 Expert One-on-One: J2EE Development without EJB 라는 책이 나오게 되었다고한다. 그리고 이 책을 기반으로 스프링프레임워크가 탄생하게 되었다고 한다

JPA는 JAVA 표준이다

  • JPA는 인터페이스의 모음이다
  • JPA 2.1 기준 표준 명세를 구현한 3가지 구현체가 있는데
    Hibernate, EclipseLink, DataNucleus 가 있다.
    (보통은 Hibernate 를 사용한다)

JPA를 왜 사용해아 하는가

SQL 중심적인 개발에서 탈출해 객체 중심개발이 가능해진다!

1 생산성, 유지보수

  • CRUD가 굉장히 편해진다
저장: jpa.persist(member)
조회: Member member = jpa.find(memberId)
수정: member.setName(“변경할 이름”)
삭제: jpa.remove(member)
  • 이제 기획자가 뒤늦게 필드추가를 요청해도 두렵지 않다
    • SQL은 JPA가 만들어주니 필드만 추가하면 된다.

2 패러다임의 불일치 해결

이런 구조가 있다고 생각해보자

1. 상속

  1. 저장을 하려면?
  • 이제 Item Album을 각각 Insert 할 필요가 없어진다
  • jpa.persist(album) 을 하면 알아서 부모클래스에도 저장이된다
  1. 조회를 하려면?
  • 이제 JOIN문을 쓸 필요가 없다
  • Album album = jpa.find(Album.class, albumId) 이런식으로 앨범을 조회하면 알아서 조인해서 가져온다.

2. 연관관계와 객체그래프 탐색

  • 연관관계 저장
member.setTeam(team);
jpa.persist(member);
  • 객체 그래프 탐색
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam();

3.신뢰할 수 있는 엔티티 계층

  • 객체그래프탐색이 자유롭기 때문에 엔티티를 신뢰할 수 있다
class MemberService {
	 ...
 
	public void process() {
 		Member member = memberDAO.find(memberId);
 		member.getTeam(); //자유로운 객체 그래프 탐색
 		member.getOrder().getDelivery();
 	}
}

위의 memberDAO.find() 처럼 이 값이 잘 나오는지 확인할 필요가 없다.

  • 또한 동일한 트랜잭션 내에서 조회한 엔티티는 같음을 보장한다
String memberId = "100";
Member member1 = jpa.find(Member.class, memberId);
Member member2 = jpa.find(Member.class, memberId);
member1 == member2; //같다.

3 성능

1. 1차캐시와 동일성 보장

  • 같은 트랜잭션 안에서는 같은 엔티티를 반환한다
String memberId = "100";
Member m1 = jpa.find(Member.class, memberId); //SQL
Member m2 = jpa.find(Member.class, memberId); //캐시
println(m1 == m2) //true

이때 SQL은 한번만 실행된다.
성능 향상이 있긴한데 그렇게 획기적으로 향상되는건 아니라고

2. 쓰기지연

  • 트랜젝션을 커밋할때까지 INSERT SQL을 모은담에 한번에 전송시킨다
  • JDBC BATCH SQL 기능으로 한번에 전송하는 것이다
  • 실제로 구현하려면 코드가 엄청 지저문해지는데 JPA는 옵션설정 하나면 끝~
  • 또 한번에 보내기 때문에 Row lock 시간이 최소화된다
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 모아서 보낸다.
transaction.commit(); // [트랜잭션] 커밋

3. 지연로딩과 즉시로딩

  • 지연로딩 : 객체가 실제 사용될 때 로딩한다
  Member member = memberDAO.find(memberId); //select문 1번
  Team team = member.getTeam();
  String teamName = team.getName(); //select 문 2번

가끔씩 Member와 Team을 같이 호출하는거라면 상관없겠지만
Member와 Team이 한쌍이라 늘 같이 호출되는거라면 2번 db를 조회하는게 비효율적이다. 그럴 때 필요한 것이 즉시로딩

  • 즉시로딩 : JOIN SQL로 한번에 연관된 객체까지 미리 조회한다.
Member member = memberDAO.find(memberId); //이때 join으로 team member 같이 호출
 Team team = member.getTeam();
 String teamName = team.getName();

보통 지연로딩으로 개발을 한 후 최적화가 필요할 때 즉시로딩으로바꾼다

JPA는 JAVA와 RDB 사이의 다른 패러다임을 해소시켜주기 위해 객체지향적으로 DB를 가져올 수 있도록 도와주는 인터페이스다
마치 Collecrion처럼 편하게 CRUD를 할 수 있으며 성능면에서도 우수하기때문에 JPA를 잘 공부해두자 📝

출처
T 아카데미 강의

profile
💻📝🤯

0개의 댓글