[자바 ORM 표준 JPA 프로그래밍] 1주차 스터디 (1)

박서영·2026년 3월 14일

1장. JPA 소개

JPA를 사용하면, CRUD SQL을 작성할 필요가 없고, 조회된 결과와 객체를 매핑하는 일도 자동으로 JPA에서 처리해준다는 장점이 있음. 또한 애플리케이션을 SQL이 아닌 객체 중심으로 개발할 수 있어 생산성과 유지보수가 좋아지고, 테스트 작성이 편리해진다.

JPA는 자바 진영에서 만든 ORM 기술 표준으로, ORM은 Object-Relational Mapping의 약자로 객체 지향 프로그래밍 언어의 객체(클래스)와 관계형 데이터베이스의 테이블을 자동으로 매핑해, SQL 쿼리 대신 코드 수준에서 데이터를 조작할 수 있게 해주는 기술을 말한다.
실제로 이전에 스프링에서 JPA를 찍먹했을 때도, SQL 쿼리를 하나도 몰랐는데 사용하는데 무리가 없었다.

1.1 SQL을 직접 다룰 때의 문제

데이터베이스에서 데이터를 관리하기 위해서는 SQL을 사용해야하는데 이때 자바로 작성한 애플리케이션은 JDBC API를 사용해 SQL을 데이터베이스에 전달하고 데이터베이스로 부터 필요한 데이터를 반환받는다.

다만, 데이터베이스는 객체 구조와 달리 데이터 중심의 구조를 가지기 때문에 객체를 데이터베이스에 직접 저장하거나 조회할 수는 없다. 그렇기에 이 다른 구조에 맞게 각각 변환해주는 부분의 코드가 필요하다.

	ResultSet rs = stmt.executeQuery(sql); //SQL 쿼리 실행
    //아래처럼 쿼리로 가져온 데이터베이스의 정보를 
    //객체에 또 다시 저장해주기 위해 변환해서 객체로 만들어주는 과정이 필요
    String member = rs.getString("MEMBER_ID");
    String name = rs.getString("NAME");
    
    Member member = new Member(member, name);
    
    //등록할 때는 또 객체에서 각 데이터(또는 필드)를 가져와서 데이터베이스에 맞게
    //처리/저장?해주는 부분이 필요 (그리고 이런 부분이 귀찮고, 너무 빈번히 작성이 필요하다)
    String sql = "INSERT INTO MEMBER(MEMBER_ID, NAME) VALUES (?,?)";
    
    pstmt.setString(1, member.getMemeberId());
    pstmt.setString(2, member.getName());
  

이 부분의 단점은 데이터베이스가 정보가 저장되는 구조와 자바에서 사용하는 객체(클래스)의 구조가 다르다는 부분에서 발생한다는걸 새삼 책을 읽으며 느꼈다. 이전에 SQL을 사용해보았을 때도, JPA를 사용해 CRUD를 작성해본 것과 비교하면 중간에 데이터베이스에서 받아온 데이터를 속성 이름에 따라 다시 받아서 형태를 코드로 직접 변화하는 작업을 매번 수행해줘야했다. 그때 강의를 들으면서는 단순히 JPA 쓰면 코드도 안 쓰고 알아서 처리해주니 편하네?라는 생각을 했는데 지금 보니까 이런 구조가 다른 것들을 알아서 처리해주니까 생략할 수 있는 코드들이라는 생각이 든다.

이렇게 코드를 작성하게 된다면 SQL에 의존적으로 개발을 하게 된다. 즉, 고객/서비스의 수정 요청에 맞추어 코드를 수정해야할 때 수정하는 양이 굉장히 많게된다. 이럴 경우 확장성이나 재사용성 측면에서도 좋지 않다.

예로는 데이터베이스에서 관리하는 속성(애트리뷰트)가 하나 늘어났을 때, JPA를 사용하지 않고 있다고 가정하면,

  1. 객체 클래스의 필드 추가
  2. 데이터베이스 수정
  3. 객체-데이터베이스 연결고리 모두 수정 (즉, SQL문을 사용했던 CRUD 관련 부분: 조회/수정/생성 등을 전부 수정해야한다)

아래 작업을 필수적으로 수행해야한다.

외에도 만약 일반적인 조회가 아니라 무언가를 기준으로 조회하거나 객체가 다른 객체와 관계를 맺는 등 실제 서비스에서는 더 다양한 목적/서비스를 위한 기능들이 존재하는걸 고려하면 이런 의존적인 개발은 비효율적이고 피로하다. 수정 과정에서 오류가 발생할 여지가 많고, 관련된 코드들이 체인처럼 연결되어 그 각각을 수정하는 데에 피로와 오류 발생 가능성이 늘어난다. 오류 발생시 또 코드를 다 살펴봐야한다는 점에서 객체에서의 문제인지, DAO에서의 문제인지, SQL/데이터베이스의 문제인지를 각각 보는 피로함 역시 존재한다.

엔티티(Entity)는 비즈니스 요구사항을 모델링한 객체를 이르는 말이다. 고객(Customer)나 팀(Team) 같이 말이다. 만약 SQL에 모든 것을 의존하게 되면 개발자들은 엔티티를 신뢰하고 사용할 수 없고, DAO를 살펴보며 어떤 SQL이 실행되는지와 어떤 객체들이 함께 조회되는지 확인해야한다.

문제점을 정리하면 진정한 의미의 계층 분할의 어려움, 엔티티의 신뢰가 어려움, SQL에 의존적인 개발이라는 문제점이 존재한다.


JPA를 사용하면 객체를 데이터베이스에 저장하거나 관리할 때, 직접 SQL문을 작성하지 않고 JPA에서 제공하는 API를 사용하면 되기에 이런 문제들이 줄어들고 해결된다. JPA를 사용해 CRUD API를 어떻게 짜는지 확인해보면 그 차이가 뚜렷하게 보인다.

  1. 저장
jpa.persist(member); //저장

persist() 메소드를 통해 객체를 데이터베이스에 저장하면, JPA가 객체와 매핑정보를 보고 적절한 INSERT SQL을 생성해 DB에 전달.

  1. 조회
String memeberID = "idString"
Member member = jpa.find(Member.class, memberID); //조회

find() 메소드는 객체 하나를 DB에서 조회하는데, 이때 JPA는 객체와 매핑정보를 보고 적절한 SELECT SQL을 생성해 DB에 전달하고 그 결과로 Member 객체를 생성해 반환.

  1. 수정
Member member = jpa.find(Member.class, memberId);
member.setName("변경이름"); //수정

JPA에서 별도로 수정 메소드를 제공하지는 않지만, 객체를 조회해 값을 변경하면 트랜잭션을 커밋할 때 DB에 적절한 UPDATE SQL이 전달된다.

  1. 연관된 객체 조회
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam(); //연관 객체 조회

코드를 살펴보면 정말 SQL문을 작성하지 않고, 자바 언어에서 만든 객체에서 getter, setter를 사용하듯이 사용하면, 데이터베이스에 알아서 저장/수정이 되며 조회 역시도 JPA에서 알아서 객체 형태로 반환해준다.

1.2 패러다임의 불일치

객체 지향에서는 부모 객체를 상속받거나, 다른 객체를 참조할 수 있는데에 반해, 데이터베이스는 데이터를 중심으로 구조화되어있기에 이런 부분의 차이가 존재한다.

이런 객체 지향 프로그래밍과 데이터베이스의 목표가 다른데에서 오는 차이를 패러다임의 불일치라고 하며, 이 두 불일치를 개발자가 중간에서 해결해야하는 문제가 생긴다.

1. 상속

예시로 객체지향 프로그래밍의 상속을 구현하기 위해서는 데이터베이스 모델링에서는 슈퍼타입 서브타입 관계를 사용해야 유사하게 설계가 가능하다.

ITEM이라는 클래스를 상속받아 ALBUM이라는 클래스가 있다고 하면, ITEM 데이터베이스의 컬럼으로 ITEM_ID(PK)와 DTYPE을 가지고 있어야하며, DTYPE 컬럼을 통해 어떤 자식 테이블관의 관계가 있는지 정의해야한다. 예를 들면 DTYPE이 ALBUM이런식으로.

하지만 이 경우네는 조회나 추가 등의 작업을 할 때 이 둘을 연결해서 추가/조회하는 코드들이 무수히 많이 필요하다.

abstract class Item {
	Long id;
    String name;
}

class Album extends Item {
	String artist;
}

위와 같은 상속관계에서 Album 객체를 저장하기 위해서는 이 객체를 분해해 두 SQL을 만들어야한다.

INSERT INTO ITEM
INSERT INTO ALBUM

이렇게 되면 부모 객체에서 부모 데이터만 꺼내 ITEM용 INSERT SQL문을 작성하고, 자식 객체에서 자식 데이터만 꺼내서 ALBUM용 INSERT SQL을 작성하고, 자식의 타입에 따라 DTYPE도 정해서 넣어주어야한다.

JPA에서의 상속

JPA에서는 이런 불일치 문제를 대신 해결해, 그저 자바에서 객체를 저장하듯이 저장하JPA에서 알아서 두 SQL을 실행하여 위의 예시처럼 저장시켜준다. 조회의 경우에도 역시 JPA에서 알아서 두 테이블을 조인해 필요한 데이터를 조회해 그 결과를 반환해준다.

2. 연관관계

자바에서 객체가 다른 객체를 참조하는 일이 빈번한데, 이 부분은 그와 관련된 구조의 차이이다. 자바가 참조를 통해 접근해서 연관된 객체를 조회하는 반면, 데이터베이스는 외래키를 사용해 연관관계를 가지고, 조인을 사용해 연관 테이블을 조회하는 차이에서 오는 문제이다.

예시로 Member 객체 안에 Team이라는 객체를 참조해 가지고 있다고 하면, 이 참조 필드를 통해 Team이라는 객체가 가진 정보들에 쉽게 접근할 수 있다. 하지만 데이터베이스에서는 Member 테이블에서 MEMBER.TEAM_ID라는 외래키 컬럼을 통해 TEAM 테이블과 관계를 맺고, 외래키를 사용해서 조인을 한 후에야 테이블을 조회할 수 있다.

또한 외래키를 가질 경우에는 하나만으로도 서로서로 조회, 즉 양방향으로도 조인을 할 수 있지만, 객체는 반대로 한 방향의 조회만 가능하다. 이런 불일치를 개발자가 중간에서 변환해주어야하는데, JPA에서 이런 변환하는 노고를 줄여준다.

JPA에서의 연관관계

상속과 마찬가지로 연관관계 역시 JPA에서 알아서 문제를 해결해준다. 참조와 외래키 사이를 서로 적절히 변환하여 SQL문을 작성해 데이터베이스로 전달하거나, 객체 조회 시에도 외래키를 적절히 참조로 변환해 준다.

3. 객체 그래프 탐색

객체 간의 연관관계는 하나가 아니라 다양하고 복잡하게 연결되어있을 수 있다. 예를 들면 위의 그림처럼. 실제 자바에서 코드를 짜다보면 character.getWeapon().getName();과 같이 한 번의 참조가 아니라, 참조한 객체에서의 참조 대상까지 가는 경우가 빈번했었다. 이런 경우는 데이터베이스의 외래키의 외래키같은 참조와 동일한 식으로의 처리는 어려울 것이다.

SQL은 직접 다룰 때 처음 어떤 SQL을 실행하느냐에 따라 객체 그래프를 어디까지 탐색할 수 있을지 정해진다. 그렇기에 어디까지 그래프 탐색이 가능한지 알기 위해 DAO를 통해 SQL을 확인해야하는 의존적인 부분이 존재한다. 따라서 DAO에 관련 조회 메소드를 상황에 따라 여러개 만들어야하는 문제가 생긴다.

JPA에서의 객체 그래프 탐색

JPA에서는 연관 객체를 사용하는 시점에 적절한 SELECT SQL을 실행한다. 즉, 이를 통해 연관된 객체를 신뢰하고 객체 지향에서 사용하듯이 조회해 사용할 수 있다. 이 기능은 실제 객체를 사용하는 시점까지 데이터베이스 조회를 미룬다고해 지연로딩이라고 한다.

4. 비교

데이터베이스는 기본키의 값으로 행을 비교하는 반면 객체는 동일성과 동등성 비교 두 가지가 존재한다.

  • 동일성: == 비교. 객체 인스턴스의 주소를 비교
  • 동등성: .equals()비교. 객체 내부의 을 비교

이런 차이로 만약에 데이터베이스의 같은 행을 다른 객체 변수에 저장하게 되면, 데이터베이스에서는 같은 값인 것이, 객체 프로그래밍에서는 서로 다른 동일성 비교의 결과를 내게된다. 이런 차이 극복을 위해 데이터베이스의 같은 행을 조회할 때마다 같은 인스턴스를 반환하도록 구현하는 것은 하지만 쉽지 않다.

JPA에서의 비교

JPA에서는 따라서 같은 트랜잭션일 때 같은 객체가 조회되는 것을 보장한다.


1.3 JPA란?

앞에서 말했듯 JPA는 자바 진영에서 만든 ORM 기술 표준으로, 자바 ORM 기술에 대한 API 표준 명세서다. ORM은 객체와 관계형 데이터베이스를 매핑해준다는 의미라고 할 수 있다. JPA는 애플리케이션과 JDBC 사이에서 동작하게된다.

앞에서 언급된 패러다임 차이나 무수히 많은 SQL문을 적는 것을 ORM 프레임워크에서 대체해주기에, 객체 측면에서 정교한 객체 모델링이 가능하고, 관계형 데이터베이스 측에서는 데이터베이스에 맞는 모델링을 할 수가 있다.

JPA를 사용하기 위해서는 JPA를 구현한 ORM 프레임워크를 선택해야한다. JPA는 기술에 대한 표준 명세서니 말이다.

JPA를 사용해야하는 이유를 정리해보면 아래와 같다.

  • 생산성: CRUD용 SQL을 직접 작성하지 않아도 되며, CREATE TABLE 같은 DDL문도 자동으로 생성해준다. -> 데이터베이스 설계 중심의 패러다임을 객체 설계 중심으로 역전 가능
  • 유지보수: SQL을 직접 다루면 생기는 SQL에 의존적인 개발을 피해 수정/추가 시 유지보수해야할 코드의 양이 줄어든다.
  • 패러다임 불일치 해결
  • 성능
  • 데이터 접근 추상화와 벤더 독립성: 페이징 처리와 같이 데이터베이스마다 달라 사용법을 배워야하는 부분을 JPA에서 처리해, 애플리케이션의 데이터베이스를 바꾸는 것이 조금 자유로워짐.

자바 ORM 표준 JPA 프로그래밍 책을 읽으며 정리하는 스터디 기록.

profile
이불 밖은 위험해.

0개의 댓글