Spring/JPA 심화

JPA : 객체와 관계형 DB의 패러다임 불일치를 해결하는 방법

JuNo_12 2025. 6. 25. 19:53

왜 JPA가 필요한가?

객체지향과 관계형 데이터베이스의 근본적인 차이

객체지향 프로그래밍을 하다 보면 자연스럽게 객체 모델링을 하게 됩니다. 하지만 데이터를 저장할 때는 관계형 데이터베이스를 사용해야 하죠. 여기서 패러다임 불일치 문제가 발생합니다.

// 우리가 원하는 객체지향적 코드
class Member {
    private String name;
    private Team team;  // 객체 참조
    
    public Team getTeam() {
        return team;    // 객체 그래프 탐색
    }
}

// 하지만 실제로는...
class MemberDAO {
    public Member findMember(Long memberId) {
        // SQL에 종속적인 코드
        String sql = "SELECT m.*, t.* FROM MEMBER m JOIN TEAM t...";
        // 결국 DAO를 확인해야 어떤 데이터가 로딩되는지 알 수 있음
    }
}

패러다임 불일치의 구체적인 문제들

1. 객체 그래프 탐색의 한계

문제상황:

// 이 코드를 믿을 수 있나요?
Member member = memberDAO.findMember(memberId);
Team team = member.getTeam();        // NPE 위험!
Order order = member.getOrders();    // 데이터가 있을까?

객체는 참조를 통해 연관된 객체로 자유롭게 탐색할 수 있어야 하는데, SQL에 종속되어 있어서 실제로는 DAO 클래스를 확인해야만 어떤 데이터가 로딩되는지 알 수 있습니다.

 

2. 객체 동일성 vs 동등성

문제상황:

// 같은 회원을 두 번 조회했을 때
Member member1 = memberDAO.findMember(1L);
Member member2 = memberDAO.findMember(1L);

System.out.println(member1 == member2);        // false 
System.out.println(member1.equals(member2));   // true (동등성)
  • 동일성(Identity): 객체 인스턴스의 주소값이 같음 (== 비교)
  • 동등성(Equality): 객체 내부의 값이 같음 (equals() 비교)

객체지향에서는 같은 식별자의 객체는 동일한 인스턴스여야 하지만, 기존 방식으로는 보장되지 않습니다.


JPA가 해결하는 방법

1. 신뢰할 수 있는 객체 그래프 탐색

// JPA 사용 시 - 연관된 객체를 '신뢰'할 수 있음
Member member = entityManager.find(Member.class, memberId);
Team team = member.getTeam();        // 안전!
Order order = member.getOrders();    // 지연 로딩으로 필요시 조회

지연 로딩(Lazy Loading): 실제 객체를 사용하는 지점까지 데이터베이스 조회를 미루는 기법

 

2. 동일성 보장

// JPA는 같은 트랜잭션일 때 같은 객체 조회를 보장
Member member1 = entityManager.find(Member.class, 1L);
Member member2 = entityManager.find(Member.class, 1L);

System.out.println(member1 == member2);  // true! 동일성 보장 ✅

JPA란 무엇인가?

Java Persistence API - 자바 진영의 ORM 기술 표준

// 마치 컬렉션에 저장하듯이!
List<Member> members = new ArrayList<>();
members.add(member);    // 저장

Member findMember = members.get(0);  // 조회

// JPA도 이와 유사하게
entityManager.persist(member);      // 저장
Member findMember = entityManager.find(Member.class, 1L);  // 조회

ORM 프레임워크에 저장하면 마치 컬렉션에 저장하는 것처럼 자연스럽게 사용할 수 있습니다.


JPA 핵심 구조

1. EntityManagerFactory - 애플리케이션 전체에서 단 한 번 생성

// 애플리케이션 시작 시 한 번만 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

왜 단 한 번만?

  • JPA 동작을 위한 기반 객체 생성 비용이 큼
  • JPA 구현체에 따라 데이터베이스 커넥션 풀도 생성
  • 스레드 간 공유와 재사용 필요

 

2. 데이터베이스 방언(Dialect) 설정

# MySQL 사용 시
hibernate.dialect=org.hibernate.dialect.MySQL8Dialect

# Oracle 사용 시  
hibernate.dialect=org.hibernate.dialect.Oracle12cDialect

동일한 JPA 코드로 다양한 데이터베이스 사용 가능!


JPQL vs SQL

객체지향 쿼리 언어 JPQL

-- SQL: 데이터베이스 테이블 대상
SELECT m.name, m.age FROM Member m WHERE m.age > 18

-- JPQL: 엔티티 객체 대상 (클래스와 필드)
SELECT m.name, m.age FROM Member m WHERE m.age > 18

핵심 차이점:

  • SQL: 데이터베이스 테이블과 컬럼을 대상으로 함
  • JPQL: 엔티티 클래스와 필드를 대상으로 함
  • JPQL은 데이터베이스 테이블을 전혀 알지 못함

영속성 컨텍스트의 핵심 기능

1. 쓰기 지연 (Write-Behind)

entityManager.persist(memberA);  // INSERT SQL을 DB로 보내지 않음
entityManager.persist(memberB);  // INSERT SQL을 DB로 보내지 않음
entityManager.persist(memberC);  // INSERT SQL을 DB로 보내지 않음

// 커밋하는 순간 한 번에 전송
transaction.commit();  // 모든 INSERT SQL을 한 번에 전송

 

2. 변경 감지 (Dirty Checking)

// 조회
Member member = entityManager.find(Member.class, 1L);

// 수정 - 별도의 update 메서드 호출 불필요!
member.setName("변경된 이름");

// 커밋 시점에 자동으로 UPDATE SQL 실행
transaction.commit();

엔티티 작성 규칙

필수 조건들

@Entity
public class Member {
    @Id
    private Long id;
    
    private String name;
    
    // 1. 기본 생성자 필수 (public 또는 protected)
    protected Member() {}
    
    // 2. 필드에 final 사용 금지
    // private final String name; // ❌ 불가능
    
    public Member(String name) {
        this.name = name;
    }
}

왜 이런 제약이 있을까?

  • JPA 구현체가 동적으로 객체를 생성하고 조작해야 하기 때문
  • 프록시 객체 생성 시 기본 생성자 필요
  • 리플렉션을 통한 필드 접근 필요

객체 그래프 탐색

참조를 통한 연관관계 탐색

@Entity
public class Member {
    @ManyToOne
    private Team team;
    
    @OneToMany(mappedBy = "member")
    private List<Order> orders;
}

// 자유로운 객체 그래프 탐색
Member member = entityManager.find(Member.class, 1L);
Team team = member.getTeam();           // 연관된 팀 조회
List<Order> orders = member.getOrders(); // 연관된 주문 목록 조회

결론: 객체 모델링을 유지하자

기존 방식으로는:

객체 모델링 시작 → SQL 중심 개발 → 점점 데이터 중심 모델로 변화

 

JPA 사용 시:

객체 모델링 시작 → JPA가 패러다임 불일치 해결 → 정교한 객체 모델링 유지 ✅

JPA는 단순히 데이터 저장 기술이 아니라, 객체지향 프로그래밍을 제대로 할 수 있게 도와주는 도구입니다.


다음 글에서는 테이블 간 연관관계에 대해 알아보겠습니다.

 

참고자료

  • 김영한, "자바 ORM 표준 JPA 프로그래밍", 에이콘출판사