JPA 영속성 컨테이너에서 엔티티 사용하기

web/JPA|2018. 10. 11. 20:43

JPA에서 사용하는 영속성 컨테이너에서 사용되는 엔티티에 대해 정리해보자.


엔티티 생명주기

영속성 컨테이너(Persistent Context)에서 존재하는 엔티티의 생명주기를 정리해보자. 
엔티티는 4가지 상태가 존재한다.

이름 

특징 

 비영속

 영속성 컨텍스트와 전혀 관계가 없는 상태 (엔티티 객체가 생성만 되고 컨텍스트와 아무런 연관이 없는경우)

 영속

 영속성 컨텍스트에 저장된 상태 (엔티티 매니저를 통해서 영속성 컨텍스트에 저장되고 영속성 컨텍스트가 관리한다.)

 준영속

 영속성 컨텍스트에 저장되었다가 분리된 상태 (영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않으면 준영속 상태가 된다. close(), clear(), detach() 메소드를 사용하면 준영속 상태가 된다.

 삭제

 삭제된 상태 (remove() 메소드를 통해 영속성 컨텍스트와 데이터베이스에서 엔티티를 제거한다.)


엔티티 조회

- 영속성 컨텍스트 매니저는 내부에 캐시를 가지고 있는데 이를 1차캐시라고 한다. 영속 상태에 들어간 엔티티는 1차 캐시에 먼저 저장되고 키는 엔티티의 @Id가 사용되고 값은 엔티티이다. entityManager.persist(member); 를  하게 되면  영속성  컨텍스트에  엔티티가 1 캐시에 들어가게 된다.

- find(Class<T> entityClass, Object primaryKey)를 통해 데이터를 조회하는데 파라미터에서  첫번째는  엔티티 클래스 번째  파라미터는  키의  값이다.

1
2
3
4
5
6
7
8
9
Member member = new Member();
member.setId("member1");
member.setAge(30);
 
// 1차 캐시에 저장
em.persist(member);
 
// 1차 캐시에서 조회
Member findMember = em.find(Member.class, "member");
cs


- 데이터 조회  1 캐시 내부에 데이터가 존재하면 데이터베이스를 뒤지지 않고 1차캐시에서 데이터를 가지고온다. 만약 1차 캐시에 데이터가 없으면 데이터를 DB에서 조회한 후 반환하면서 1차캐시에 삽입한다. 다음부터는 1차 캐시에서 동일한 데이터가 있으면 그곳에서 가져온다.

- 영속성 컨텍스트에서 꺼낸 같은 키의 데이터는 항상 같다. (meber1 == member1)


엔티티 삽입

Persistence Context에 엔티티가 들어가고 1차 캐시에 저장되고 데이터베이스에 값이 저장되기 위해서내부에 INSERT SQL을 내부 쿼리 저장소에 저장해두고 커밋되는 순간에 데이터 베이스에서 처리한다. 이 과정을  쓰기지연(transactional write-behind)이라 한다.

1
2
3
4
5
6
Member member = new Member();
member.setId("member1");
member.setAge(30);
 
// 1차 캐시에 저장 (이 부분 까지는 데이터가 저장되지 않는다.)
em.persist(member);
cs


엔티티 수정
기본적으로 SQL 쿼리로 업데이트 진행 시 수정되는 필드가 많으면 그만큼 쿼리가 많아지고 복잡해진다.

1
2
3
4
5
6
7
8
9
10
11
update
    member
set
    name = 'cjung',
    AGE = 22,
    ADDRESS = 'Seoul'
    ..
    ..
    ...
WHERE
    id = 'weduld'
cs

JPA로 기존 데이터를 수정하려고 할 때는 단순하게 데이터를 조회하고 데이터를 변경한 뒤에 commit만 하면된다. 추가적으로 update() 명령이 필요가 없다. 왜냐하면 데이터베이스가 엔티티의 변경사항을 자동으로 감지하는 dirty checking을 하기 때문이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
 * springboottest
 *
 * @author wedul
 * @since 03/10/2018
 **/
@Service
@Slf4j
public class MemberServiceImpl implements MemberService {
 
  @PersistenceContext
  private EntityManager entityManager;
 
  @Override
  @Transactional
  public void jpaService() {
 
    try {
      login(entityManager);
    } catch (Exception ex) {
      log.error(ex.getMessage());
    }
  }
 
  private void login(EntityManager em) {
    String id = "wedul";
        
    // 사용자 찾기
    Member findMember = em.find(Member.class, id);
 
    member.setUserName("weduls");
    member.setAge(2);
 
    // 수정
    findMember.setAge(28);
  }
}
cs

위에 작업이 종료되면 UserName과 Age만 수정될 것 같지만 전체 모든 필드가 업데이트 된다. 모든 필드가 업데이트 되어서 데이터 전송량이 문제가 될 수 있지만 장점도 있다. 데이터베이스는 동일한 쿼리에 대해서는 파싱된 쿼리를 재사용하기 때문에 바인딩된 데이터만 바뀐 쿼리가 실행되기 때문에 성능적 효과가 있을 수 있다.

만약 수정된 부분만 업데이트 하고 싶으면 @DynamicUpdate 어노테이션을 붙히면 된다. Insert에서도 null이 아닌 필드만 삽입 하고 싶은 경우에는 @DynamicInsert를 사용할 수 있다.


엔티티 삭제

엔티티를 삭제하려면 업데이트와 동일하게 데이터를 조회하고 remove 명령어를 사용하여 엔티티를 삭제할 수 있다. 입력과 마찬가지로 바로 삭제하는 것이 아니라 삭제 쿼리를 모으는 저장소에 보관했다가 커밋을 하게 되면 수행된다.



Flush

플러시는 영속성 컨텍스트의 변경내용을 데이터베이스에 반영한다. 영속성 컨텍스트의 내용을 데이터베이스에만 반영할 뿐 컨텍스트에서 지우는 것은 아니다. 플러시가 호출되는 방법은 세가지가 있다.
1. flush() 메서드를 직접 호출해서 강재로 flush한다.
2. 트랜잭션이 커밋될때 자동으로 flush()가 호출된다.
3. JPQL 쿼리 실행시 JPA와 다르게 flush가 바로 호출된다.


옵션

- FlushModeType.AUTO : 커밋과 쿼리를 실행할 때 기본 옵션
- FlushModeType.COMMIT : 커밋할 경우에만 플러시


상태

연속성 상태를 준영속 상태로 변경하는 방법 (준영속 상태는 연속성 컨텍스트로 부터 엔티티가 분리된 상태로 더이상 관리 하지 않는 것을 의미한다.)

em.detach(entity) : 특정 엔티티만 준영속 상태로 전환 
-> 1차 캐시와 쓰기 지연 SQL 저장소에서 해당 엔티티가 관리하는 모든 정보 제거

em.clear() : 영속성 컨텍스트를 완전히 초기화
em.close() : 영속성 컨텍스트를 종료
=> 개발자가 준영속성 상태로 만들일이 거의 없다.

다시 준영속 또는 비용속 상태에서 영속 상태로 바꾸기 위해서 merge 메스드를 이용하면 된다. merge는 준영속 엔티티를 새롭게 병합된 영속 엔티티로 반환해준다. merge를 호출할때 넘긴 파라미터에 엔티티의 식별자의 값으로 찾는 엔티티가 없으면 데이터베이스에서 조회하고 만약 없으면 새로운 엔티티를 생성해서 병합한다.

1
2
3
4
@PersistenceContext
private EntityManager entityManager;
 
Member member = em.merge(member);
cs


댓글()

JPA persistence 설정 및 Entity manager 설명

web/JPA|2018. 10. 10. 22:29

JPA를 사용하기 위해서는 persistence.xml을 이용하여 사용 설정을 해야한다.

persistence-unit에 이름을 설정하고 각종 데이터베이스를 설정한다.  구조는 다음과 같이 되어있다.

1
2
3
4
5
<persistence-unit name="wedulpos">
 <properties>
   <!-- 드라이버, 연결정보 및 dialect 설정>
 </properties>
</persistence-unit>
cs


그리고 부가적인 속성으로 hibernate 속성을 설정해줄 수 있다. 해당 속성은 하이버네이트 전용 속성이다.

hibernate 속성

hibernate.show_sql : 하이버네이트가 실행한 SQL 출력
hibernate.format_sql : 하이버네이트가 실행한 SQL 보기 쉽게 정렬한다.
hibernate.use_sql_comments :  쿼리를 출력할  주석도 함꼐 출력한다.
hibernate.id.new_generator_mappings : JPA 표준에 맞춘 새로운  생성 전략을 사용.



Spring boot에서 persistence.xml

지금까지는 자바 프로젝트에서 설정을 하였다면 스프링 부트에서는 어떨까? 동일하게 persistence.xml을 만들고 해야할까? 자바 스프링 부트에서는 starter-data-jpa를 로드하면 별도의 persistence.xml을 사용할 필요가 없다. 하지만 별도의 persistence.xml을 설정하고 싶다면 이 부분을 참고하자.
 https://docs.spring.io/spring-boot/docs/current/reference/html/howto-data-access.html#howto-use-traditional-persistence-xml 

하지만 굳이 힘들게 설정하고 싶지 않다면 application.properties에 설정을 입력함으로써 사용할 수 있다.http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html#boot-features-connect-to-production-database


EntityManagerFactory와 EntityManager
엔티티 매니저 팩토리는 엔티티 매니저를 만드는 공장으로써 이 객체를 계속 만들어서 사용하면 비용이 크다. 그래서 엔티티 매니저 팩토리는 하나를 생성하여 공유하고 엔티티 매니저는 절대 여러 스레드가 동시에 접근해서 사용하면 안된다. 엔티티 매니저는 생성되었다고 바로 커넥션이 생기는 것이 아니다. 연결이 필요하고자 할때 연결이 진행되는 lazy방식으로 진행되고 트랜잭션이 시작될 때 커넥션이 획득된다.


Persistence Context(영속성 컨텍스트)
- 영속성 컨텍스트는 엔티티를 영구 저장하는 환경으로써 EntityManager로 엔티티를 저장하거나 조회하면 EntityManager는 영속성 컨텍스트에 엔티티를 보관하고 관리한다.


1
2
3
4
5
@PersistenceContext
private EntityManager entityManager;
 
// 등록
em.persist(member);
cs


영속성 컨텍스트는 엔티티를 식별자 값으로 구분한다. 그래서 해당 엔티티는 @Id라는 식별자가 있어야한다 또한 영속성 컨텍스트에서 엔티티가 반영되려면 트랜잭션이 커밋되는 순간에 flush되는 순간에 처리한다.


다음번에는 영속석 컨텍스트를 사용해서 엔티티를 조회, 쓰기 등을 처리해보자.




댓글()

JPA 기본 어노테이션 설명

web/JPA|2018. 10. 6. 11:04

JPA에서 사용되는 기본적인 어노테이션 몇개를 정리해보자.


@Entity

- 클래스와 테이블과 매핑한다고 JPA에게 알려준다. 이렇게 @Entity가 사용될 클래스를 엔티티 클래스라고 한다.


@Table

- 엔티티 클래스에 매핑할 테이블 정보를 알려준다. (이 어노테이션을 생략하면 클래스 이름을 테이블정보로 매핑한다.)


@Id

- 엔티티 클래스의 필드를 테이블에 기본키로 매핑한다. (데이터베이스는 엔티티를 구별할때 이 키값으로 구분한다.)


@Column

- 필드를 컬럼에 매핑한다.


매핑 정보가 없는 필드

- @Column을 생략하면 필드명을 사용해서 컬럼명과 매핑하게 된다. 만약 대소문자를 데이터베이스가 구분할 경우에는 꼭 위에 @Column어노테이션을 사용해서 진행해야한다.


#Dialect(방언)

- 데이터베이스마다 서로 다른 특징(Mysql의 limit, Oracle의 Rownum....)들을 JPA에서 방언(Dialect)라고 한다. 이렇게 데이터베이스 벤더사마다 서로 다른 기능을 사용해서 JPA를 사용하면 나중에 데이터베이스를 바꿀때 문제가 발생하기 때문에 문제가 없는 방언 클래스를 사용해야한다. 


위의 어노테이션이 적용된 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.wedul.springboottest.member.dto;
 
import lombok.Data;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
 
/**
 * springboottest
 *
 * @author wedul
 * @since 03/10/2018
 **/
@Entity
@Data
@Table(name = "MEMBER")
public class Member {
 
  @Id
  @Column(name = "ID")
  private String id;
 
  @Column(name = "NAME")
  private String userName;
 
  // 매핑 정보가 없는 필드
  private int age;
 
}
 
cs


댓글()

JPA(Java Persistence Api)의 장점과 단점 및 사용해야 하는 이유.

web/JPA|2018. 10. 6. 10:56

국내에서는 Mybatis를 사용하지만 대기업과 여러 스타트업에서는 JPA를 사용하고 있는 추세이다. 그리고 세계적으로 JPA 특히 ORM 프레임워크인 하이버네이트 사용률이 더 높다.

그래서 더 자세한 부분을 이해하기 위해서 공부를 시작했다. 공부 시작하기전에 왜 JPA가 좋은지 정리해봤다.


생산성

- 개발자가 일일히 CRUD용 쿼리를 작성해줘야하던 Mybatis와 같은 Mapper방식은 컬럼이 추가되거나하면 수정해주어야하는 부분이 상당히 많았다. 이로 인해서 자바를 사용하지만 객체중심 개발이 아니라 데이터베이스 흐름으로 개발을 하게되는 문제가 있다. JPA를 사용하게 되면 쿼리를 직적 생성하는 것이 아니고 만들어진 객체로 데이터베이스를 다루기 때문에 객체 중심으로 개발을 진행할 수 있다. 


유지보수

- SQL을 직접적으로 작성하지 않고 엔티티 필드가 되는 객체를 다뤄서 데이터베이스를 동작시키기 때문에 유지보수가 더욱 간결하다. 왜냐하면 쿼리가 수정되면 그에 따라서 그를 담을 DTO 필드도 모두 변경이 되야 하지만 JPA를 사용하게 되면 단순히 엔티티 클래스 정보만 변경하면 쉽게 관리가 가능하기 때문이다.


성능

- 일반적인 Spring의 encache 기능처럼 동일한 쿼리에 대한 캐시 기능을 사용하기 때문에 더욱 높은 성능적 효율성을 경험할 수있다. 


RDBMS 종류와 무관한 코딩

- 객체 중심으로 동작하기 때문에 Oracle, Mysql, Mssql과 같이 서로 다른 벤더사 데이터베이스를 사용하려고 할 때 문법을 바꿔줘야하는 수고를 줄일 수 있다. 


제약사항 및 단점

- JPA는 통계처리와 같이 복잡한 쿼리보다는 실시간 처리용 쿼리에 더 최적화되어 있다. 물론 JPA에서 제공하는 Native query기능을 사용할 수 있지만 통계처럼 복잡하고 미세하게 쿼리 작업이 필요하다면 Mybatis와 같은 Mapper 방식을 사용하는 것이 더 효율적일 수 있다. Spring에서 JPA와 Mybatis를 혼용해서 사용할 수 있기 때문에 필요에 따라 적절한 방식으로 선택해가면서 사용하면 될 것 같다.



댓글()
  1. 이사작전 2019.01.20 21:26 댓글주소  수정/삭제  댓글쓰기

    좋은 포스팅 감사합니다.