JPA

[JPA] Hibernate 실행해보기

제리 . 2020. 11. 11. 16:52

새로운 프로젝트를 생성한다. 의존성 관리는 maven을 사용한다. 프로젝트 구조는 아래와 같다.

 

 

먼저 의존성 관리를 위해  pom.xml을 보자

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <dependencies>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>5.4.10.Final</version>
    </dependency>
    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <version>1.4.199</version>
    </dependency>
  </dependencies>
  <groupId>org.example</groupId>
  <artifactId>ch2-jpa</artifactId>
  <version>1.0-SNAPSHOT</version>
</project>

여기서 두가지 라이브러리를 사용한다. 

hibernate는 JPA 표준과 하이버네이트를 포함하는 라이브러리이다.

h2 데이터베이스는 h2 데이터 베이스를 사용하게해준다. 따로 mysql이나 오라클같은 데이터베이스 대신 가벼운 h2를 사용하여 간편하게 테스트한다.

 

이번에는 회원 클래스에 JPA가 제공하는 매핑 어노테이션을 추가해보자.

@Entity
@Table(name = "MEMBER")
public class Member {

  @Id
  @Column
  private String id;

  @Column(name = "NAME")
  private String username;

  private Integer age;

  public String getId() {
    return id;
  }

  public void setId(String id) {
    this.id = id;
  }

  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public Integer getAge() {
    return age;
  }

  public void setAge(Integer age) {
    this.age = age;
  }
}

 

@Entity 

클래스를 테이블고 매핑한다고 JPA에게 알려준다. @Entity가 사용된 클래스를 엔티티 클래스라 한다.

 

@Table

엔티티 클래스에 매핑할 테이블 정보를 알려준다. 여기서는 name 속성을 사용해서 MEMBER테이블에 매핑했다. 이 어노테이션이 없으면 클래스의 이름을 테이블 이름으로 매핑한다.

 

@Id

앤티티 클래스의 필드를 테이블의 기본 키에 매핑한다. 여기서는 엔티티의 id필드를 테이블의 id 기본 키 컬럼에 매핑했다. @Id가 사용된 필드를 식별자 필드라 한다.

 

@Column

필드를 컬럼에 매핑한다. 여기서는 name 속성을 사용해서 Member 엔티티의 username 필드를 MEMBER 테이블의 NAME 컬럼에 매핑했다.

 

매핑 정보가 없는 필드

매핑 어노테이션이 없다면 필드명을 사용해서 컬럼에 매핑한다. 만약 대소문자를 구분하는 데이터베이스를 사용한다면 @Column에 name을 명시적으로 매핑해야한다.

 

 

persistence.xml은 JPA가 사용할 정보를 알려준다.

<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
  <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.user_sql_comments" value="true"/>
      <property name="hibernate.id.new_generator_mappings" value="true"/>
    </properties>
  </persistence-unit>
</persistence>

property부분을 보자

 

javax.persistence.jdbc.driver : jdbc 드라이버

 

javax.persistence.jdbc.user : 데이터베이스 접속 아이디

 

javax.persistence.jdbc.password : 데이터베이스 접속 비밀번호

 

javax.persistence.jdbc.url : 데이터베이스 접속 URL

 

hibernate.dialect : 데이터베이스 방언 설정 (방언은 데이터베이스마다 데이터 타입이나 함수명, 페이징 처리가 다르다. 특정 데이터베이스만의 고유한 기능을 방언이라고한다.)

 

hibernate.show_sql : 실행한 sql 출력

 

hibernate.format_sql : sql을 보기 편한 포멧에 맞춰 출력

 

hibernate.user_sql_comments : 쿼리를 출력할 때 주석도 함께 출력

 

hibernate.id.new_generator_mappings : JPA 표준에 맞춰 새로운 키 생성 전략을 사용(추후에 다시 정리)

 

JpaMain 클래스로 가보자

public class JpaMain {

  public static void main(String[] args) {
    //앤티티 매니저 팩토리 - 생성
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
    //엔티티 매니저 - 생성
    EntityManager em = emf.createEntityManager();
    //트랜잭션 - 획득
    EntityTransaction tx = em.getTransaction();

    try {
      tx.begin(); //트랜잭션 - 시작
      logic(em); //트랜잭션 - 종료
      tx.commit(); //트랜잭션 - 커밋
    } catch (Exception e) {
      tx.rollback(); //트랜잭션 - 롤백
    } finally {
      em.close(); //엔티티 매니저 - 종료
    }
    emf.close(); //엔티티 매니저 팩토리 - 종료
  }
}

코드는 앤티티 매니저 설정, 트랜잭션 관리, 비즈니스 로직 3가지로 구성되어있다.

 

앤티티 매니저 생성 과정은 아래와 같다.

미리 정의된 persistence.xml의 정보를 읽어와 엔티티 매니저 팩토리를 생성한다. 이때 Persistence클래스를 사용하는데 이 클래스는 엔티티 매니저 팩토리를 생성해서 JPA를 사용할 수 있게 준비한다.

 

EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");

이렇게 하면 META-INF/persistence.xml에서 이름이 jpabook인 영속성 유닛을 찾아서 엔티티 매니저 팩토리를 생성한다. JPA를 동작시키기 위한 기반 객체를 만들고 JPA 구현체에 따라 데이터베이스 커넥션 풀도 생성하기 때문에 비용이크다. 따라서 앤티티 매니저 팩토리는 애플리케이션 전체에서 딱 한번만 생성하고 공유해서 사용해야한다.

 

EntityManager em = emf.createEntityManager();

앤티티 매니저는 앤티티 매니저 팩토리에서 생성한다. JPA기능의 대부분은 앤티티 매니저가 제공한다(등록, 수정, 삭제 등등) 앤티티 매니저는 내부의 데이터베이스 커넥션을 유지하면서 데이터베이스와 통신한다. 그러므로 스레드간에 공유하거나 재사용하면 안 된다.

 

다 사용한 엔티티 매니저와 팩토리는 종료해야한다.

 

JPA를 사용한면 항상 트랜잭션 안에서 데이터를 변경해야하고 트랜잭션 없이 데이터를 변경하면 예외가 발생한다.

 

아래의 로직을 실행시켜보자.

private static void logic(EntityManager em) {
    String id = "id1";
    Member member = new Member();
    member.setId(id);
    member.setUsername("Lee");
    member.setAge(25);

    //등록
    em.persist(member);

    //수정
    member.setAge(30);

    //한 건 조회
    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);
  }

여기서 수정 부분을 보면 조금 의아하다. update를 명시적으로 하지않는다. 이는 JPA에서 어떤 엔티티가 변경되었는지 추적하는 기능을 갖추고 있기때문에 명시적으로 알려줄 필요가 없다. 값을 변경하면 update문이 자동으로 실행된다. 그렇기 때문에 값의 변경이 잦은 web view에서 dto대신 사용하게 된다면 데이터베이스에 수많은 쿼리가 날라간다. 

 

위의 애플리케이션을 실행하면 아래의 결과를 얻는다.

 

그런데 여러 회원 목록을 조회하는 코드를 보자.

 

TypeQuery<Member> query = em.createQuery("select m from Member m", Member.class)
List<Member> members = query.getResultList();

JPA를 사용하면 개발자는 객체를 중심으로 개발하고 데이터베이스는 JPA에게 맡겨야한다. 앞에서 실행한 삽입, 수정, 삭제는 SQL을 전혀 사용하지 않았다. 문제는 검색 쿼리이다. JPA는 앤티티 객체 중심이므로 검색할 때도 테이블이아닌 객체를 대상으로 검색해야 한다. 그런데 이런 방식은 데이터베이스의 모든 데이터를 애플리케이션으로 불러와서 앤티티 객체로 변경한 다음에 검색해야한다. 사실상 불가능하다. 애플리케이션에 필요한 데이터만 불러오려면 검색 조건이 포함된 SQL이 필요하다. JPA는 JPQL이라는 쿼리언어로 이 문제를 해결한다.

 

JPQL은 SQL과 유사한 객체지향 쿼리 언어이다. 둘의 가장 큰 차이점은 다음과 같다.

-JPQL은 앤티티 객체를 대상으로 쿼리한다. (클래스와 필드를 대상으로 쿼리)

-SQL은 데이터베이스 테이블을 대상으로 쿼리한다.

 

위의 쿼리를 보면 select문에 객체가 사용되었다. JPQL은 테이터베이스의 테이블을 전혀 알지 못한다. JPA는 위의 JPQL 쿼리를 분석해 적절한 SQL문을 만들어 조회한다.

select m from Member m

-> select M.ID, M.NAME, M.AGE FROM MEMEBER M