JPA (2) JPA 설정과 기본 구동 방식

김영한님의 자바 ORM 표준 JPA 프로그래밍 강의 정리

Project setting

  1. H2 Database 설치

  2. dependencies
    • 강의에서는 maven 기반이지만 gradle로 설정했으므로 아래처럼 추가한다
       dependencies {
       	implementation 'org.hibernate:hibernate-core:5.6.5.Final'
       	runtimeOnly 'com.h2database:h2'
       }
      
  3. JPA 설정
    • src/main/resources 폴더에 META-INF 폴더 생성
    • persistence.xml 추가
    • javax.persistence.jdbc.url에서 url은 사용하는 DB에 맞게 변경해야 함
     <?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는 특정 DB에 종속되지 X
    • hibernate.dialect에서 DB에 맞게 설정

JPA 구동 방식

  1. Persistance가 META-INF/persistence.xml에서 설정 정보 조회
  2. Persistance가 EntityManagerFactory 생성
  3. EntityManagerFactory에서 EntityManager들을 생성

Main

메인 메서드에서 EntityManagerFactory를 생성하도록 한다. 여기에 persistanceUnitName을 넣어줘야 하는데 이 이름은 persistence.xml에서 ` `에서 설정한 name 값을 넣어주도록 한다. EntityManagerFactory를 통해서 EntityManager를 만들고 EntityManager를 close하기 전에 필요한 작업들을 하고 모든 작업이 끝나면 EntityManagerFactory도 닫아주도록 한다.

public class HelloJpaApplication {

	public static void main(String[] args) {
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

		EntityManager em = emf.createEntityManager();
		em.close();
		
		emf.close();
	}

}

Entity & table

간단하게 member라는 테이블을 H2 DB에서 만들고 객체도 생성하도록 한다. 객체를 생성할 때 꼭 @Entity 어노테이션을 추가해서 JPA가 관리하는 대상이라는 걸 알 수 있도록 한다. PK에 해당하는 필드에는 @Id 어노테이션을 추가해준다.

create table Member (
    id bigint not null,
    name varchar(255),
    primary key (id)
);
// Member
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;
	}
}

이렇게 테이블과 엔터티 생성을 마치고 메인 메서드에서 엔터티 추가 작업을 해보았다. 기존에 EntityManagerFactory, EntityManager를 생성한 코드에서 트랜잭션을 가져오는 코드도 추가해야 한다. JPA에서는 모든 데이터 변경이 트랜잭션 안에서 이루어지기 때문에 트랜잭션을 가져온 후에 트랜잭션을 시작하고 그 후에 member 엔터티를 추가하는 코드를 작성한다. 그 다음, EntityManager에서 저장하도록 persist하고 트랜잭션에서도 커밋해주도록 한다.

public class HelloJpaApplication {

	public static void main(String[] args) {
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

		EntityManager em = emf.createEntityManager();
        
        // JPA에서 모든 데이터 변경은 트랜잭션 안에서 실행
		EntityTransaction tx = em.getTransaction();
		tx.begin();
		
        // member 추가 코드
		Member member = new Member();
		member.setId(1L);
		member.setName("user1");
        
		em.persist(member);  // 저장

		tx.commit(); // 커밋

		em.close();

		emf.close();
	}

}

그런데 앱을 실행하자 Exception in thread “main” java.lang.IllegalArgumentException: Unknown entity: basic.hellojpa.Member 라는 메세지가 나왔다. 검색해보니 gradle을 사용할 때는 persistence.xml에 클래스를 추가해줘야 해결되는 문제였다. <classs>로 경로를 잘 맞게 클래스를 추가해주도록 한다.

<persistence-unit name="hello">
        <class>basic.hellojpa.Member</class>
    ...
</persistence-unit>

다시 앱을 실행하면 하이버네이트에서 실행한 쿼리가 출력된다. H2 콘솔에서도 데이터가 추가된 것을 확인할 수 있다.

Hibernate: 
    /* insert basic.hellojpa.Member
        */ insert 
        into
            Member
            (name, id) 
        values
            (?, ?)

Update & Delete

위에서 추가한 멤버 객체를 수정하고 싶을 때도 매우 간단하다. find 메서드로 클래스와 PK 값을 넣어서 객체를 가져오고 setter 메서드에서 수정해주면 끝이다. 메인 메서드의 코드는 에러가 발생할 경우, 트랜잭션을 롤백하는 코드로 변경하도록 했다. 데이터 삭제는 더 간단하게 remove에 find로 찾은 객체를 넣어주면 된다.

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.getName());	// user1
			findMember.setName("user2");
			System.out.println(findMember.getName());   // user2
            
            // em.remove(findMember); 데이터 삭제
            
			tx.commit();

		} catch (Exception e) {
			tx.rollback();
		} finally {
			em.close();
		}

		emf.close();
	}

주의할 사항

  • EntityManagerFactory는 하나만 생성해서 애플리케이션 전체에서 공유
  • EntityManager는 쓰레드 간에 공유하면 안됨 (사용 후 버려야 함)
  • JPA의 모든 데이터 변경트랜잭션 안에서 실행

JPQL

JPA를 사용하면 엔티티를 중심으로 개발하게 되는데 검색 쿼리를 짤 때 어려움이 생긴다. 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 해야하는데 모든 DB데이터를 객체로 변환해서 검색할 수는 없다. JPA에서는 SQL를 추상화한 JPQL이라는 객체 지향 쿼리 언어를 제공하는데 SQL과 문법이 비슷하며 SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN을 지원한다. JPQL은 엔티티 객체를 대상으로 쿼리를 짜도록 한다. 그렇기 때문에 아래의 JPQL 문에서 Member는 DB 테이블이 아닌 Member라는 엔티티이다.

em.createQuery("select m FROM Member as m", Member.class).getResultList();