[Java]ORM

allnight5·2023년 1월 31일
0

JPA

목록 보기
4/5

1. DB 탄생 후 Java 앱과 연동을 위해 JDBC 이 탄생했다.

JDBC

  • Java Database Connectivity
    • 문장 그대로 Java 앱과 DB 를 연결시켜주기 위해 만들어진 기술이다.
    • 그렇기 때문에 JPA 도 이 기술을 사용하여 구현되어 있다.
  • JDBC Driver Manager 는 런타임 시점에
    • Connection(연결) 을 생성하여 쿼리를 요청할 수 있는 상태를 만들어주고
    • Statement(상태) 를 생성하여 쿼리를 요청하게 해주고
    • ResultSet(결과셋) 을 생성해 쿼리 결과를 받아올 수 있게 해줍니다.
      - 꼭 사용후에는 각각 close() 를 호출해서 자원 해제를 시켜줘야 합니다!

2. JDBC 의 여러 문제로 QueryMapper 이 탄생했다.

  • JDBC 로 직접 SQL을 작성했을때의 문제

    • SQL 쿼리 요청시 중복 코드 발생
    • DB별 예외에 대한 구분 없이 Checked Exception (SQL Exception) 처리
    • Connection, Statement 등.. 자원 관리를 따로 해줘야함
      • 안해주면 메모리 꽉차서 서버가 죽음
  • 이 문제 해결을 위해 처음으로 Persistence Framework 등장!

    • Persistence Framework 는 2가지가 있다.
      • SQL Mapper : JDBC Template, MyBatis 👈 요게 먼저나옴
      • ORM : JPA, Hibernate
  • SQL Mapper (QueryMapper)

    • SQL ↔ Object
    • SQL 문과 객체(Object)의 필드를 매핑하여 데이터를 객채화

JDBC Template

  • SQL Mapper 첫번째 주자로 JDBCTemplate 탄생
    • 쿼리 수행 결과와 객채 필드 매핑
    • RowMapper 로 응답필드 매핑코드 재사용
    • Connection, Statement, ResultSet 반복적 처리 대신 해줌
      - But, 결과값을 객체 인스턴스에 매핑하는데 여전히 많은 코드가 필요함

MyBatis

  • SQL Mapper 두번째 주자로 MyBatis 탄생
    • 반복적인 JDBC 프로그래밍을 단순화
    • SQL 쿼리들을 XML 파일에 작성하여 코드와 SQL 을 분리!
    •  But, 결국 SQL을 직접 작성하는것은 피곤하다…(DB 기능에 종속적)
    •  But, 테이블마다 비슷한 CRUD 반복, DB타입 및 테이블에 종속적이다.

3. QueryMapper 의 DB의존성 및 중복 쿼리 문제로 ORM 이 탄생했다.

  • ORM 은 DB의 주도권을 뺏어왔다고 표현해도 과언이 아닙니다.
  • ORM 은 DAO 또는 Mapper 를 통해서 조작하는것이 아니라 테이블을 아예 하나의 객체(Object)와 대응시켜 버립니다.
  • 말이 쉽지…. 객체지향(Object) 을 관계형 데이터베이스(Relation) 에 매핑(Mapping) 한다는건 정말 많은 난관이 있습니다.

ORM이 해결해야 하는 문제점과 해결책

상속의 문제

  • 객체 : 객체 간의 멤버변수나 상속관계를 맺을수있다.
  • RDB : 테이블은 상속 관계가 없고 모두 독립적으로 존재한다.

    해결법 : 매핑정보에 상속정보를 넣어준다(@OneToMany, @ManyToOne)

관계 문제

  • 객체 : 참조를 통해 관계를 가지며 방향을 가진다. (다대다 관계도 있음)
  • RDB : 외래키(FK)를 설정하여 Join 으로 조회시에만 참조가 가능하다. (즉, 다대다는 매핑 테이블 필요)

해결방법 : 매핑정보에 방향정보를 넣어준다. (@JoinColumn, @MappedBy)

탐색 문제

  • 객체 : 참조를 통해 다른 객체로 순차적 탐색이 가능하며 콜렉션도 순회한다.
  • RDB : 탐색시 참조하는 만큼 추가 쿼리나, Join 이 발생하여 비효율적이다.

해결방법 : 매핑/조회 정보로 참조탐색 시점을 관리한다.(@FetchType, fetchJoin())

밀도 문제

  • 객체 : 멤버 객체크기가 매우 클 수 있다.
  • RDB : 기본 데이터 타입만 존재한다.

해결방법 : 크기가 큰 멤버 객체는 테이블을 분리하여 상속으로 처리한다. (@embedded)

식별성 문제

  • 객체 : 객체의 hashCode 또는 정의한 equals() 메소드를 통해 식별
  • RDB : PK 로만 식별

♂️ 해결방법 : PK 를 객체 Id로 설정하고 EntityManager는 해당 값으로 객체를 식별하여 관리 한다.(@Id,@GeneratedValue )

캐싱 기능은 객체 지향 프로그래밍이 가진 가장 큰 장점이다.

  • 1차 캐시

    • 영속성 컨텍스트 내부에는 엔티티를 보관하는 저장소가 있는데 이를 1차 캐시라고 한다.
    • 일반적으로 트랜잭션을 시작하고 종료할 때까지만 1차 캐시가 유효하다.
    • 1차 캐시는 한 트랜잭션 계속해서 원본 객체를 넘겨준다.
  • 2차 캐시

    • 애플리케이션 범위의 캐시로, 공유 캐시라고도 하며, 애플리케이션을 종료할 때 까지 캐시가 유지된다.
    • 2차 캐시는 캐시 한 객체 원본을 넘겨주지 않고 복사본을 만들어서 넘겨준다.
    • 복사본을 주는 이유는 여러 트랜잭션에서 동일한 원본객체를 수정하는일이 없도록 하기 위해서이다.

2차캐시 적용방법

  1. Entity에 @Cacheable 적용 후 설정 추가
@Entity
@Cacheable
public class Team {
# application.yml

spring.jpa.properties.hibernate.cache.use_second_level_cache: true
# 2차 캐시 활성화합니다.

spring.jpa.properties.hibernate.cache.region.factory_class: XXX
# 2차 캐시를 처리할 클래스를 지정합니다.

spring.jpa.properties.hibernate.generate_statistics: true
# 하이버네이트가 여러 통계정보를 출력하게 해주는데 캐시 적용 여부를 확인할 수 있습니다.
  1. sharedCache.mode 설정
# appplication.yml

spring.jpa.properties.javax.persistence.sharedCache.mode: ENABLE_SELECTIVE
cache mode 종류내용
ALL모든 엔티티를 캐시합니다.
NONE캐시를 사용하지 않습니다.
ENABLE_SELECTIVECacheable(true)로 설정된 엔티티만 캐시를 적용합니다.
DISABLE_SELECTIVE모든 엔티티를 캐시하는데 Cacheable(false)만 캐시하지 습니다.
UNSPECIFIEDJPA 구현체가 정의한 설정을 따릅니다

영속성 컨텍스트(1차 캐시)를 활용한 쓰기지연

영속성 4가지 상태 ( 비영속 > 영속 > 준영속 | 삭제)

  • 쓰기 지연이 발생하는 시점
    • flush() 동작이 발생하기 전까지 최적화한다.
    • flush() 동작으로 전송된 쿼리는 더이상 쿼리 최적화는 되지 않고, 이후 commit()으로 반영만 가능하다.
  • 쓰기 지연 효과
    • 여러개의 객체를 생성할 경우 모아서 한번에 쿼리를 전송한다.
    • 영속성 상태의 객체가 생성 및 수정이 여러번 일어나더라도 해당 트랜잭션 종료시 쿼리는 1번만 전송될 수 있다.
    • 영속성 상태에서 객체가 생성되었다 삭제되었다면 실제 DB에는 아무 동작이 전송되지 않을 수 있다.
    • 즉, 여러가지 동작이 많이 발생하더라도 쿼리는 트랜잭션당 최적화 되어 최소쿼리만 날라가게된다.

키 생성전략이 generationType.IDENTITY 로 설정 되어있는 경우 생성쿼리는 쓰기지연이 발생하지 못한다.

  • why? 단일 쿼리로 수행함으로써 외부 트랜잭션에 의한 중복키 생성을 방지하여 단일키를 보장한다.

flush() 와 commit()은 엄연히 다르다

flush는 DB에 쿼리가 전송되었으나 반영이 안된상태로
git으로 이야기 할것같으면 commit을 하고 push를 하지 않은 반영 대기 상태이다
트랜잭션1 에서 업데이트하거나 추가한 내용을 트랜잭션2 에서는 알수없다.
아니면 pull request를 하고 다른 사람들이 모여서 회의 하면서 이것을 반영할지 안할지 토론하는 준비단계이기도 하다.
현실로는 판매자가 택배를 출발 시켰을때의 상태이다 아직 도착하여 고객이 받지 못했다. 취소가 가능하다

commit은 flush가 된것을 DB에 적용시키는것으로
commit이 된다면 다른 트랜잭션이 드디어 알수있게 된다.
git에서는 드디어 push가 되어 git프로젝트내에 적용되여 다른 사람들도 가져가거나 볼수있게되는것이다.
고객이 택배를 받아서 승인한 상태로 더이상 취소가 불가능하며 반품이라는 기능을 사용해야한다.

EntityManager em = entityMangerFactiory.createEntityManger();
EntityTransaction transaction = em.getTransaction();
Team teamA = new Team();
teamA.setName("TeamA");
em.persist(teamA);//DB에 삽입

em.flush(teamA);//DB에 보내기 생략가능(영성속 컨텍스트에 저장된 상태)
transaction.commit()//트렌젝션을 DB에 반영, 실제 insert SQL커밋 수행

Team teamB = new Team();
teamB.setName("TeamB");
em.persist(teamB);//DB에 삽입

Member member_A = new Member();
member_A.setName("memberA");
member_A.setTeam(teamA);

em.persist(member_A);

Member findMember = em.find(Member.class, member_A.getId());
Team findTeam= findMember.getTeam();

System.out.println(findTeam.getName());
flush가 있는 경우

create member
create team
insert team      // flush로 인해 쓰기지연이 발생하지 않음
insert member    // flush로 인해 쓰기지연이 발생하지 않음
print "TeamA" (memberA.getTeam())
flush가 없는 경우

create member
create team
print "TeamA" (memberA.getTeam()) // 쓰기 지연이 발생하더라도 영속성 컨텍스트에서 조회해옴
insert team      // 쓰기 지연이 발생한 부분
insert member    // 쓰기 지연이 발생한 부분
profile
공부기록하기

0개의 댓글