JPA란?
Java Persistence API의 줄임말이다.
*persist: 계속하다, 끝까지 집착하다.
그리고 자바 진영의 ORM 기술 표준이다.
ORM 이란?
Obeject - relational - mapping(객체 관계 매핑)이다.
뜻은 "객체"랑 "관계형 데이터베이스"랑 매핑을 해준다는 뜻이다.
이걸 쓰게 되면 장점은,
객체를 객체대로 설계
관계형 데이터베이스(RDBMS)는 관계형 데이터베이스대로 설계할 수 있다는 장점이 있다.
R == relatinal이다.
이런 장점은, SQL중심적인 프로그래밍에서 객체설계프로그래밍으로 패러다임을 변환할 수 있다.
따라서 ORM 프레임워크의 역할은 중간에서 "객체와 관계형 데이터베이스의 다른 부분을 매핑을 해주는 역할"을 한다.
JPA 동작
JPA는 애플리케이션과 JDBC 사이에서 동작한다.
JPA는 결론적으로 정말 새로운 기술이 아니라, 매핑을 해주는 역할이다.
JAVA 애플리케이션에서 DB와 통신을 하려면
JDBC API를 사용해야 한다.
원래라면, 그거를 개발자가 직접 JDBC API를 짜야했다면
JPA는 그 일을 대신해 준다고 생각하면 된다.
JPA 동작 - 저장
JPA는 멤버객체를 저장한다고 생각해 보자.
멤버객체(entity)를 멤버 DAO에다가 넘기고
엔티티를 저장해 달라고 JPA에다가 던지기만 하면, JPA가 알아서 회원객체를 분석하여
insert sql 쿼리문을 만들어줍니다.
* 저장된 멤버객체를 entity라고 부른다.
JPA 동작 - 조회
조회할 때는, JPA에게 회원 아이디만 던지면 된다.
EX) 회원 ID 5번을 찾아주세요.
라고 하면 JPA가 회원객체를 분석해서 SELECT Quary를 알아서 만든다.
그다음에 JDBC APU를 사용하고 RESULT SET매핑을 해준다.
그래서, 결론은
- 패러다임의 불일치 해결
- 상속문제점 해결
까지 해결해 준다.
JPA가 entity object를 다 만들어서 우리에게 반환한다.
예전에, 스프링의 역사를 보면
자바표준인 EJB빈이 실용성이 떨어지고 구현할게 많아서 안 쓴다.
(백엔드 개발자의 고민거리가 많은데 해결하려면 많은 시간할애)
따라서
그것에 화난 개발자(Gaving King)가 만든 게 "하이버네이트" 오픈소스입니다.
그래서 결론은 현재 JPA 표준 인터페이스로 하이버네이트를 많이 선호하고 있습니다.
JPA를 왜 사용해야 하는가?
JPA가 sql 중심적인 개발 해서 객체 중심적인 개발로 패러다임을 바꿔주기 때문에 사용해야 합니다.
- 패러다임의 불일치 해결
- 생산성
- 유지보수
- 성능
- 데이터 접근 추상화의 벤더 독립성(공부해야 함)
// JPA의 CRUD
저장: jpa.persist(member)
조회: Member member = jpa.find(memberId)
수정: member.setName(“변경할 이름”)
삭제: jpa.remove(member)
유지보수측면의 JPA
기존의 필드 변경 시 모든 SQL를 수정해야 하는 단점이 있다.
예를 들어, 코드 다 짰는데 전화번호 속성을 까먹었다.
원래라면 DB쿼리문도 다시 짜주어야 하는데 JPA를 사용하면, 속성만 추가하면
SQL쿼리문을 알아서 짜준다.
JPA와 패러다임의 불일치 해결
- JPA와 상속
- JPA와 연관관계
- JPA와 객체 그래프 탐색
를 다 해결해 준다.
JPA는 같은 트랜잭션 안에서 같은 엔티티를 반환
처럼 같은 트랜잭션에서 같은 엔티티를 반환하여
같다는 결과가 나온다.
이는 성능향상과 관련이 있다.
이처럼 중간에 기술이 끼게 되면 생기는 이점이 크게 2가지가 존재한다.
1. 모아서 보내는 게 가능해진다(buffer writing)
2. 조회할 때, 이미 조회한 건 다시 안 하고 원래 있던걸 반환한다(캐시)
=> JPA가 중간에 끼기 때문에 이런 것들을 사용 가능하다, 성능향상과 관련 있다.
1. 모아서 보내기 (buffer writing)
데이터를 버퍼로 모을 수 있다.
1. 트랜잭션을 커밋할 때까지 INSERT SQL을 모음
2. JDBC BATCH SQL 기능을 사용해서 한 번에 SQL 전송
트랜잭션을 지원하는 쓰기 지연 - INSERT
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 모아서 보낸다.
transaction.commit(); // [트랜잭션] 커밋
트랜잭션을 커밋하기 전까지, INSERT문을 모을 수 있다.
모아놨다가(INSERT문 안 날리고 기다림)
그러다가 트랜잭션을 커밋하면 INSERT쿼리문을 한 번에 네트워크로 보내고
그다음에 COMMIT 진행(네트워크 통신 비용이 엄청 줄어든다.)
JDBC에 Batch SQL이라는 기능을 사용해서 한 번에 보내기 가능하다.
트랜잭션을 지원하는 쓰기 지연 - UPDATE
1.. UPDATE, DELETE로 인한 로우(ROW) 락 시간 최소화
2. 트랜잭션 커밋 시 UPDATE, DELETE SQL 실행하고, 바로 커밋
transaction.begin(); // [트랜잭션] 시작
changeMember(memberA);
deleteMember(memberB);
비즈니스_로직_수행(); //비즈니스 로직 수행 동안 DB 로우 락이 걸리지 않는다.
//커밋하는 순간 데이터베이스에 UPDATE, DELETE SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋
지연로딩과 즉시로딩
지연로딩 | 즉시로딩 |
예를 들어 멤버랑 팀이 있다.(근데 거의 같이 안쓴다.) 이럴때는 멤버만 쓸때는 멤버만 조회하는 것이 낫다. |
예를 들어, 멤버랑 팀이 있다.(둘이 연관관계가 있다.) 어떨때는 멤버랑 팀이 거의 같이 불린다. 즉, 로직을 짜보니 항상 둘이 같이 쓰는것을 확인했다. 이렇다면!! 한번에 JOIN을 해서 가져오는게 낫다. (네트워크 통신 overhead(cost)가 줄어든다.) DB도 여러번 갈 필요없고 좋다. |
- 지연 로딩: 객체가 실제 사용될 때 로딩(거의 따로따로 쓰인다면 유리하다)
- 즉시 로딩: Join SQL로 한 번에 연관된 객체까지 미리 조회(멤버랑 팀이 웬만하면 같이 쓰일 때 유리)
그래서 JPA를 짤 때는
지연로딩으로 일단 짜두었다가
성능최적화할 개발단계가 되면, 즉시로딩이 유리한 부분이 있다면 "지연로딩을 즉시로딩으로" 바꾼다.
이런 식으로 성능 최적화가 가능하다.
2. 캐시사용(cache), 동일성 보장
- 같은 트랜잭션 안에서는 같은 엔티티를 반환 - 약간의 조회 성능 향상
- DB Isolation Level이 Read Commit이어도 애플리케이션에서 Repatable Read보장
String memberId = "100";
Member member1 = jpa.find(Member.class, memberId);
Member member2 = jpa.find(Member.class, memberId);
member1 == member2; //같다.
라고 쓰면, 첫 번째는 SQL Quary를 날려서 가지고 온다.
JPA가 중간에 그걸 들고 있다.
두 번째는 JPA가 들고 있는 메모리상에서 그걸 가지고 와서 반환을 해준다.
그래서 "==" 비교하면 같다고 나오고
두 번 조회했지만, SQL를 한 번만 실행되며 실행속도가 올라간다.
(같은 트랜젝션의 조건이라면)
JPA의 성능 최적화 기능(정리)
- 1차 캐시와 동일성(identity) 보장
- 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)
- 지연 로딩(Lazy Loading)
'Back-end > Spring' 카테고리의 다른 글
[JPA] Entity에 JPA로 (생성시간, 수정시간)넣기 - Auditing (0) | 2023.11.26 |
---|---|
[lombok] lombok 어노테이션 정리 (0) | 2023.11.25 |
[spring, JPA] junit test시 hibernate query 노출하는 법 (2) | 2023.11.22 |