on
애플리케이션 개발
자바 ORM 표준 JPA 프로그래밍을 공부하고 정리하는 포스트입니다.
애플리케이션 개발
실제로 JPA를 사용하는 애플리케이션을 만들어보겠습니다.
JPA 구동 방식
먼저, JPA는 Persistence라는 클래스로 시작합니다. 프로젝트를 생성할 때 만든 persistence.xml
이라는 설정 정보를 읽어서 EntityManagerFactory
라는 클래스를 만듭니다.
그리고 EntityManagerFactory
에서 필요할 때마다 EntityManager를 찍어내서 사용합니다.
실습
DB는 H2를 사용하기위해 H2 의존성을 추가하고, 실습에 사용할 JPA 구현체인 hibernate 의존성을 추가하겠습니다.
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>
<groupId>jpa-basic</groupId>
<artifactId>ex1-hello-jpa</artifactId>
<version>1.0.0</version>
<dependencies>
<!-- JPA Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.6.3.Final</version>
</dependency>
<!-- H2 Database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.0.204</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
이어서 persistence.xml 파일을 통해 설정 정보를 추가하겠습니다.
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="hello">
<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.hbm2ddl.auto" value="create" />-->
</properties>
</persistence-unit>
</persistence>
등록
JPA 동작을 확인하기 위해 JpaMain 클래스를 생성해줍니다. 이 클래스에서 JPA가 실제로 동작하는 것을 확인하겠습니다.
package hellojpa;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
}
}
JPA 구동방식에서 봤듯이 Persistence로 시작하고 EntityManagerFactory를 생성하겠습니다. Persistence.createEntityManagerFactory()
를 잘보시면 persistence-unit의 name을 넘기라고 되어있는데 persistence.xml에 보면 이름이 있는 것을 볼 수 있습니다.
만들고 난 후 동작을 확인하기 위해 실행해서 아래와 비슷하게 오류없이 나타나면 잘 실행된 것입니다.
이제 EntityManagerFactory에서 EntityManager를 꺼내야 합니다.
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
/* start code */
/* end code */
em.close();
emf.close();
}
}
EntityManager를 꺼낸 후 주석 사이에서 실제 동작하는 코드를 작성하게 됩니다. 애플리케이션이 완전히 끝나면 EntityManagerFactory가 종료되어야 합니다. 둘 다 종료하는 것까지 추가한 후 실행해보겠습니다.
이렇게 세팅을 한 후 실제로 동작하는 것을 확인하려면 DB에 데이터가 있어야 합니다. 객체와 테이블을 생성하고 매핑해보도록 하겠습니다.
H2 DB를 실행한 후 아래의 SQL 문을 실행해서 테이블을 만들도록 합니다.
create table Member (
id bigint not null,
name varchar(255),
primary key (id)
);
이제 DB가 만들어졌으니 JPA에서 계속 만들어보겠습니다. 테이블과 매핑이 되는 Member라는 클래스를 생성하겠습니다.
package hellojpa;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class Member {
@Id
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
여기서 가장 중요한 것은 @Entity
입니다. 이 애노테이션이 있어야 JPA가 처음 로딩될 때 JPA를 사용한다고 인식하고 관리해주기 때문입니다.
이제 실제로 Member를 저장해보겠습니다. EntityManagerFactory는 애플리케이션 로딩 시점에 단 하나만 만들면 됩니다. EntityManager는 실제 DB를 저장하거나 쿼리를 날리고 종료되는 트랜잭션 단위를 할 때마다 꼭 만들어주어야 합니다.
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
Member member = new Member();
member.setId(1L);
member.setName("HelloA");
em.persist(member);
em.close();
emf.close();
}
}
persist()
에 member를 넣으면 저장해줍니다. member를 생성하고 난 후 id가 없는 상태로 실행하면 에러가 발생합니다. 실행해보겠습니다.
결과를 봤을 때 뭔가 데이터가 추가된거 같지는 않습니다. 왜 그럴까요?
JPA에서는 트랜잭션이라는 단위가 굉장히 중요합니다. 꼭 트랜잭션 안에서 작업을 해야합니다.
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
Member member = new Member();
member.setId(1L);
member.setName("HelloA");
em.persist(member);
tx.commit();
em.close();
emf.close();
}
}
em.getTransaction()
으로 트랜잭션을 가져온 후 트랜잭션을 시작합니다. 로직이 실행되고나서 커밋을 합니다. 실행해보겠습니다.
쿼리가 나온 것을 볼 수 있습니다. 그리고 실제 DB를 보면 저장이 된 것을 볼 수 있습니다.
지금까지 만든 코드는 좋은 코드가 아닙니다. 중간에 문제가 생겼을 때 뒤에 로직이 호출되지 않습니다. 따라서 정석적인 코드로 바꿔보겠습니다.
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member = new Member();
member.setId(2L);
member.setName("HelloB");
em.persist(member);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
여기서 em.close()
가 굉장히 중요합니다. EntityManager가 내부적으로 데이터 커넥션을 가지고 동작하기 때문에 사용을 다하고 나면 꼭 닫아주어야 합니다. 물론 실무에서는 이러한 코드를 스프링이 다 처리해주기 때문에 볼 수 없습니다.
조회
지금까지 등록을 해봤으니 조회하는 코드를 살펴보겠습니다.
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member findMember = em.find(Member.class, 1L);
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.name = " + findMember.getName());
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
EntityManager가 객체를 대신 저장해주는 자바 컬렉션과 같다고 생각하면 됩니다. find()
로 멤버 객체를 찾아와서 잘 찾아왔는지 확인해보겠습니다.
SELECT 쿼리가 나오고 id는 ‘1’, name은 ‘HelloA’인 것을 확인할 수 있습니다.
수정
조회를 해서 멤버를 찾았으니 이름을 바꿔보겠습니다. setName()
을 활용하면 되는데 이 때 persist를 사용할 필요가 없습니다.
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member findMember = em.find(Member.class, 1L);
findMember.setName("HelloJPA");
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
update 쿼리가 발생했고, DB를 확인해보면 값이 변경된 것을 볼 수 있습니다.
자바 객체에서 값만 바꿨는데 데이터가 업데이트 되었습니다. JPA를 통해서 엔티티를 가져오면 JPA가 관리를 해줍니다. 트랜잭션을 커밋하는 시점에 다 체크해서 변경이 있다면 JPA가 업데이트 쿼리를 날려줍니다.
삭제
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member findMember = em.find(Member.class, 1L);
em.remove(findMember);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
삭제는 찾은 객체를 remove()
에 넣어주기만 하면 됩니다.
주의
EntityManagerFactory는 보통 웹 서버가 올라오는 시점에 단 하나만 생깁니다.
EntityManager는 고객의 요청이 올 때마다 데이터베이스 커넥션을 빨리쓰고 돌려주는 것처럼 사용하고 버려야 합니다. 즉, 쓰레드 간에 공유를 해서는 안됩니다.
그리고 JPA의 모든 데이터 변경은 트랜잭션 안에서 실행되어야 합니다.
JPQL 소개
조회 방법으로 find()
를 봤었습니다. 그런데 다음과 같은 고민들이 있다고 생각해보겠습니다.
- 나이가 18살 이상인 회원을 모두 검색하고 싶다면?
- 현재 데이터베이스에 있는 모든 회원을 검색하고 싶다면?
이를 위해서 JPQL을 사용할 수 있습니다.
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
// Member findMember = em.find(Member.class, 1L);
List<Member> result = em.createQuery("select m from Member as m", Member.class).getResultList();
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
이렇게 createQuery()
를 이용해서 쿼리를 작성할 수 있습니다.
그런데 자세히보면 조금 다른 것이 있습니다. JPA의 입장에서 코드를 작성할 때 테이블을 대상으로 코드를 작성하지 않습니다. 그러면 무엇을 대상으로 할까요? 바로 Member 객체입니다. 실행해보겠습니다.
SQL에서는 필드를 다 나열했지만, JPQL은 Member 엔티티를 선택한 것이라고 보면 됩니다.
그러면 이것이 무슨 메리트가 있을까요? 한 가지 예를 들면 페이지네이션이 굉장히 편리해집니다.
...
public class JpaMain {
public static void main(String[] args) {
...
try {
List<Member> result = em.createQuery("select m from Member as m", Member.class)
.setFirstResult(1)
.setMaxResults(10)
.getResultList();
...
}
setFirstResult()
와 setMaxResults()
로 시작지점으로부터 정해진 갯수의 데이터를 쉽게 가져올 수 있습니다.
이처럼 JPQL이라는 것은 객체를 대상으로 하는 객체지향 쿼리라고 생각하시면 됩니다. 방언에 맞춰서 DB에 맞게 알아서 번역을 해주고 기본적으로 ANSI SQL이 지원하는 쿼리는 다 사용이 가능합니다.
JPA를 사용하면 엔티티 객체를 중심으로 개발을 합니다. 그런데 문제는 검색 쿼리입니다. 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색을 하기 때문에 모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능합니다.
결국 애플리케이션에 필요한 데이터만 DB에서 불러오려면 검색조건이 포함된 SQL이 필요합니다. 이 SQL을 RDB에 있는 물리적 테이블에 날려버리면 종속되버리기 때문에 엔티티 객체를 대상으로 날릴 수 있는 JPQL이 제공되는 것입니다.
Comments
JPA 의 다른 글
-
엔티티 매핑 2부 28 Jan 2022
-
엔티티 매핑 1부 22 Jan 2022
-
영속성 관리 - 내부 동작 방식 2부 18 Jan 2022
-
영속성 관리 - 내부 동작 방식 1부 18 Jan 2022
-
애플리케이션 개발 17 Jan 2022
-
JPA 소개 15 Jan 2022
-
JPA 시작하기 07 Nov 2021