JPA 세팅하기
김영한 : 자바 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 구동 방식
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
Leave a comment