728x90
SQL 중심적인 개발의 문제점
1. SQL에 의존적인 개발
- 기능을 추가해서 테이블이 생성될 때마다 CRUD SQL을 다 만들어줘야 한다.
- Jbdb Template, MyBatis가 Mapping에 도움을 주긴 하나 한계가 있다.
2. 패러다임의 불일치 - 객체 vs 관계형 데이터 베이스
- 관계형 데이터베이스와 객체지향은 서로 가지고 있는 사상이 다르다.
- 객체지향 개발 : 추상화, 캡슐화, 정보은닉, 상속, 다성형 등 시스템의 복잡성을 제어하는 다양한 장치들을 제공한다.
- 관계형 데이터베이스 : 데이터를 잘 정규화해서 저장하는 것이 목표다.
🔍 객체를 영구 보관하는 다양한 저장소
- Object
- RDB
- 현재 가장 많이 쓰인다.
- NoSQL
- 대안이 될 수 있지만 아직 메인이 되기엔 부족하다.
- File
- 객체 100만건을 Serialize 저장하고, 또 반대로 100만건을 Deserialize해서 Object화 한 다음 검색하는 건 불가능하다.
- OODB
- 사망
- RDB
🔍 객체를 관계형 데이터 베이스에 저장하는 과정
- 개발자들이 SQL 매퍼가 되어갈 수 있다.
3. 객체와 관계형 데이터베이스의 차이
- 상속
- 연관관계
- 데이터 타입
- 데이터 식별 방법
🔍 상속
- 객체의 상속관계와 유사한 관계형 데이터베이스의 개념으로 Table 슈퍼타입, 서브타입 관계가 있다.
- 객체 상속 관계는 그저 extends나 implements로 상속 관계를 맺고 캐스팅도 자유롭다.
- 하지만 상속 받은 객체(Album, Movie, Book)를 데이터 베이스로 저장하려면 어떻게 해야할까?
- 1. 객체 분해 : Album 객체를 Item과 분리한다.
- 2. Item Table에 하나, Album 테이블에 하나. 두 개의 쿼리를 작성해서 저장한다.
- Album 객체를 DB에 조회하는 방법
- ITEM과 ALBUM을 조인해서 가져온 다음 조회한 필드를 각각 맞는 객체(Item, Album)에 매핑시켜서 가져와야 한다.
- 결국 DB에 저장할 객체는 상속관계를 쓰지 않는다.
- 관계형 데이터 베이스에는 상속 개념이 없다.
객체지향 개념인 자바 컬렉션을 이용하면 저장과 조회를 쉽게 할 수 있다.
- 저장하는 방법
- list.add(album);
- 조회하는 방법
- Album album = list.get(albumId);
- Item item = list.get(albumId);
🔍 연관관계
- 객체는 참조를 사용한다.
- member.getTeam()
- 테이블은 외래 키를 사용한다.
- JOIN ON M.TEAM_ID = T.TEAM_ID
객체는 Member → Team으로 참조할 수 있다. 하지만 Team → Member는 Team 객체에 Member를 참조할 객체가 없기에 불가능하다.
테이블은 서로가 서로를 참조할 키(FK)가 있기 때문에 서로가 서로를 참조 가능하다. (양방향)
4. 객체를 테이블에 맞추어 모델링하기
예시1
class Member {
String id; // MEMBER_ID 칼럼 사용
Long teamId; // TEAM_ID FK 컬럼 사용
String username; // USERNAME 칼럼 사용
}
class Team {
Long id; // TEAM_ID 칼럼 사용
String name; // NAME 칼럼 사용
}
/* 쿼리 */
INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAME) VALUES ...
INSERT INTO TEAM(TEAM_ID, NAME) VALUSE ...
- 예시1 코드는 객체지향적이지 못하다.
- 왜냐하면 Member 클래스에서 Team 클래스를 참조하는데 Long 타입으로 참조하는 것보다 Team 클래스를 참조하는 것이 객체지향적이기 때문이다.
예시2
class Member {
String id; // MEMBER_ID 칼럼 사용
Team team; // 참조로 연관관계를 맺는다.
String username; // USERNAME 칼럼 사용
}
class Team {
Long id; // TEAM_ID 칼럼 사용
String name; // NAME 칼럼 사용
}
/* 쿼리 */
INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAME) VALUES ...
INSERT INTO TEAM(TEAM_ID, NAME) VALUSE ...
- 예시2는 예시1을 조금 더 객체지향 설계로 바꿨다.
- Member클래스에서 참조되는 Team 객체의 id를 가져와서 저장해야 한다.
- member.getTeam().getId();
예시2의 조회 방법
public Member find (String memberId) {
// SQL 실행
Member member = new Member();
// 데이터베이스에서 조회한 회원 관련 정보 모두 입력
Team team = new Team();
// 회원과 팀 관계 설정
member.setTeam(team);
return member;
}
/* 쿼리 */
SELECT M.*, T.*
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
🔍 객체 그래프 탐색
- 객체는 자유롭게 객체 그래프를 탐색할 수 있어야 한다.
- 예) member.getXXX()
- Member 객체에서 엔티티 그래프를 통해 Category 객체까지도 접근이 가능해야 한다.
🔍 객체지향 모델링의 단점
1. 처음 실행하는 SQL에 따라 탐색 범위가 결정된다.
/* 쿼리 */
SELECT M.*, T.*
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
member.getTeam(); // ok
member.getOrder(); // null
- 처음 SQL에 MEMBER와 TEAM을 조회했기 때문에 getTeam()으로 값을 가져올 수 있다.
- 하지만 getOrder()로 값을 가져오면 null 값이 반환된다.
- 이처럼 처음 SQL 구문에 따라 탐색 범위가 결정된다.
2. 엔티티 신뢰문제
class MemberService {
public void process() {
Member member = memberDAO.find(memberId);
member.getTeam(); // ???
member.getOrder().getDelivery(); // ???
}
}
- 자유롭게 member안의 모든 참조를 자유롭게 참조할 수 있을까?
- 다른 개발자가 새로운 필드를 추가했는데 조회 로직에서 해당부분 매핑을 빼놨다면?
- 실제로 모든 코드와 쿼리를 분석하기전까지는 엔티티 그래프 검색이 어디까지 되는지 확신할 수 없다.
그렇다고 모든 객체를 미리 로딩할 수 없다.
memberDAO.getMember(); // Member만 조회
memberDAO.getMemberWithTeam(); // Member와 Team 조회
memberDAO.getMemberWithOrderWithDelivery(); // Member, Order, Delivery 조회
- 모든 객체를 미리 로딩하면 상황에 따라 동일한 회원 조회 메서드를 여러번 생성하게 된다.
결론은 진정한 의미의 계층 분할이 어렵고 관계형 데이터 베이스를 객체지향적으로 모델링할수록 난이도가 높아진다.
🔍 동일한 식별자로 조회한 두 객체 비교
String memberId = "100";
Member member1 = memberDAO.getMember(memberId);
Member member2 = memberDAO.getMember(memberId);
member1 == member2 // false
// member1과 member2는 서로 다르다.
class MemberDAO {
public Member getMember(String memberId) {
String sql = "SELECT * FROM MEMBER WHERE MEMBER_ID = ?";
...
// JDBC API, SQL 실행
return new Member(...);
}
}
- member를 조회할 때 new Member()를 통해 새로운 객체를 만들어서 조회하기 때문에 두 인스턴스 비교는 내용물이 같더라도 다르다.
반면 자바 컬렉션에서 조회한 객체는 서로 같다.
String member = "100";
Member member1 = list.get(memberId);
Member member2 = list.get(memberId);
member1 == member2 // true
결론적으로 객체지향적으로 모델링할 수록 매핑작업만 늘어나고 불일치가 늘어나서 사이드이펙트가 커지기만 한다.
그렇다면 객체를 자바 컬렉션에 저장하듯이 DB에 저장할 수 없을까? 하는 고민에서 나온 것이 JPA다.
JPA 소개
1. JPA란?
- Java Persistence API
- 자바 진영의 ORM 기술 표준
- JPA는 인터페이스의 모음이다.
- JPA를 표준 명세를 구현한 3가지 구현체가 있다.
- 하이버네이트
- EclipseLink
- DataNucleus
- 주로 하이버 네이트를 쓴다.
- JPA를 사용하는 것은 마치 자바 컬렉션을 통해 데이터를 다루는 것과 비슷하다.
- JPA는 특정 데이터베이스에 종속적이지 않다.
2. ORM이란?
- Object-relation mapping (객체 관계 매핑)
- 객체는 객체대로 설계
- 관계형 데이터베이스는 관계형 데이터베이스대로 설계
- ORGM 프레임워크가 중간에서 매핑 역할을 담당한다.
- 대중적인 언어에는 대부분 ORM 기술이 존재한다.
- ORM은 객체와 RDB 두 기둥 위에 있는 기술
3. JPA 동작 원리
- JPA는 애플리케이션과 JDBC 사이에서 동작한다.
🔍 JPA 저장 과정
- 저장 동작 순서
- JPA에 객체(MemberDAO)를 넘기면 JPA가 분석을 시작한다.
- 분석을 완료하면 JPA가 INSERT SQL 쿼리문을 생성한다.
- 그 후 JPA가 JDBC API를 사용해서 INSERT 쿼리를 DB에 보낸다.
- 중요한 점은 JPA와 관계형 데이터베이스의 패러다임 불일치를 해결했다는 점이다.
🔍 JPA 조회 과정
- 조회 동작 순서
- JPA에 PK 값을 넘기면 JPA가 SELECT SQL 쿼리를 생성한다.
- JPA가 JDBC API를 사용해서 SELECT 쿼리를 DB에 보내고 결과를 반환 받는다.
- DB에서 반환된 결과를 ResultSet 객체를 사용하여 정보를 매핑한다.
- ResultSet은 SELECT문의 결과를 저장하는 객체다.
- 여기서도 객체지향과 관계형데이터베이스의 패러다임 불일치를 해결시켰다는 점이 중요하다.
4. 왜 JPA를 사용하는가?
- SQL 중심적인 개발에서 객체중심 개발로 할 수 있다.
- 생산성이 높아진다.
- 유지보수가 좋다.
- 패러다임의 불일치를 해결해준다.
- 성능이 좋다.
- 데이터 접근 추상화와 벤더 독립성
- 표준
🔍 생산성 ↑
jpa.persist(member) // 저장
Member member = jpa.find(memberId) // 조회
member.setName("변경할_이름") // 수정
jpa.remove(member) // 삭제
- JPA가 인터페이스로 이미 코드를 다 구현해놨다.
- 개발자는 그저 사용만 하면 된다.
🔍 유지보수
기존 방식
- 기존에는 필드 변경시 모든 SQL을 수정해야 한다.
JPA 사용
- 필드가 변경되어도 SQL 쿼리문은 JPA가 작성해주기 때문에 개발자가 SQL을 작성하지 않아도 된다.
🔍 패러다임 불일치 해결
- JPA와 상속
- JPA와 연관관계
- JPA와 객체 그래프 탐색
- JPA와 비교
JPA와 상속
- jpa는 상속 객체를 자동으로 처리해준다. 개발자가 코드만 작성하면 JPA가 SQL 쿼리를 자동으로 생성해준다.
- 저장하기
/* 개발자 코드 */
jpa.persist(album);
/* JPA 처리 */
INSERT INTO ITEM ...
INSERT INTO ALBUM ...
- 조회하기
/* 개발자 코드 */
Album album = jpa.find(Album.class, albumId);
/* JPA 처리 */
SELECT I.*, A.*
FROM ITEM I
JOIN ALBUM A ON I.ITEM_ID = A.ITEM_ID
- 연관관계
member.setTeam(team);
jpa.persist(member);
- 객체 그래프 탐색
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam();
🔍신뢰할 수 있는 엔티티, 계층
class MemberService {
public void process() {
Member member = memberDAO.find(memberId);
member.getTeam(); // 자유로운 객체 그래프 탐색
member.getOrder().getDelivery();
}
}
- JPA를 통해 가져온 객체는 믿고 사용할 수 있다.
🔍 JPA의 동일한 식별자로 조회한 두 객체 비교
String memberId = "100"
Member member1 = jpa.find(Member.class, memberId);
Member member2 = jpa.find(Member.class, memberId);
member1 == member2 // true
- 동일한 트랜잭션에서 조회한 엔티티는 같음을 보장한다.
🔍 JPA의 성능 최적화 기능
- 1차 캐시와 동일성(identity)을 보장한다.
- 트랜잭션을 지원하는 쓰기 지연(transactional write-behind) 기능을 제공한다.
- 지연 로딩(Lazy Loading)을 지원한다.
1차 캐시와 동일성 보장
String memberId = "100";
Member m1 = jpa.find(Member.class, memberId); // SQL
Member m2 = jpa.find(Member.class, memberId); // 캐시
m1 == m2 // true. 동일한 객체 조회시 여번 조회해도 sql 구문 1번만 실행
- 같은 트랜잭션 안에서는 같은 엔티티를 반환한다. - 약간의 조회 성능 향상
- 같은 객체를 반환 결과가 있으면 여러번 조회해도 SQL 구문이 1번만 실행된다.
- DB Isoldation Level이 Read Commit이어도 애플리케이션에서 Repeatable Read를 보장한다. (중요하지 않다.)
트랜잭션을 지원하는 쓰기 지원 - INSERT (버퍼링 기능)
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
// 여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
// 커밋하는 순간 데이터베이스에 INSERT SQL을 모아서 보낸다.
transaction.commit(); // [트랜잭션] 커밋
- 트랜잭션을 커밋할 때 까지 INSERT SQL 구문을 모은다.
- JPA는 JDBC BATCH SQL 기능을 사용해서 한번에 SQL 구문을 DB에 전송한다.
트랜잭션을 지원하는 쓰기 지원 - UPDATE (버퍼링 기능)
transaction.begin(); // [트랜잭션] 시작
changeMember(memberA);
delete(memberB);
비즈니스_로직_수행(); // 비즈니스 로직 수행 동안 DB 로우 락이 걸리지 않는다.
// 커밋하는 순간 데이터베이스에 UPDATE, DELETE SQL을 모아서 보낸다.
transaction.commit(); // [트랜잭션] 커밋
- UPDATE, DELETE로 인한 로우(ROW)락 시간 최소화
- 트랜잭션 커밋시 UPDATE, DELETE SQL 실행하고 바로 커밋한다.
지연 로딩과 즉시로딩
- 지연 로딩 : 객체가 실제 사용될 로딩
- 즉시 로딩 : JOIN SQL로 한번에 연관된 객체까지 미리 조회
- JPA는 지연 로딩과 즉시 로딩을 쉽게 바꿀 수 있다.
👀 참고 자료
https://www.inflearn.com/course/ORM-JPA-Basic/dashboard
https://catsbi.oopy.io/0a48ff61-ee46-4909-b5b4-d5f63937195f
728x90
'[JPA] > JPA 프로그래밍 - 기본편' 카테고리의 다른 글
[JPA] 다양한 연관관계 매핑 (0) | 2022.03.29 |
---|---|
[JPA] 연관관계 매핑 기초 (0) | 2022.03.28 |
[JPA] 엔티티 매핑 (0) | 2022.03.27 |
[JPA] 영속성 관리 - 내부 동작 방식 (0) | 2022.03.26 |
[JPA] JPA 시작하기 (0) | 2022.03.26 |