인프런 이영한님의 강의를 바탕으로 작성된 내용입니다.
객체의 상속관계를 가장 유사하게 표현한 Table 슈퍼타입 서브타입 관계도
객체를 관계형 데이터로 바꾸는 일련의 과정
결과적으로 DB에 저장할 객체에는 상속관계를 안쓴다.
위의 과정을 자바 컬렉션으로 저장한다면?
위의 과정을 자바 컬렉션으로 조회한다면?
객체의 경우 Taem 객체를 사용하여 Member 객체를 참조할 수 없지만 (단방향), 테이블의 경우 MEMBER의 FK를 통해 TEAM이 MEMBER를 확인할 수 있다.(양방향)
양방향을 단방향 객체로 조회할 때 SQL문을 사용하며 복잡한 과정들이 발생하게 된다.
그러나 처음 실행하는 SQL에 따라 탐색 범위가 결정된다.
SELETE M.*, T.*
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
member.getTeam(); // Ok
member.getOrder(); // null 처음에 설정시 ORDER 테이블은 JOIN하지 않았기 때문에
또한 모든 테이블을 연결하여 엔티티를 작성하였다 하더라도 엔티티에 대한 신뢰 문제로 프로그래머가 다 확인해야하는 작업이 필요하다.
가장 중요한 점은 패러다임의 불일치를 해결해준다? 어떤 의미인지 앞서 설명한 객체 와 테이블 간의 다른 점을 말하는 것인지 더 공부해 봐야 할점
성능
- 1차 캐시와 동일성(identity)보장
같은 트랜잭션 안에서는 같은 엔티티를 반환 - 약간의 조회 성능 향상
2. DB Isolation Level이 Read Commit이어도 애플리케이션에서 Repeatable Read 보장 - 아직 이해하기 어려운 개념...
String memberId = "100";
Member m1 = jpa.find(Member.class, memberId); //SQL
Member m2 = jpa.find(Member.class, memberId); //캐시
println(m1 == m2) //true
SQL 1번만 실행
트랜잭션을 커밋할 때까지 INSERT SQL을 모음
JDBC BATCH SQL 기능을 사용해서 한번에 SQL 전송
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 모아서 보낸다.
transaction.commit(); // [트랜잭션] 커밋
데이터 접근 추상화와 벤더 독립성 - 표준
ORM은 객체와 RDB 두 기둥위에 있는 기술 !!!!
JPA는 인터페이스 / Hibernate, Spring Data JPA 모두 JPA를 구현하는 구현체 !!!
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); // Gradle 사용시 persistance.xml 에 사용할 객체 명시 해줘야함 <class>hellojpa.Member</class>
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction(); // transaction 이라는 단위가 중요, jpa의 모든 작업은 transaction안에서 작업이 발생해야한다.
tx.begin();
try {
// table column 생성
/*Member member = new Member();
member.setId(2L);
member.setName("HelloB");*/
// 조회
/*Member findMember = em.find(Member.class, 1L);
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.name = " + findMember.getName());*/
// 수정
Member findMember = em.find(Member.class, 1L); // 생성 후 jpa가 관리 -> 변경이 발생시 update query가 발생
findMember.setName("HelloJPA"); // 데이터를 수정 후 따로 persist를 할 필요가 없다 -> java의 collection 처럼 사용
/*// 저장
em.persist(member);*/
/* // 삭제
em.remove(em.find(Member.class, 2L));*/
// database commit, 문제가 생기면 rollback 해야함
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close(); // entity manager가 내부 데이터베이스 커넥션을 가지고 동작하기 때문에 사용 후 닫아주는 것이 중요하다.
}
emf.close();
}
- EntityManagerFactory는 하나만 생성해서 애플리케이션 전체에서 공유
- EntityManager는 쓰레드간 공유 X (사용하고 반듯이 close)
- JPA의 모든 데이터 변경은 Transaction 안에서 실행
List<Member> result = em.createQuery("select m from Member as m", Member.class) // query 문과 비슷한 문법// 테이블 대상이 아니라 객체 대상의 query
.setFirstResult(1)
.setMaxResults(6) // 페이징이 가능 1번 부터 6번까지 Limit offset
.getResultList();
for (Member member : result) {
System.out.println("member.name = " + member.getName());
}
• JPA를 사용하면 엔티티 객체를 중심으로 개발
• 문제는 검색 쿼리
• 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색 모든
• DB 데이터를 객체로 변환해서 검색하는 것은 불가능
• 애플리케이션이 필요한 데이터만 DB에서 불러오려면 결국 검 색 조건이 포함된 SQL이 필요
SQL을 추상화해서 특정 데이터베이스 SQL에 의존X
JPQL을 한마디로 정의하면 객체 지향 SQL
객체와 관계형 데이터베이스 매핑하기 (ORM Object Relational Mapping)
영속성 컨텍스트
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
EntityManagerFactory emf = Persistance.createEntityManagerFactory(persistanceUnitName);
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername(“회원1”);
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
//객체를 저장한 상태(영속)
em.persist(member);
1차 캐시
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
//1차 캐시에 저장됨
em.persist(member);
//1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");
1차 캐시가 존재하는 경우 DB를 조회하지 않고 1차 캐시를 조회한다.
Member findMember2 = em.find(Member.class, "member2"); // DB에 있다는 가정
1차 캐시에 조회한 id에 해당하는 entity값이 존재하지 않기 때문에 DB를 조회하며 조회 후 1차캐시에 저장되어 반환된다. 그 이후 member2 를 조회시 1차 캐시에서 조회된다.
동일성(identity) 보장
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); //동일성 비교 true
1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터 베이스가 아닌 애플리케이션 차원에서 제공
트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋
변경 감지(Dirty Checking)
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // [트랜잭션] 시작
// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");
// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);
//em.update(member) 이런 코드가 있어야 하지 않을까?
transaction.commit(); // [트랜잭션] 커밋
Java의 collection 과 같이 값을 변경하면 바로 update query를 해준다.
최초의 엔티티 상태를 스냅샷으로 1차 캐시에 저장후 원래의 엔티티는 쓰기지연SQL저장소에 insert query로 저장, 이후 flush() 호출에 의해 변경된 엔티티는 스냅샷과 비교하여 변경되었다면 update query가 쓰기지연SQL저장소에 저장 후 이전 query와 함께 commit된다.
지연 로딩(Lazy Loading)
플러쉬 (Flush()) : 영속성 컨텍스트의 변경내용을 데이터베이스에 반영
영속성 컨텍스트를 비우지 않는다.
영속성 컨텍스트의 변경내용을 데이터베이스에 동기화 한다.
트랜잭션이라는 작업단위가 중요 -> commit 직전에만 동기화 하면된다.
//회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);
em.detach(entity) : 특정 엔티티만 준영속 상태로 전환
em.clear() : 영속성 컨텍스트를 완전히 초기화
em.close() : 영속성 컨텍스트를 종료
//객체를 삭제한 상태(삭제)
em.remove(member);