본문 바로가기
  • 개발공부 및 일상적인 내용을 작성하는 블로그 입니다.
JPA

JPA 의 구동방식

by 방구석 대학생 2020. 9. 4.

"인프런 - 자바 ORM 표준 JPA 프로그래밍 강의를 듣고 작성한 글 입니다."

www.inflearn.com/course/ORM-JPA-Basic#

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다. 초급 웹 개발 프로그

www.inflearn.com

 

 

JPA 의 기본적인 구동방식은 일단 persistence.xml 파일에 작성되어 있는 내용을 조회한 후 해당 속성을 포함하는 EntityManagerFactory 클래스 객체를 만든다. 이후 트랜잭션, 즉 고객의 요청이 있을 때마다 이 객체를 통해 EntityManager 클래스의 객체를 생성하여 해당 객체를 통해 작업을 진행할 수 있다.

(여기서 EntityManager 클래스 객체는 트랜잭션 요청이 있을 때마다 생성해야 하므로, EntityManagerFactory 클래스와 달리 여러개를 생성할 수 있다.)

 

트랜잭션은 EntityTransaction 클래스의 객체를 이전에 생성한 EntityManager 클래스의 객체를 통해 생성하여 얻을 수 있다.

이후 만들어진 EntityTransaction 클래스의 객체에서 begin 메소드를 이용하여 데이터베이스 트랜잭션을 실행시킨다.

 

 

요약

1. JPA 설정 정보 조회(persistence.xml)

2. EntityManagerFactory 클래스 객체 생성(JPA 설정 정보 참조)

3. 트랜잭션 발생 시 EntityManagerFactory 객체를 통해 EntityManager 클래스 객체 생성

4. 이후 EntityTransaction 클래스의 객체에 트랜잭션을 얻어온 후 begin 메소드를 통해 데이터베이스 트랜잭션 실행

 

 

앞전에 작성한 글에서 persistence.xml 을 통해 JPA 의 설정을 할 수 있다. 해당 파일에서 JPA 가 설정 정보를 조회하고 나면 아래의 소스 코드와 같이 EntityManagerFactory 클래스의 객체를 생성 시킬수 있다.

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

 

EntityManagerFactory 클래스의 객체를 만들때  persistence.xml 파일에 있는 정보에서 property-unit name 속성에 작성되어 있는 이름을 토대로 해당 파일에 작성되어 있는 속성들을 포함하여 EntityManagerFactory 객체를 만들 수 있다.

 

EntityManagerFactory 클래스 객체는 만드는 순간 데이터베이스와의 연결 등 왠만한 작업들을 모두 다 할 수 있으나,

애플리케이션이 로딩 될 시점에 딱 하나만 만들어줘야 한다는 주의점이 있다.

 

이후 트랜잭션, 즉 고객의 요청이 발생할 때마다 해당 트랜잭션을 처리하기 위한 객체인 EntityManager 클래스 객체를 앞전에 생성한 EntityManagerFactory 객체를 통해 생성한다.

EntityManager em = emf.createEntityManager(); 

 

EntityManagerFactory 클래스에서 createEntityManager() 메소드를 꺼내서 활용한다.

EntityManager 클래스 객체의 경우 트랜잭션(클라이언트가 서비스를 이용하는 일련의 과정) 의 단위마다 하나씩 만들어줘야 한다.

즉, 고객의 요청이 있을 경우 EntityManager 클래스 객체를 통해서 작업을 하게 된다.

- 주의할 점 : EntityManager 는 쓰레드간에 공유가 블가능하므로 한번 사용된 뒤엔 버려야 한다.

 

이후 생성한 EntityManager 객체를 통해 EntityTransaction 클래스의 객체를 생성한 후, getTransaction 메소드를 통해 트랜잭션을 얻은 다음 begin() 메소드를 이용하여 데이터베이스 트랜잭션을 시작시킨다.

EntityTransaction tx = em.getTransaction(); // getTransaction 을 통해 트랜잭션을 얻을 수 있다.
tx.begin(); // 데이터베이스 트랜잭션 시작

 

 

이제 부터 시작시킨 트랜잭션을 이용하여 여러가지 작업을 해보자.

우선 아래는 작업에 활용할 도메인 클래스 Member.java 의 소스 코드이다.

(아직 lombok 은 적용시키지 않았다.)

 

- Member.java

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 임을 알리기 위해 클래스 시작 전에 @Entity 어노테이션을 달아주고, 데이터의 키값으로 지정하려는 필드위에 @Id 어노테이션을 달아줌으로서 해당 필드를 Entity 의 키 값으로 인식시킨다.

 

이제부터 생성한 트랜잭션을 활용하여 데이터 삽입, 수정, 삭제 등의 작업을 수행해보자.

우선 오류가 생길것을 대비하기 위해 try - catch 문을 활용한다.

try{
	tx.commit(); // 작업이 정상적으로 완료되었을 경우 트랜잭션을 커밋한다.
} catch(Exception e){
	tx.rollback(); // 오류가 생겼을 경우 트랜잭션을 롤백한다.
} finally {
	em.close();
    /* EntityManager 가 내부적으로 데이터베이스 컬렉션과 연동하여 동작하므로
       사용이 끝나면 꼭 닫아줘야 한다. */
 }

 

이제 여기서 앞서 작성한 Member 클래스를 통해 데이터 삽입 작업을 수행해보자.

 - 데이터 삽입

try{
	
    // 데이터 삽입
    Memeber member = new Memeber(); // 객체 생성
    member.setId(1L); // id 필드값 설정
    member.setName("HelloA"); // name 필드 값 설정
    
    em.persist(member); // 데이터 삽입한 객체 저장
    tx.commit(); 
} catch(Exception e){
	tx.rollback(); 
} finally {
	em.close();
 }

 

이제 위의 데이터가 잘 삽입 되었는지 알아보기 위해 해당 데이터를 id 값을 통해 조회하는 코드를 작성해보자.

(굳이 코드로 작성할 필요 없이 H2 console 을 실행시켜서 목록에 있는 생성한 데이터베이스 테이블을 클릭한 후, 나타나는 SELECT 문을 실행시키면 데이터베이스에 데이터가 잘 삽입되어 있는지 확인해볼 수 있다. )

 

- 데이터 조회

try{
	
    //데이터 조회
    Member findmember = em.find(Memeber.class, 1L); 
    /* EntityManager 클래스의 find 메소드는 데이터를 찾고자 하는 도메인 객체의 클래스 이름과 
       해당 데이터의 키값을 변수로 받아서 데이터를 찾는다. */
    System.out.println("findMember.id = " + findmember.getId()); // 찾은 데이터 출력
    System.out.println("findMember.name = " + findmember.getName());
    tx.commit(); 
} catch(Exception e){
	tx.rollback(); 
} finally {
	em.close();
 }

위 소스코드와 같이 EntityManager 클래스의  find 메소드를 통해 찾고자 하는 객체의 데이터를 가져올 수 있다.

 

다음은 데이터 삭제이다.

데이터 삭제의 경우 데이터 조회에서 찾은 데이터를 EntityManager 클래스의 remove 메소드를 통해 삭제 시킬 수 있다.

삽입과 달리 persist 메소드를 사용하지 않는다.

 

- 데이터 삭제

try{
	
    //데이터 조회
    Member findmember = em.find(Memeber.class, 1L); 
    
    em.remove(findmember); // find 메소드를 통해 찾은 데이터를 EntityManager 의 remove 메소드를 통해 제거한다.
    tx.commit(); 
} catch(Exception e){
	tx.rollback(); 
} finally {
	em.close();
 }

 

이제 삽입 되어있는 데이터를 수정해보자.

그런데 여기서 데이터를 수정하려면 일단 데이터베이스에 수정할 데이터가 들어가 있어야 하는데, 위에서 데이터베이스에 있던 데이터를 삭제했기 때문에 데이터 삽입 과정을 다시 한번 거친 다음에 데이터 수정 작업을 진행해보자.

 

위에 작성해놓은 코드대로 데이터 삽입 작업을 무사히 마쳤다면 아래와 같은 코드로 해당 데이터를 수정해 줄 수 있다.

데이터 수정 또한 위의 데이터 삭제와 같이 persist 메소드를 사용하지 않아도 된다.

 

- 데이터 수정

try{
	
    //데이터 수정
    Member findmember = em.find(Memeber.class, 1L); 
    findmember.setName("HelloJPA"); // name 필드 값 수정
    
    tx.commit(); 
} catch(Exception e){
	tx.rollback(); 
} finally {
	em.close();
 }

 

그런데 여기서 드는 의문점이 있다.

데이터 삭제 때는 그렇다 쳐도, 왜 데이터 수정에서 EntityManger 클래스의 persist 메소드를 사용하지 않아도 괜찮을까?

 

JPA 를 활용한 프로그래밍에 있어서 JPA 를 통해 가져온 Entity 는 JPA 가 관리를 해준다.

여기서 JPA 는 Entity 를 관리해주는 동안 데이터가 변경 되었는지 그렇지 않은지를 트랜잭션이 커밋되는 시점에 체크를 하게 되는데, 만약 변경점이 있을 경우 Update query 를 만들어서 데이터베이스에 전달해준다.

그렇게 쿼리가 전달되고 나면 그 이후에 트랜잭션이 커밋된다.

 

한 마디로 말해 JPA 가 데이터 변경을 감지할 경우 알아서 update query 를 데이터베이스에 전달해주기 때문에 굳이 EntityManager 클래스의 persist 메소드를 통해 수정한 데이터를 저장해두지 않아도 괜찮은 것이다.

 

지금까지 JPA 를 이용한 기본적인 데이터 삽입, 수정, 조회, 삭제 과정을 알아보았다.

그렇다면 이번엔 데이터를 조회하는데 있어 특정 조건을 걸고 싶다면 어떻게 해야할까?

모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능한데, 애플리케이션이 필요한 데이터만 DB 에서 불러오려면 결국 검색 조건이 포함된 SQL 이 필요하다.

 

여기서 JPA 를 이용하여 특정 조건을 만족하는 데이터를 검색하려면 JPQL 을 사용하면 된다.

 

JPA 를 사용하면 Entity 객체를 중심으로 개발을 하게 되는데 여기서 해당 객체를 검색할 때 SQL 처럼 테이블을 대상으로 검색하는 것이 아닌 Entity 객체를 대상으로 검색 할 수 있게끔 만들어진 언어가 JPQL 이다. 

 

- JPQL : JPA 에서 제공하는 SQL 을 추상화한 객체지향 쿼리 언어로 SQL 문법과 유사한 점이 있다.

- JPQL 과 SQL 간의 차이점 : SQL 은 데이터베이스 테이블을 대상으로 쿼리를 실행하는데 반해, JPQL 은 Entity 객체를 대상으로 쿼리를 실행한다.(테이블이 아닌 객체를 대상으로 검색하는 객체 지향 쿼리이다.)

- SQL 을 추상화한 것이므로 특정 데이터베이스 SQL 에 의존하지 않는다.

 

- 페이징을 이용한 JPQL 조건 검색

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

 

위의 코드에서 createQuery 메소드를 이용해 JPQL 언어를 작성하여 Member Entity 를 대상으로 쿼리를 전달한다.

여기서 setFirstResult(5), setMaxResults(8) 과 같은 메소드를 통해 "5번째 위치부터 최대 8개의 데이터 검색" 이라는 조건을 부여함으로서 페이징 기능을 구현한다.

 

위의 createQuery 메소드 에서 작성된 JPQL 문이 전달됨으로서 hibernate 를 통해 작성된 조건이 붙어있는 조회에 대한 SQL 문은 아래와 같다.

select
            member0_.id as id1_0_,
            member0_.name as name2_0_ 
        from
            Member member0_ limit ? offset ?

 위의 SQL 쿼리를 잘 살펴보면 from 문에서 limit, offset 과 같이 hibernate 에서만 사용되는 페이징 기능에 대한 dialect(방언)을 사용하면서, 검색하고자 하는 필드들을 모두 나열하여 검색하고 있다.

 

그런데 만약 여기서 JPQL 이 아닌 위에서 작성한대로 SQL 형식으로 데이터베이스 테이블을 대상으로 직접 조건 검색을 한다고 가정했을때

검색해야 하는 테이블의 필드 숫자가 굉장히 많다면 그 많은 필드값들을 하나하나 모두 열거 해야 하기 때문에 코드가 굉장히 길어질 것이다.

 

그러나 JPQL 을 이용하면 createQuery 내부에 작성된 "select m from Member as m" 쿼리와 같이 객체를 대상으로 검색하는 단 한 줄의 쿼리만으로 조건 검색을 완료할 수 있게 된다.

 

 

이제 모든 트랜잭션 및 작업이 끝나 실제 애플리케이션이 완전히 종료되면 EntityManagerFactory 를 완전히 닫아줘야 한다.

emf.close();