JPA와 모던 자바 데이터 저장기술
1. 현재 개발 트렌드
• 애플리케이션 : 객체지향 언어 (ex: [Java, Scala, Kotlin...])
• 데이터베이스 : 관계형 데이터 (ex: [Oracle, Mysql, PostgreSQL...])
• 객체를 관계형 DB에 관리하는 것에 시간을 많이씀
• 객체 → 데이터, 데이터 → 객체 : SQL 중심적인 개발이 됨
2. SQL 중심적인 개발의 문제점
2.1 기능추가, 테이블 생성 될때마다 CRUD SQL을 다 만들어야함
(JdbcTemplate, MyBatis가 Mapping에 도움을 주는 것은 있지만 그래도 한계가 있음)
• 회원 객체의 CRUD가 구현되있는 상황
- 기존 회원 객체 테이블 기능 쿼리 구현
/*회원 객체*/
public class Member {
private String memberId;
private String name;
}
/*쿼리*/
INSERT INTO MEMBER(MEMBER_ID, NAME) VALUES ...
SELECT MEMBER_ID, NAME FROM MEMBER M
UPDATE MEMBER SET .
- 전화 번호 필드를 추가해야하는 상황
/*회원 객체*/
public class Member {
private String memberId;
private String name;
private String tel;
}
/*쿼리*/
INSERT INTO MEMBER(MEMBER_ID, NAME, TEL) VALUES ...
SELECT MEMBER_ID, NAME, TEL FROM MEMBER M
UPDATE MEMBER SET ...TEL = ?
※ 모든 CRUD에 TEL을 하나하나 추가해야함
※ SQL에 의존적인 개발
2.2 패러다임의 불일치 (객체 VS RDBMS)
• 관계형 데이터베이스와 객체지향은 사상이 다름
객체지향개발 : 추상화, 캡슐화, 정보은닉, 상속, 다형성등 시스템의 복잡성을 제어하는 다양한 장치들 제공
관계형데이터베이스 : 데이터를 잘 정규화해서 저장하는 것이 목표.
• 객체를 관계형 데이터베이스에 저장하는 도식
※ 객체를 SQL로 변환해서 RDB에 저장하는 변환과정을 개발자가 해야함
3. 객체와 관계형 데이터 베이스의 차이
3.1 상속
• 객체의 상속관계와 유사한 관계형 데이터베이스의 개념으로 Table 슈퍼타입, 서브타입 관계가 있음
• 상속 받은 객체(Album, Movie, Book)을 데이터베이스에 저장하려면 복잡함
- 객체 분해 : Album객체를 Item과 분리
- Item Table에 하나, Album테이블에 하나 두개의 쿼리를 작성해서 저장
• Album 객체를 DB에서 조회하는것도 복잡
- SQL로 ITEM과 ALBUM을 조인해서 데이터를 가져옴
- 조회한 필드를 각각 맞는 객체(ITEM, ALBUM)에 매핑시켜서 가져와야함
결론: DB에 저장할 객체는 상속관계를 쓰지 않음
3.2 연관 관계
• 객체는 참조를 사용 : member.getTeam();
• 테이블은 외래 키를 사용 : JOIN ON M.TEAM_ID = T.TEAM_ID
• Member와 Team간에 참조
- 객체: Member → Team 은 member.getTeam()을 통해 가능, Team → Member 는 참조할 객체가 없기 때문에 불가능
- 테이블: 서로가 서로를 참조할 키(FK)가 있기 때문에 양측이 참조가 가능하다. Member ↔ Team
• 객체를 테이블에 맞춰 모델링, 테이블에 맞춘 객체 저장
/* 회원 객체 */
class Member{
String id; //MEMBER_ID 컬럼 사용
Long teamId; //참조로 연관관계를 맺는다.
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) VALUES...
- 객체 지향적이지 못함
• 객체다운 모델링, 객체 모델링 저장
/* 회원 객체 */
class Member{
String id; // MEMBER_ID 컬럼 사용
Team team; // Team 객체 참조로 연관관계를 맺음
String username; // USERNAME 컬럼 사용
Team getTeam(){
return team;
}
}
/* 팀객체 */
class Team{
Long id; // TEAM_ID 컬럼 사용
String name; // NAME 컬럼 사용
}
/* 쿼리에 teamId를 저장하기 위해 꺼냄*/
member.getTeam().getId();
/*쿼리*/
INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAME) VALUES ...
INSERT INTO TEAM(TEAM_ID, NAME) VALUES...
• 객체 모델링 조회
SELECT M.*, T.*
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
public Member find(String memberId){
//SQL 실행
Member member = new Member();
//데이터터베이스에서 조회한 회원 관련 정보를 모두 입력
Team team = new Team();
//회원과 팀 관계 설정
member.setTeam(team);
return member;
}
• 객체 그래프 탐색
- 객체는 자유롭게 객체 그래프를 탐색할 수 있어야 함
- Member 객체에서 엔티티 그래프를 통해 Category 까지도 접근이 가능해야 함
ex) member.getOrder().getOrderItem().getItem().getCategory;
• 처음 실행하는 SQL에 따라 탐색 범위가 결정
SELECT M.*, T.*
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
member.getTeam(); //OK
member.getOrder(); //null
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 조회
...
※ 기존의 방식으로는 계층형 아키택처, 진정한 의미의 계층분할을 구현 할 수 없음
4. 객체 비교
• 동일한 식별자(memberId)로 조회한 두 객체 비교
String memberId = "100";
Member member1 = memberDAO.getMember(memberId);
Member member2 = memberDAO.getMember(memberId);
member1 == member2; // false 다르다
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 memberId = "100";
Member member1 = list.get(memberId);
Member member2 = list.get(memberId);
member1 == member2; // true 같다.
※ 동일성과 동등성(Identical & Equality)
- 자바에서 두 개의 오브젝트 혹은 값이 같은가
: 동일한 오브젝트라는 말(identical)은 같은 레퍼런스를 바라보고 있는 객체라는 말로 실제론 하나의 오브젝트라는 의미
: 동등한 오브젝트라는 말(Equivalent)은 같은 내용을 담고 있는 객체라는 의미
※ 결론
객체지향적으로 모델링을 할 수록 매핑작업만 늘어나고 불일치가 늘어나서 사이드이펙트가 커지기만함.
객체를 자바 컬렉션에 저장하듯이 DB에 저장하기위해 나온 것이 JPA
JPA (Java Persistence API)
1. 용어
• JPA : 자바진영의 ORM 기술 표준
• ORM
- Object-relational mapping(객체 관계 매핑)
- 객체는 객체대로 설계 하고, RDBMS는 RDBMS대로 설계해서 ORM 프레임워크가 중간에서 매핑 해줌
- 대중적인 언어에는 대부분 ORM 기술이 존재
2. JPA 동작 원리
2.1 애플리케이션과 JDBC 사이에서 동작함
2.2 저장
2.3 조회
3. JPA를 사용하는 이유
3.1 생산성
• 저장: jpa.persist(member)
• 조회: Member member = jpa.find(memberId)
• 수정: member.setName(“변경할 이름”)
• 삭제: jpa.remove(member)
※ CRUD가 간편함
3.2 유지보수
/*회원 객체*/
public class Member {
private String memberId;
private String name;
private String tel;
}
/*쿼리*/
INSERT INTO MEMBER(MEMBER_ID, NAME, TEL) VALUES ...
SELECT MEMBER_ID, NAME, TEL FROM MEMBER M
UPDATE MEMBER SET ...TEL = ?
※ 기존에는 필드 변경시 모든 SQL을 수정했어야했으나, JPA는 필드만 추가하면 되고 SQL은 JPA가 수행함
3.3 패러다임 불일치 해결
• 상속
- 상속되어 있는 객체의 저장
: 개발자의 할일 jpa.persist(album);
: JPA가 나머지 처리
- 상속되어 있는 객체의 조회
: 개발자가 할일 Album album = jpa.find(Album.class, albumId);
: JPA가 나머지 처리
• 연관관계
- 연관관계 저장
member.setTeam(team);
jpa.persist(member);
• 객체 그래프 탐색
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; //같다
※ 동일한 트랜잭션에서 조회한 엔티티는 같음을 보장
3.4 성능 최적화 기능
• 1차 캐시와 동일성 보장
String memberId = "100";
Member m1 = jpa.find(Member.class, memberId); //SQL
Member m2 = jpa.find(Member.class, memberId); //캐시
println(m1 == m2) //true
- 같은 트렌젝션 안에는 같은 엔티티를 반환(조회성능 향상)
- DB Isolation Level이 Read Committed이어도 애플리케이션에서 Repeatable Read 보장
- SQL을 한번만 수행함
※ DB Isolation Level(DB 격리 수준)
<아래로 내려갈수록 트랜잭션간 고립 정도가 높아지고 성능이 떨어짐>
- READ UNCOMMITTED : 어떤 트랜잭션의 변경내용이 COMMIT이나 ROLLBACK과 상관없이 다른 트랜잭션에서 보여짐
- READ COMMITTED : 어떤 트랜잭션의 변경 내용이 COMMIT 되어야만 다른 트랜잭션에서 조회할 수 있음
- REPEATABLE READ : 트랜잭션이 시작되기 전에 커밋된 내용에 대해서만 조회할 수 있는 격리수준
- SERIALIZABLE : 읽기 작업에도 공유 잠금 설정, 동시에 다른 트랜잭션에서 이 레코드를 변경하지 못하게 됨
• 쓰기 지연
- insert
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 모아서 보낸다.
transaction.commit(); // [트랜잭션] 커밋
※ 트랜잭션을 커밋할 때까지 INSERT SQL을 모으고 JDBC BATCH SQL 기능을 사용해서 한번에 SQL 전송
- update
transaction.begin(); // [트랜잭션] 시작
changeMember(memberA);
deleteMember(memberB);
비즈니스_로직_수행(); //비즈니스 로직 수행 동안 DB 로우 락이 걸리지 않는다.
//커밋하는 순간 데이터베이스에 UPDATE, DELETE SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋
※ UPDATE, DELETE로 인한 로우(ROW)락 시간 최소화
※ 트랜잭션 커밋 시 UPDATE, DELETE SQL 실행하고, 바로 커밋
• 지연 로딩
- 지연 로딩: 객체가 실제 사용될 때 로딩
- 즉시 로딩: JOIN SQL로 한번에 연관된 객체까지 미리 조회
'일상의 흔적 > Study' 카테고리의 다른 글
면접 Java 질문 - 1 (0) | 2023.03.22 |
---|---|
자바 ORM표준 JPA 프로그래밍 (기본편) : STS로 세팅 (0) | 2023.03.22 |
생활코딩 OAuth2.0 (0) | 2023.03.21 |
인프런 스프링 MVC 2 (백엔드 웹개발 활용 기술) : 로그인처리2 필터, 인터셉터 - 7 (0) | 2023.03.21 |
인프런 스프링 MVC 2 (백엔드 웹개발 활용 기술) : 로그인처리1 쿠키, 세션 - 6 (0) | 2023.03.20 |