1. 엔티티 매니저 팩토리 생성 과정
Persistance 인스턴스가 설정정보를 조회하고 name과 같은 EntityManagerFactory를 생성한다.
엔티티 매니저 팩토리 생성과정
- Persistance 인스턴스가 설정 정보를 로딩한다. Hibernate가 설정 정보에서 영속성 유닛을 찾아서 리소스를 준비한다.
영속성유닛
영속성 유닛(Persistence Unit)은 엔티티 매니저를 구성하는 근간이 되는 단위이다. 예를 들어, 어떤 데이터베이스와 연결하고, 어떤 엔티티들을 관리하며, 어떤 속성으로 동작할지 등을 정의한다.(설정파일에 영속성 유닛1 영속성 유닛 2 이렇게 여러개가 존재할 수 있는 것, 그리고 각 유닛마다 DB드라이버, 방언, DB주소, id, pwd 설정이 있음) JPA에서는 이 영속성 유닛을 기준으로 트랜잭션을 실행하고, 엔티티 매니저를 생성한다.
- 엔티티를 스캔하고 메타 모델을 만든다.
메타모델
메타모델을 사용하면 문자열로 경로를 지정하는 대신, 정적 클래스를 통해 접근할 수 있다. 예: "member.name" 대신 Member_.name 이런 식으로 작성 가능하다. 메타모델은 엔티티의 필드를 정적 클래스로 관리하여 타입 안전성을 높인다. 문자열로 경로를 지정하면 오타가 있어도 컴파일 오류가 발생하지 않지만, 메타모델을 사용하면 컴파일 시점에 잘못된 필드명을 바로 잡을 수 있다. 또한 Criteria API와 함께 쓰면 동적 쿼리를 더욱 안전하고 명확하게 작성할 수 있다. Criteria API는 JPA에서 제공하는 코드 기반 쿼리 생성 기능이다. 전통적인 JPQL이나 SQL처럼 문자열로 쿼리를 작성하는 대신, 메서드 체인과 타입 안전한 빌더(CriteriaBuilder)를 사용하여 동적 쿼리를 작성할 수 있게 해준다. 문자열이 아닌 코드로 쿼리를 정의하기 때문에, 오타를 비롯한 오류를 컴파일 시점에 잡을 수 있고, 메타모델과 결합하면 더욱 안전하게 사용할 수 있다.
- 커넥션 풀 라이브러리(HikariCP 등)를 써서 DataSource로 등록(네트워크 소켓 생성, 인증, 설정 교환 등)
- 엔티티 매니저 팩토리를 구현한 SessionFactory를 생성한다.
엔티티 매니저 팩토리는 application전체에서 한번만 생성하고 공유해서 사용한다.
엔티티 매니저는 커넥션과 밀접한 관계가 있으므로, 스레드간에 공유하거나 재사용하면 안된다.
2. 어플리케이션 개발, 트랜잭션 관리, 비지니스 로직
다음은 옛날에 쓰던 방식이다.
//코드가 보기 안좋으면 다크 모드로 확인 바랍니다.
//엔티티 매니저 팩토리 생성
EntityManagerFactory emf = Persistance.createEntityManagerFactory("jpabook");
//엔티티 매니저 생성
EntityManager em = emf.createEntityManager();
//종료
em.close();
emf.close();
//트랜잭션 관리
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
//로직 수행 후...
tx.commit();
} catch (Exception e) {
tx.rollback();
}
//비지니스 로직
//수정
member.setAge(20);
//등록
em.persist(member);
//조회
Member findMember = em.find(Member.class, id);
//목록 조회
List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
//삭제
em.remove(member);
아래는 최신에 스프링과 함께 JPA를 사용할 때 실제로 어떻게 개발하는지에 대한 예시이다.
- JpaRepository 인터페이스 상속
- 주로, 위에 처럼 직접 로직을 구현하기보다, Spring Data JPA 방식이라면 별도 구현 없이도
JpaRepository
인터페이스만 상속하면 기본 CRUD 메서드를 사용할 수 있다.public interface MemberRepository extends JpaRepository<Member, Long> { // 기본 CRUD 및 쿼리 메서드 제공 } //이 인터페이스만 만들고 Service에서 사용하면 된다.
- 주로, 위에 처럼 직접 로직을 구현하기보다, Spring Data JPA 방식이라면 별도 구현 없이도
- 엔티티 매니저 주입
@PersistenceContext
로 Spring이 자동 생성한 엔티티 매니저를 주입받아 사용한다.
- 트랜잭션
- Service에
@Transactional
을 붙이면, Service내의 모든 메서드가 시작될 때 트랜잭션을 시작하고 종료 시점에 커밋(혹은 롤백)한다. 커밋이 되면 더티 체킹을 통해 DB에 반영된다. ※ this를 내부에 호출하는 메서드, public이 아닌 메서드인 경우는 트랜잭션이 적용이 되지 않음. 이는 스프링은 AOP 프록시 기반이기 때문.
- Service에
@Repository
public class MemberRepository {
@PersistenceContext
private EntityManager em;
public Long createMember(String name, int age) {
Member member = new Member();
member.setName(name);
member.setAge(age);
em.persist(member); // 등록
return member.getId();
}
public Member findMember(Long id) {
return em.find(Member.class, id); // 단건 조회
}
public List<Member> findAllMembers() {
return em.createQuery("select m from Member m", Member.class)
.getResultList(); // 목록 조회(JPQL)
}
public void updateMemberAge(Long id, int newAge) {
Member member = em.find(Member.class, id);
if (member != null) {
member.setAge(newAge); // 변경 감지(더티 체킹)
}
}
public void deleteMember(Long id) {
Member member = em.find(Member.class, id);
if (member != null) {
em.remove(member); // 삭제
}
}
}
3. JPA 더티 체킹 (Dirty Checking)
JPA에서 엔터티를 변경할 때 em.persist()
또는 em.merge()
를 호출하지 않아도 자동으로 변경 사항이 감지되어 데이터베이스에 반영되는 기능이다. 이를 더티 체킹 (Dirty Checking) 또는 자동 변경 감지라고 한다.
// 더티 체킹 예제 코드
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa-example");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin(); // 트랜잭션 시작
// 1. 엔터티 조회
Member member = em.find(Member.class, 1L);
// 2. 엔터티 값 변경
member.setName("New Name"); // 변경 감지
// 3. 트랜잭션 커밋 (자동으로 UPDATE SQL 실행)
tx.commit();
em.close();
emf.close();
jpa의 기본 전략은 update시 엔티티의 모든 필드를 업데이트한다.
JPA 구현체(특히 Hibernate)는 엔티티가 변경될 때, 기본적으로 모든 필드를 포함하는 UPDATE 쿼리를 생성한다. 이러한 전략을 쓰는 이유는 크게 두 가지이다:
- 쿼리 재사용(쿼리 캐싱)으로 인한 성능 이점
- 쿼리 구조가 동일하므로 DB에서 미리 파싱해둔 쿼리를 재사용할 수 있다.
- DB는 동일한 쿼리가 오면 파싱, 컴파일 과정을 생략하고 실행 계획(Execution Plan)을 재활용한다.
- 이 때문에 많은 트래픽이 몰리는 상황에서 오버헤드를 줄일 수 있다.
- 로직 단순화
- 엔티티 변경 시 “어떤 필드가 바뀌었는지” 매번 동적으로 체크해서 부분 UPDATE 쿼리를 생성하려면 추가 로직과 자원 소모가 크다.
- Hibernate 입장에서는 엔티티 변경 시점마다 항상 동일한 형태의 UPDATE 쿼리를 만들어내는 것이 구현이 단순하고 안정적이다.
- 특히 필드 수가 많지 않다면, 이런 방식이 충분히 합리적인 경우가 많다.
부분 UPDATE가 필요한 경우
@DynamicUpdate
: Hibernate 제공 어노테이션으로, 실제 변경된 필드만 포함하는 UPDATE 쿼리를 생성하도록 한다. 필드 수가 매우 많고, 자주 바뀌지 않는 필드들이 있을 때 부분 UPDATE가 더 이득일 수 있다. 보통 컬럼이 대략 30개 이상이 되면 동적 수정 쿼리가 더 빠르다고 한다. 가장 정확한 것은 직접 환경에서 테스트해보는 것. 하지만 컬럼이 30개라면 테이블 설계 책임이 분리되지 않은 것일 확률이 높음.
'백엔드' 카테고리의 다른 글
[Spring JPA] 1. JPA 이야기 (김영한 JPA 교재) (0) | 2025.03.05 |
---|---|
JPA Entity(JPA 공식문서 정리 - 1) (1) | 2025.03.03 |
MySQL 쿼리 최적화: 테이블 스캔, 인덱스 스캔, 옵티마이저, 인덱스 힌트, EXPLAIN ANALYZE (0) | 2025.02.25 |
Included Columns(SQL-server) (0) | 2025.02.21 |
인덱스 조각화, 실행계획, 인덱스 scripts (0) | 2025.02.21 |