JPA(1)

3 minute read

JPA 소개

  • Java Persistence API
  • 자바 진영의 ORM 기술 표준

ORM이란?

  • Object-relational mapping(객체 관계 매핑)
  • 객체는 객체대로 설계
  • 관계형 데이터베이스는 관계형 데이터베이스대로 설계
  • ORM 프레임워크가 중간에서 매핑
  • 대중적인 언어에는 대부분 ORM 기술이 존재

JDBC 동작 원리

GitHub Logo -> JPA 내부에서 요청이 들어오면 JDBC API를 이용하여 자동 쿼리를 생성 후 적용

JPA를 왜 사용해야 할까?

  • SQL 중심적인 개발에서 객체 중심으로 개발
  • 생산성
  • 유지보수
  • 패러다임의 불일치 해결
  • 성능
  • 데이터 접근 추상화와 벤더 독립성
  • 표준

생산성 - JPA와 CRUD

  • 저장 : jpa.persist(member)
  • 조회 : Member member = jpa.find(memberId)
  • 수정 : member.setName(“이름)
  • 삭제 : jpa.remove(member)

-> JDBC처럼 긴 sql문을 사용하지 않아도된다.

유지보수

  • 기존 : 필드 변경시 모든 SQL 수정
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가 처리
public class Member {
 private String memberId;
 private String name;
 private String tel;
 ...
}

-> sql을 수정할 필요도 만들 필요도 없다.

JPA와 패러다임의 불일치 해결

다음 4가지와 같은 문제들을 JPA는 해결해준다.

상속

GitHub Logo

-> 만약 Item 객체를 상속받는 Movie 객체를 DB에 저장한다면, 객체를 테이블에 맞추어 분해하고 각 테이블에 INSERT 쿼리를 보내야한다. 그리고 Movie객체를 조회한다면 각각의 테이블을 Join을 수행하여야한다.

JPA는 아래와 같이 동작하여 해결한다.

  • 삽입
jpa.persist(movie); // 개발자가 할 일

/** 귀찮은 쿼리들은 JPA가 처리
INSERT INTO ITEM...
INSERT INTO MOVIE...
**/

  • 조회
Movie movie = jpa.find(Movie.class, movieId); //개발자가 할 일
/** 귀찮은 쿼리들은 JPA가 처리
SELECT I.*, A.*
 FROM ITEM I
 JOIN MOVIE A ON I.ITEM_ID = A.ITEM_ID
**/

연관관계

// 연관관계 저장
memeber.setTeam(team);
jpa.persist(member);

//객체 그래프 탐색
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam();

-> 연관관계 매핑 필요없이 바로 호출하여 팀 확인가능(JPA의 지연로딩 기능 덕분), 자유로운 객체 그래프 탐색 가능

JPA와 비교하기

  • JDBC로 비교하기
    String memberId = "100";
    Member member1 = memberDAO.getMember(memberId);
    Member member2 = memberDAO.getMember(memberId);
    member1 == member2; //다르다.

    class MemberDAO {
    
    public Member getMember(String memberId) {
    String sql = "SELECT * FROM MEMBER WHERE MEMBER_ID = ?";
    //JDBC API, SQL 실행
    return new Member();
    }
    }

-> 쿼리를 날릴 때마다 객체를 생성하여 반환하기 때문에 서로 다르다.

JPA로 호출하여 비교하기

String memberId = "100";
Member member1 = jpa.find(Member.class, memberId);
Member member2 = jpa.find(Member.class, memberId);
member1 == member2; //같다.

-> 동일한 트랜잭션에서 조회한 엔터티는 같음을 보장

JPA의 성능 최적화 기능

  • 1차 캐시와 동일성(identiy) 보장

같은 트랜잭션 안에서는 같은 엔티티를 반환 - 약간의 조회 성능 향상 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

트랜잭션을 커밋할 때까지 INSERT SQL을 모음 JDBC BATCH SQL 기능을 사용해서 한번에 SQL 전송

transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 모아서 보낸다.
transaction.commit(); // [트랜잭션] 커밋
  • 트랜잭션을 지원하는 쓰기 지연 - UPDATE

UPDATE, DELETE로 인한 로우(ROW)락 시간 최소화 트랜잭션 커밋 시 UPDATE, DELETE SQL 실행하고, 바로 커밋

transaction.begin(); // [트랜잭션] 시작
changeMember(memberA); 
deleteMember(memberB); 
비즈니스_로직_수행(); //비즈니스 로직 수행 동안 DB 로우 락이 걸리지 않는다. 
//커밋하는 순간 데이터베이스에 UPDATE, DELETE SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋
  • 지연 로딩과 즉시 로딩

지연 로딩: 객체가 실제 사용될 때 로딩 즉시 로딩: JOIN SQL로 한번에 연관된 객체까지 미리 조회

GitHub Logo

-> 지연 로딩은 team의 이름이 필요할 때 그때 JPA가 sql쿼리를 요청해서 반환한다. -> 즉시 로딩은 처음 member가 호출될 때 바로 TEAM 테이블과 조인을해서 연관된 테이블을 모두 찾아서 반환한다.

데이터 접근 추상화 벤더 독립성

JPA는 interface들의 집합으로써 특정 DB에 종속적이지 않습니다. 각각의 DB가 제공하는 SQL 문법과 함수는 조금씩 다르기 때문에 Application 개발 시 사용하는 DB에 맞추어 JPA에 DB Dialect(방언)을 설정해주면 대부분의 DB를 사용할 수 있습니다.

GitHub Logo

JPA 동작확인하기

Member Entity

@Entity
@Getter
@Setter
public class Member {
    @Id
    private Long id;
    private String name;

    public Member(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    public Member() {

    }
}

-> Entity 애노테이션을 붙여준다.

@Repository
public class MemberRepository {
    @PersistenceContext
    EntityManager em;

    public void join(Member member){
        em.persist(member);
    }
    public void update(){
        Member findMember = em.find(Member.class,1L);
        findMember.setName("Hong");
    }
    public void find(){
      Member findMember = em.find(Member.class,1L);
    }
    public void remove(){
      Member deleteMember = em.find(Member.class,1L);
      em.remove(deleteMember);
    }

}

-> EntityManger로 기본적인 CRUD가 가능하다.

만약 JPA의 기본 인터페이스가 아닌 경우인 특정 조건에 해당되는 튜플들을 조회하고싶다면?

List<Member> members = em.createQuery("select m from Member as m", Member.class)
                .getResultList();
        for (Member member: members) {
            System.out.println("member = " + member.getId() + ": " + member.getName());
        }

-> JPQL사용(뒤에서 자세히 설명한다.)

  • 출처 : 김영한의 자바 ORM 표준 JPA 프로그래밍 - 기본편

Tags:

Categories:

Updated:

Leave a comment