JPA

devkwon·2024년 1월 5일

ORM?

MYbatis같은 SQL mapper를 사용하면 JDBC API 사용 코드를 많이 줄일 수 있지만 여전히 CRUD용 SQL문을 작성해야한다.

이러한 과정은 매우 비생산적

객체와 관계형 데이터베이스 간의 차이를 중간에서 해결해주는 ORM(Object Relational Mapping)프레임워크가 이를 해결해준다.

ORM중 자바 진영의 표준이 JPA다.

JPA

지루하고 반복적인 CRUD SQL을 알아서 처리해줄 뿐만 아니라 객체 모델링과 관계형 데이터베이스 사이의 패러다임의 차이점도 해결해준다. 따라서 개발자는 SQL을 직접 작성하는 것이 아니라 어떻게 작동될지만 생각하면 된다.

왜 JPA를 사용해야하는가?

  • 생산성
    JPA를 사용하면 자바 컬렉션에 객체를 저장하듯이 JPA에게 저장할 객체를 전달하면 된다. 더 이상 SQL을 개발자가 직접 작성하지 않아도 된다. 따라서 데이터베이스 설계 중심의 패러다임을 객체 설계 중심으로 역전시킬 수 있다.

  • 유지보수
    SQL을 직접 다루면 엔티티에 변경사항이 생기면 결과를 매핑하기 위한 JDBC API 코드를 모두 변경해야 했다. 반면에 JPA를 사용하면 이런 과정을 JPA가 대신 처리해주므로 수정해야할 코드가 적어진다. 또한 패러다임의 차이를 해결해주므로 객체지향 언어가 가진 장점을 활용해서 유연하고 유지보수하기 좋은 도메인 모델을 설계할 수 있다.

  • 성능
    JPA는 애플리케이션과 데이터베이스 사이에서 다양한 성능 최적화 기회를 제공한다.

  • 데이터 접근 추상화와 벤더 독립성
    JPA는 애플리케이션과 데이터베이스 사이에 추상화된 데이터 접근 계층을 제공해서 애플리케이션이 특정 데이터베이스 기술에 종속되지 않도록 한다.

  • 표준
    JPA는 자바 진영의 ORM 표준이다. 표준을 사용하면 다른 구현 기술로 손쉽게 변경할 수 있다.

springboot starter에서 jpa를 불러오면 된다.

매핑

@Entity

해당 클래스를 테이블과 매핑한다고 JPA에게 알려준다. 해당 어노테이션이 사용된 클래스를 엔티티 클래스라 한다.

@Table

엔티티 클래스에 매핑할 테이블 정보를 알려준다. 해당 어노테이션을 생략하면 클래스 이름을 테이블 이름으로 매핑한다.

@Id

엔티티 클래스의 필드를 Primary Key에 매핑한다.

@Column

필드를 컬럼에 매핑한다.

Nothing

만약 매핑 어노테이션이 없다면, 필드명을 사용해서 컬럼명으로 매핑한다.

Persistence.xml 설정

JPA는 persistence.xml을 사용해서 필요한 설정 정보를 관리한다.

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.1"> //버젼과 네임스페이스 지정

    <persistence-unit name="jpabook">
    //영속성 유닛에 이름 부여

        <properties>

            <!-- 필수 속성 -->
            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
            <property name="javax.persistence.jdbc.user" value="sa"/>
            <property name="javax.persistence.jdbc.password" value=""/>
            <property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" /> 
            // 데이터베이스 방언

            <!-- 옵션 -->
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.format_sql" value="true" />
            <property name="hibernate.use_sql_comments" value="true" />
            <property name="hibernate.id.new_generator_mappings" value="true" />

            <!--<property name="hibernate.hbm2ddl.auto" value="create" />-->
        </properties>
    </persistence-unit>

</persistence>

데이터베이스 방언

JPA는 특정 데이터베이스에 종속적이지 않은 기술이다. 그런데 각 데이터베이스가 제공하는 문법과 함수는 조금씩 다르다. 이때 특정 데이터베이스만의 고유한 기능을 JPA에서는 방언(Dialect)이라 한다.

개발

JPA코드는 크게 3가지로 나누어져 있다.

  • 엔티티 매니저 설정
  • 트랜잭션 관리
  • 비즈니스 로직

엔티티 매니저 설정

엔티티 매니저 팩토리 생성

JPA를 시작하려면 우선 persistence.xml의 설정 정보를 사용해서 엔티티 매니저 팩토리를 생성해야 한다. 이때 Persistence 클래스를 사용하는데 이 클래스는 엔티티 매니저 팩토리를 생성해서 JPA를 사용할 수 있게 한다.

 //엔티티 매니저 팩토리 생성
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
        

해당 코드를 통해 이름이 jpabook인 영속성 유닛을 찾아서 엔티티 매니저 팩토리를 생성한다.

persistence.xml을 읽고 JPA를 동작시키기 위한 기반 객체를 만들고, JPA 구현체에 따라서는 데이터베이스 커넥션 풀도 생성하므로 엔티티 매니저 팩토리를 생성하는 비용은 아주 크다. 따라서 엔티티 매니저 팩토리는 어플리케이션 전체에서 딱 한 번만 생성하고 공유해서 사용해야 한다.

엔티티 매니저 생성

        EntityManager em = emf.createEntityManager(); //엔티티 매니저 생성

엔티티 매니저 팩토리에서 엔티티 매니저를 생성한다. 엔티티 매니저는 JPA의 대부분의 기능을 제공한다. 엔티티 매니저는 데이터베이스 커넥션과 밀접한 관계가 있으므로 스레드 간에 공유하거나 재사용하면 안 된다.

종료

em.close();

사용이 끝난 엔티티 매니저는 반드시 종료해야 한다.

emf.close();

애플리케이션을 종료할 때 엔티티 매니저 팩토리도 종료해야 한다.

트랜잭션 관리

JPA를 사용하면 항상 트랙잭션 안에서 데이터를 변경해야 한다. 그렇지 않으면 예외가 발생한다. 트랜잭션을 시작하려면 엔티티 매니저에서 트랜잭션 API를 받아와야 한다.

EntityTransaction tx = em.getTransaction(); //트랜잭션 기능 획득

        try {


            tx.begin(); //트랜잭션 시작
            logic(em);  //비즈니스 로직
            tx.commit();//트랜잭션 커밋

        } catch (Exception e) {
            e.printStackTrace();
            tx.rollback(); //트랜잭션 롤백
        }

비즈니스 로직

    public static void logic(EntityManager em) {
		
        //객체 생성
        String id = "id1";
        Member member = new Member();
        member.setId(id);
        member.setUsername("test");
        member.setAge(2);

        //등록
        em.persist(member);

        //수정
        member.setAge(20);

        //단일 조회
        Member findMember = em.find(Member.class, id);
        System.out.println("findMember=" + findMember.getUsername() + ", age=" + findMember.getAge());

        //목록 조회
        List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
        System.out.println("members.size=" + members.size());

        //삭제
        em.remove(member);

    }

JPQL

TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class); 
// Member 테이블이 아닌 Member 객체
List<Member> members = query.getResultList();

JPA를 사용하면 애플리케이션 개발자는 엔티티 객체를 중심으로 개발하고 데이터베이스에 대한 처리는 JPA에 맡겨야한다. 하지만 검색 쿼리는 문제가 된다. 만약 엔티티 객체를 대상으로 검색하려면 데이터베이스의 모든 데이터를 불러와서 엔티티 객체로 변경한 다음 검색해야하는데, 이는 불가능하다.
결국 검색 조건이 포함된 SQL을 사용해야 한다. JPA는 이를 JQPL(Java Persistence Query Language)로 해결한다.

  • JQPL은 엔티티 객체를 대상으로 쿼리한다.
  • SQL은 데이터베이스 테이블을 대상으로 쿼리한다.

JPQL은 데이터베이스 테이블을 전혀 알지 못한다.
JPQL을 사용하려면 em.createQuery(JPQL,반환 타입) 메소드를 실행해서 쿼리 객체를 생성한 후 쿼리 객체의 getResultList() 메소드를 호출하면 된다. 그후 JPA는 JPQL을 분석해서 적절한 SQL을 만들어 데이터베이스에서 데이터를 조회한다.

0개의 댓글