JPA 세팅하기

3 minute read

김영한 : 자바 ORM 표준 JPA 프로그래밍 - 기본편 강의를 공부한 내용입니다.


환경

Mac Mojave (10.14.6)
IntelliJ IDEA Ultimate
JDK 11
Maven
h2 (1.4.199)


Maven Dependency

<dependencies>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>5.3.10.Final</version>
    </dependency>
    
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.199</version>
    </dependency>

    <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.1</version>
    </dependency>
</dependencies>

JDK11 부터 Java EE 모듈이 제거되었다.
JDK11 이상 버전을 사용하는 경우 JAXB 라이브러리를 꼭 추가해줘야한다.

추가하지 않는다면 아래와 같은 에러가 발생한다.

/Library/Java/JavaVirtualMachines/jdk-11.0.11.jdk/Contents/Home/bin/java -javaagent:/Users/ekkkk1/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/211.7442.40/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=51071:/Users/ekkkk1/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/211.7442.40/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/ekkkk1/workspace/inflearn-kyh/jpa-basic/ex01-hello-jpa/target/classes:/Users/ekkkk1/.m2/repository/org/hibernate/hibernate-entitymanager/5.3.10.Final/hibernate-entitymanager-5.3.10.Final.jar:/Users/ekkkk1/.m2/repository/org/jboss/logging/jboss-logging/3.3.2.Final/jboss-logging-3.3.2.Final.jar:/Users/ekkkk1/.m2/repository/org/hibernate/hibernate-core/5.3.10.Final/hibernate-core-5.3.10.Final.jar:/Users/ekkkk1/.m2/repository/org/javassist/javassist/3.23.2-GA/javassist-3.23.2-GA.jar:/Users/ekkkk1/.m2/repository/antlr/antlr/2.7.7/antlr-2.7.7.jar:/Users/ekkkk1/.m2/repository/org/jboss/jandex/2.0.5.Final/jandex-2.0.5.Final.jar:/Users/ekkkk1/.m2/repository/com/fasterxml/classmate/1.3.4/classmate-1.3.4.jar:/Users/ekkkk1/.m2/repository/javax/activation/javax.activation-api/1.2.0/javax.activation-api-1.2.0.jar:/Users/ekkkk1/.m2/repository/org/dom4j/dom4j/2.1.1/dom4j-2.1.1.jar:/Users/ekkkk1/.m2/repository/org/hibernate/common/hibernate-commons-annotations/5.0.4.Final/hibernate-commons-annotations-5.0.4.Final.jar:/Users/ekkkk1/.m2/repository/javax/persistence/javax.persistence-api/2.2/javax.persistence-api-2.2.jar:/Users/ekkkk1/.m2/repository/net/bytebuddy/byte-buddy/1.9.5/byte-buddy-1.9.5.jar:/Users/ekkkk1/.m2/repository/org/jboss/spec/javax/transaction/jboss-transaction-api_1.2_spec/1.1.1.Final/jboss-transaction-api_1.2_spec-1.1.1.Final.jar:/Users/ekkkk1/.m2/repository/com/h2database/h2/1.4.199/h2-1.4.199.jar me.gicheol.JpaMain
6 05, 2021 11:59:51 오후 org.hibernate.jpa.internal.util.LogHelper logPersistenceUnitInformation
INFO: HHH000204: Processing PersistenceUnitInfo [
	name: hello
	...]
6 05, 2021 11:59:51 오후 org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.3.10.Final}
6 05, 2021 11:59:51 오후 org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException
	at org.hibernate.boot.spi.XmlMappingBinderAccess.<init>(XmlMappingBinderAccess.java:43)
	at org.hibernate.boot.MetadataSources.<init>(MetadataSources.java:86)
	at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.<init>(EntityManagerFactoryBuilderImpl.java:212)
	at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.<init>(EntityManagerFactoryBuilderImpl.java:174)
	at org.hibernate.jpa.boot.spi.Bootstrap.getEntityManagerFactoryBuilder(Bootstrap.java:76)
	at org.hibernate.jpa.HibernatePersistenceProvider.getEntityManagerFactoryBuilder(HibernatePersistenceProvider.java:171)
	at org.hibernate.jpa.HibernatePersistenceProvider.getEntityManagerFactoryBuilderOrNull(HibernatePersistenceProvider.java:119)
	at org.hibernate.jpa.HibernatePersistenceProvider.getEntityManagerFactoryBuilderOrNull(HibernatePersistenceProvider.java:61)
	at org.hibernate.jpa.HibernatePersistenceProvider.createEntityManagerFactory(HibernatePersistenceProvider.java:50)
	at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:79)
	at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:54)
	at me.gicheol.JpaMain.main(JpaMain.java:11)
Caused by: java.lang.ClassNotFoundException: javax.xml.bind.JAXBException
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
	... 12 more

Process finished with exit code 1

JPA 설정하기

경로는 /resources/META-XML/persistence.xml 이다.

<persistence-unit name="hello">
    <properties>
        <!-- 필수 속성 -->
        <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
        <property name="javax.persistence.jdbc.user" value="jdbc:h2:tcp://localhost/~test"/>
        <property name="javax.persistence.jdbc.password" value=""/>
        <property name="javax.persistence.jdbc.url" value=""/>
        <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.hbm2ddl.auto" value="create"/>-->
    </properties>
</persistence-unit>

persistence-unit name은 일종의 DB의 이름이라고 볼 수 있다.

필수 속성은 JDBC 설정을 하듯이 DB 정보를 기입한다.

여기서 특이한 것은 hibernate.dialect인데, JPA 방언이라 부른다.
각 DB는 문법이 조금씩 다를 수 있다.
JPA는 dialect를 통해 이를 맞춰 준다.

옵션도 하나씩 알아보겠다.

  • hibernate.show_sql : hibernate가 DB에 날리는 쿼리를 보여준다.
  • hibernate.format_sql : 쿼리를 예쁘게 보기 좋게 출력해준다. (show_sql이 false면 보여지지 않음)
  • hibernate.use_sql_comments : 추가적인 주석을 출력해준다. (ex) JPQL문)
  • hibernate.hbm2ddl.auto : 애플리케이션 실행 시 DDL 구문을 자동으로 실행시켜준다.
속성 의미 쿼리
create 애플리케이션이 실행될 때 기존 테이블을 삭제하고 새로 생성한다. DROP - CREATE
create-drop create와 동일하나, 애플리케이션이 종료될 때 생성했던 테이블을 삭제한다. DROP - CREATE - DROP
update 기존 테이블이 존재할 경우 그대로 사용한다. 존재하지 않는다면 생성한다. ALTER
validate Session factory 실행시 스키마가 적합한지 검사 후 이상있으면 예외를 발생시킨다.  
none DDL 기능을 사용하지 않는다.  

주의할 점은 update는 진정한 update가 아니다.
만약 Member 테이블에 id란 컬럼이 member_id로 변경되었다.
속성을 update로 둔 채로 실행시키면,
기존 id 컬럼이 삭제되고, member_id 컬럼이 생성되는 것이 아니라,
id는 유지되고, member_id는 생성된다.

JPA가 member_id를 새로 생성된건지, 수정된건지 알 방법이 없기 때문이다.
그렇기 때문에 update는 위험할 수 있다.

개발시엔 create, 운영시엔 validate 혹은 none을 사용하는 것이 좋아 보인다.


JPA 구동 방식

error

JPA는 persistance.xml의 정보를 조회해 Persistence 클래스를 생성한다.
이를통해 EntityManagerFactory를 생성하고, EntityManager를 만들어 사용한다.

public static void main(String[] args) {
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
    EntityManager em = emf.createEntityManager();
    EntityTransaction tx = em.getTransaction();

    try {
        /* 
            비즈니스 로직
            ...
            */
            
        tx.commit();
    } catch (Exception e) {
        tx.rollback();    
    } finally {
        em.close();
    }

    emf.close();
}

그림의 순서대로 Persistance를 통해 EntityManagerFactory를 만들고,
또 이를통해 EntityManger를 생성한다.

이때 Persistence.createEntityManagerFactory()의 인자값은
persistence.xml의 persistence-unit name의 값이다.

또한 JPA는 반드시 Transaction안에서 수행되어야 하기 때문에
EntityTransaction 객체를 만들어야한다.


Table 생성

CREATE TAGBLE Member (
    id BIGINT NOT NULL,
    name VARCHAR(255),
    PRIMARY KEY(id)
);

CRUD

// C
Member member = new Member();
member.setId(1L);
member.setName("GICHEOL");

em.persist(member);

// R
Member member = em.find(Member.class, 1L);
System.out.println("member.getId() = " + member.getId());
System.out.println("member.getName() = " + member.getName());

// U
Member member = em.find(Member.class, 1L);
member.setName("EKKKK1");

// D
Member member = em.find(Member.class, 1L);
em.remove(member);

여기서 제일 신기한건 역시 update이다.
따로 persist를 하지 않아도, 자바 객체를 다루듯이 알아서 update를 해준다.

아래를 보면 update문이 실행된것을 알 수 있다.

Hibernate: 
    select
        member0_.id as id1_0_0_,
        member0_.name as name2_0_0_ 
    from
        Member member0_ 
    where
        member0_.id=?
Hibernate: 
    /* update
        me.gicheol.Member */ update
            Member 
        set
            name=? 
        where
            id=?

주의사항

  • EntityManagerFactory는 하나만 생성되어 App 전체에서 공유된다.
  • EntityManager는 요청에 따라 생기고 삭제된다 쓰레드간에 공유되면 안된다.
  • JPA의 모든 데이터 변경은 트랜잭션 안에서 실행되어야 한다.

JPQL

JPA도 쿼리를 완전히 사용하지 않는건 아니다.

JPA는 Entity 객체를 중심으로 개발되는데,
SELECT 쿼리는 상당히 복잡해질 가능성이 높다.

복잡한 쿼리를 사용하게 될 경우 JPQL을 사용해야 한다.

JPQL은 SQL을 추상화한 객체지향 쿼리 언어이며, SQL문법과 유사하다.
Entity 객체를 대상으로 쿼리한다.

아래와 같은 모양새이다.

List<Member> result = em.createQuery("select m from Member as m", Member.class).getResultList();

result.forEach(System.out::println);
em.find()

    Hibernate: 
        select
            member0_.id as id1_0_0_,
            member0_.name as name2_0_0_ 
        from
            Member member0_ 
        where
            member0_.id=?
    member.getId() = 1
    member.getName() = GICHEOL


em.createQuery()

    Hibernate: 
        /* select
            m 
        from
            Member as m */ select
                member0_.id as id1_0_,
                member0_.name as name2_0_ 
            from
                Member member0_
    Member{id=1, name='GICHEOL'}

다시한번 말하지만 JPA는 JPQL을 사용할때 대상은 DB 테이블이 아닌 객체이다.

select m 을 했을 뿐인데, 실제 실행된 쿼리를 보면
Member의 id와 name을 조회한 것을 알 수 있다.
(주석으로 된 쿼리는 작성한 JPQL문이다.)

JPQL의 장점은 SQL을 추상화했기 때문에,
개발 중간에 데이터베이스가 변경되더라도,
특정 SQL 문법에 의존적이지 않는다.

ex) Oracle : SUBSTR
    MySQL : SUBSTRING


Tags:

Categories:

Updated:

Leave a comment