객체는 객체대로 설계하고 관계형 DB는 관계형 DB대로 설계한 후, 둘 사이의 차이들은 ORM 프레임워크가 해결해주겠다는 것
EJB - 엔티티 빈(자바 표준)
🔽
하이버네이트(오픈소스)
🔽
JPA(자바 표준)
대부분 8~90% 이상 하이버네이트를 사용한다고 생각하면 됨
jpa.persist(member)
Member member = jpa.find(memberId)
member.setName("변경할 이름")
jpa.remove(member)
기본적인 CRUD 코드가 다 구현되어있다. 그냥 메서드 가져다가 사용하면 된다! (개발자가 쿼리 작성하고 있지 않아도됨)
제일 환상적인 것은 수정부분
엔티티 객체의 속성을 변경하면, JPA가 이를 감지해서 자동으로 UPDATE 쿼리를 생성해서 날린다.
WHY?
생각해보면 JPA라는 것은 마치 자바 컬렉션에 데이터 넣고 빼듯이 RDB에 데이터 넣고 뺄 수 있게 하기위해 나온 것이다.
자바 컬렉션에서 꺼내온 객체를 수정하고, 수정한 객체를 다시 컬렉션에 집어넣지 않듯이 (왜냐면 레퍼런스를 가져와서 수정한 것이기 때문에 원본객체의 속성이 변경됨) JPA를 사용하면 따로 사용자가 UDPATE 쿼리를 날려주지 않아도 해당 변경사항을 자동으로 DB에 반영시켜준다.
이전 게시글에서도 보았듯이 객체에 필드하나만 추가하더라도 모든 쿼리들을 다 수정해주어야한다. (오류 발생확률 다분함)
하지만 JPA를 사용한다면 DB에 새로운 컬럼이 추가되어있다는 가정하에 객체의 필드만 추가하면 된다.
개발자가 쿼리에 손댈필요 없음!!!
왜? JPA가 변경된 객체보고 쿼리 생성해주니까!
JPA는 관계형 DB와 객체의 패러다임 불일치 문제를 해결해준다!
jpa.persist(album);
INSERT INTO ITEM ...
INSERT INTO ALBUM ...
개발자는 부모객체를 상속받는 객체를 JPA를 통해 저장하면, JPA가 알아서 INSERT쿼리를 2개로 나눠서 날린다. 개발자는 DB의 구조에 대해 크게 고민하지 않고 객체를 DB에 저장할 수 있다.
Album album = jpa.find(Album.class, albumId);
SELECT I.*, A.*
FROM ITEM I
JOIN ALBUM A ON I.ITEM_ID = A.ITEM_ID
개발자가 조회하고 싶은 엔티티 클래스 타입과 PK값을 넘기면 JPA에서 알아서 테이블 조인해서 연관된 객체 데이터까지 가져온다.
member.setTeam(team);
jpa.persist(member); //persist : 영구저장하다
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam();
JPA사용하면 엔티티계층을 신뢰하고 객체 그래프를 탐색할 수 있다!!
String memberId = "100";
Member member1 = jpa.find(Member.class, memberId);
Member member2 = jpa.find(Member.class, memberId);
member1 == member2; //같다
JPA는 동일한 트랜잭션에서 조회한 엔티티는 같음을 보장한다!
JPA와 같이 계층 사이에 중간계층을 두면 할 수 있는 것이 2가지가 있다
JPA를 잘 다룬다면 단순하게 SQL 사용하는 것보다 성능 끌어올릴 수 있다.
캐싱전략 종류
write through와 write behind 차이
String memberId = "100";
Member m1 = jpa.find(Member.class, memberId); //SQL
Member m2 = jpa.find(Member.class, memberId); //캐시
println(m1==m2) //true (결과적으로 SQL 한번만 실행)
똑같은 pk로 엔티티조회시, 처음 조회할 때는 SQL을 날리지만 다음부터는 캐싱된 값을 조회해온다.
우리가 일반적으로 생각하는 캐싱과는 다름
하나의 요청이 들어와서 트랜잭션이 시작되고 끝나는 그 사이에만 유지되는 캐싱이기 때문에 사실 그렇게 엄청난 성능상 이득은 없다 😂
버퍼링 기능
transaction.begin(); //트랜잭션 시작
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
//여기까지 INSERT SQL을 DB에 보내지 않는다
//커밋하는 순간 DB에 INSERT SQL을 모아서 보낸다
transaction.commit(); //트랜잭션 커밋
위 코드에서 각각의 요청마다 쿼리를 날리면 네트워크를 3번타야함.
➡ 속도상 느림
하지만 한번에 SQL을 전송할 수 있다면 네트워크를 한번만 타도 됨.
➡ JDBC BATCH 를 사용하면 가능하지만, 코드가 굉장히 더러움
➡ JPA를 사용한다면 옵션하나 켜줌으로써 해결 (spring 사용하면 batch size 옵션 간단히 설정 가능)
Member member = memberDAO.find(memberId); // SELECT * FROM MEMBER 쿼리 날라감
Team team = member.getTeam();
String teamName = team.getName(); // SELECT * FROM TEAM, TEAM객체가 실제로 사용될 때 쿼리 날라감
지연로딩은 연관된 객체 필드를 프록시로 초기화해두고, 해당 객체가 실제로 사용될 때 쿼리 날려서 객체를 가져와서 초기화한다.
Member member = memberDAO.find(memberId); // SELECT M.*, T.* FROM MEMBER JOIN TEAM ... (한번에 연관된 엔티티 객체까지 다 가져옴)
Team team = member.getTeam();
String teamName = team.getName();
ORM은 객체와 RDB 두 기둥위에 있는 기술
객체지향과 RDB 모두 다 잘알아야한다.
더 중요한거 굳이 고르라하면, RDB라고 할 수 있다.
왜냐면, 객체지향적인 언어는 바뀔 수 있음. 하지만 RDB의 데이터는 훨씬 더 오래 살아남음.
따라서 꾸준하게 둘다 공부하고 잘하는 상태에서 사용해야한다!
해당 게시글은 인프런 김영한님의 <자바 ORM 표준 JPA 프로그래밍 - 기본편>을 듣고 정리한 내용입니다.