[JPA] 1장. JPA 소개

이동엽·2023년 2월 24일
1

jpa

목록 보기
1/8

자바 ORM 표준 JPA 프로그래밍을 학습한 내용을 정리합니다.


🔥 1.1 SQL을 직접 다룰 때 발생하는 문제점

관계형 데이터베이스는 가장 대중적이고, 신뢰할 만한 안전한 데이터 저장소다.
→ 그래서 자바로 개발하는 애플리케이션은 대부분 관계형 데이터베이스를 사용한다.

1.1.1 반복, 반복 그리고 반복

SQL을 직접 다룰 때의 문제점을 알아보자.

  • 회원 객체
public class Member {
    private String memberId;
	private Stirng name;
}
  • 회원용 DAO(데이터 접근 객체)
public class MemberDAO {
	public Member find(String memberId) {...}
}

MemberDAO의 find() 메소드로 회원 조회 기능을 개발하는 순서

  1. 회원 조회용 SQL을 작성
    SELECT member_id, name FROM member WHERE member_id = ?

  2. JDBC API를 사용해 SQL을 실행
    ResultSet rs = stmt.executeQuery(sql);


  1. 조회 결과를 Member 객체로 매핑
String memberId = rs.getString("member_id");
String name = rs.getString("name");
        
Member member = new Member();
member.setMemberId(memberId);
member.setName(name);

회원 등록 기능을 추가했다고 가정

  • Member DAO
public class MemberDAO {
	public Member find(String memberId) {...}
    public void save(Member member) {...}
}

회원 등록 기능을 개발하는 과정

  1. 회원 등록용 SQL 작성
    String sql = "INSERT INTO member(member_id, name) VALUES(?, ?)";

  2. 회원 객체의 값을 꺼내 등록 SQL에 전달

pstmt.setString(1, member.getMemberId());
pstmt.setString(2, member.getName());
  1. JDBC API를 사용해서 SQL을 실행
    pstmt.executeUpdate(sql);

데이터베이스는 객체 구조와는 다르게 데이터 중심의 구조를 가진다.
→따라서, 객체를 데이터베이스에 직접 저장하거나 조회할 수가 없다!

→ 개발자는 SQL과 JDBC API를 사용해 변환 작업을 직접 해주어야 한다.

  • 이때, 개발자가 직면하는 문제점
    • 너무 많은 SQL과 JDBC API를 코드로 작성해야 한다.
    • 테이블마다 이런 비슷한일이 많다면? → 무한 반복!


1.1.2 SQL에 의존적인 개발

위에서 MemberDAO를 완성했는데, 갑자기 회원의 연락처도 저장해달라는 요구사항이 발생

  • 회원 클래스에 필드 추가
public class Member {
	private String memberId;
    private Stirng name;
    private String tel; //추가		
}

  • 연락처를 저장할 수 있도록 INSERT SQL 수정
    String sql = "INSERT INTO member(member_id, name, tel) VALUES (?, ?, ?)";

  • 회원 객체의 연락처 값을 꺼내 등록 SQL에 전달
    pstmt.setString(3, member.getTel());

현재까지 상황 정리

  • 데이터 접근 계층을 사용해 SQL을 숨겨도 어쩔 수 없이 DAO를 통해 어떤 SQL이 실행되는지 확인해야 한다.
    → 이는 진정한 의미의 계층 분할이 아니다.
    → 물리적으로 SQL과 JDBC API를 데이터 접근 계층에 숨기는 데에는 성공했을지 몰라도,
    → 논리적으로는 엔티티와 아주 강한 의존관계를 가지고 있다.
    → 이것이 변경사항이 생겼을 때 대부분의 코드를 변경해야 하는 문제를 불러 일으킨다.

SQL의 문제점

  • 진정한 의미의 계층 분할이 어렵다.
  • 엔티티를 신뢰할 수 없다.
  • SQL에 의존적인 개발을 피하기 어렵다.


1.1.3 JPA와 문제 해결

  • JPA를 사용한다면?
    • 객체를 데이터베이스에 저장할 때 개발자가 직접 SQL을 작성하지 않아도 된다!
    • JPA API를 이용하면 적절한 SQL을 생성해 데이터베이스에 전달하기 때문!

JPA가 제공하는 CRUD API를 간단히 알아보자!

  • 저장 기능
    jpa.persist(member);

  • 조회 기능
String memberId = "helloId";
Member member = jpa.find(Member.class, memberId); //조회

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

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


🔥 1.2 패러다임의 불일치

객체지향과 관계형 데이터베이스의 패러다임 불일치 문제를 살펴보자!

1.2.1 상속

  • 객체는 상속이라는 기능을 가지고 있지만, 테이블은 상속이 없다!
    • 다행히 데이터베이스 모델링에는 슈퍼타입-서브타입 관계를 사용해 상속과 유사하게 만들 수 있다.
    • 우측 그림에서, DTYPE 컬럼을 사용해 어떤 자식 테이블과 관계있는지 나타낸다.
      • ex) DTYPE = MOVIE 이면 영화 테이블과 관련있음

JPA와 상속

  • JPA는 상속과 관련된 패러다임의 불일치 문제를 개발자 대신 해결해준다.
    • 마치 자바 컬렉션에 객체를 저장하듯 JPA에게 객체를 저장하면 된다.

JPA를 이용해 객체를 저장하는 과정

  1. Item을 상속한 Album 저장
    jpa.persist(album);

  2. JPA는 SQL을 실행해 객체를 ITEM, ALBUM 두 테이블에 나누어 저장

INSERT INTO ITEM ...
INSERT INTO ALBUM ...
  1. Album을 조회
String albumId = "id001";
Album album = jpa.find(Album.class, albumId);
  1. JPA는 ITEM과 ALBUM 두 테이블을 조인해 필요한 데이터를 조회한 뒤, 결과를 반환
SELECT I.*, A.*
  FROM item I
  JOIN album A ON I.item_id = A.item_id

연관관계

  • 객체는 참조를 사용해서 → 다른 객체와 연관관계를 가짐
    객체는 참조에 접근해서 → 연관된 객체를 조회
  • 테이블은 외래키를 사용해서 → 다른 테이블과 연관관계를 가짐
    테이블은 조인을 사용해서 → 연관된 테이블을 조회

참조를 사용하는 객체 ←→ 외래키를 사용하는 관계형 데이터베이스

  • Member 객체 예시 :
    • member.getTeam(); 을 이용해 참조 필드에 접근 가능
      class Member {
      		Team team;
      		Team getTeam() { return team; }
      }
  • Member 테이블 예시 :
    • 조인을 통해 연관된 TEAM 테이블을 조회 가능
      SELECT M.*, T.*
        FROM MEMBER M
        JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
  • 추가로 객체의 경우는 참조가 있는 방향으로만 조회할 수 있다.
    • ex) Team에 member 필드가 없다면 반대 방향으로는 조회할 수 없음.
    • 반면에 테이블은 외래 키 하나로 양쪽 조회가 가능하다!

객체를 테이블에 맞추어 모델링

  • 테이블에 맞춘 객체 모델
    class Member {
    		String id;        //MEMBER_ID 컬럼
    		Long teamId;      //TEAM_ID FK 컬럼
    		String username;  //USERNAME 컬럼
    }
    
    class Team {
    		Long id;          //TEAM_ID PK 사용
    		String name;      //NAME 컬럼 사용
    }
  • 단, 이러한 방식을 따르면 좋은 객체 모델링을 기대하기 어렵고, 객체지향의 특징을 잃어버리게 된다!

객체지향 모델링

  • 참조를 사용하는 객체 모델
    class Member {
    		String id;        //MEMBER_ID 컬럼
    		**Team team;        //참조로 연관관계를 맺는다.**
    		String username;  //USERNAME 컬럼
    }
    
    class Team {
    		Long id;          //TEAM_ID PK 사용
    		String name;      //NAME 컬럼 사용
    }
  • team 필드를 보면 외래 키의 값이 아닌 참조를 보관한다.
    • 조회가 가능해졌지만, 테이블에 저장하기가 쉽지 않음!
    • 따라서 개발자가 중간에서 변환 역할을 해야 한다.

  • 개발자가 직접 연관관계 설정 → 이 과정들은 모두 패러다임 불일치를 해결하려고 소모하는 비용임!
    public  Member find(String memberId) {
    		
    		//SQL 실행
    		...
    		Member member = new Member();
    		//DB에서 조회한 회원 관련 정보를 모두 입력
    
    		Team team = new Team();
    		...
    		//DB에서 조회한 팀 관련 정보를 모두 입력
    
    		//회원과 팀 관계 설정
    		member.setTeam(team);
    		return member;
    }

JPA와 연관관계

  • JPA는 연관관계 패러다임의 불일치 문제를 해결한다.
    member.setTeam(team);     //회원과 팀 연관관계 설정
    jpa.persist(member);      //회원과 연관관계 함께 저장
  • 마찬가지로 객체를 조회할 때 외래 키를 참조로 변환하는 일도 JPA가 처리한다.
    Member member = jpa.find(Member.class, memberId);
    Team team = member.getTeam();

1.2.3 객체 그래프 탐색

  • 객체에서 회원이 소속된 팀을 조회할 때 Team team = member.getTeam(); 와 같이 참조를 이용한다.
    • 이런 식으로 연관된 팀을 찾는 것을 객체 그래프 탐색이라 한다.

SQL을 직접 다루면 처음 실행하는 SQL에 따라 객체 그래프를 어디까지 탐색할 수 있는지 정해진다.

→ 이는 객체지향 개발자에겐 너무 큰 제약!

  • 회원 조회 비즈니스 로직
    • 이 코드로썬 어디까지 객체 그래프를 탐색할 수 있을지 알 수 없다. Order 조회가능? Delivery 가능?
    • 따라서 결국 DAO를 열어서 SQL을 직접 확인해야 한다.
      class MemberService {
      		public void process() {
      				Member member = memberDAO.find(memberId);
      				member.getTeam();
      				member.getOrder().getDelivery();  // ???
      		}
      }

JPA와 객체 그래프 탐색

  • JPA를 사용하면 객체 그래프를 마음껏 탐색할 수 있다.
    member.getOrder().getOrderItem()........ //자유로운 객체 그래프 탐색
    • JPA를 사용하면 연관된 객체를 신뢰하고 마음껏 조회할 수 있다!
    • 이 기능은 실제 객체를 사용하는 시점까지 DB 조회를 미룬다고 해서 지연 로딩이라 한다.

  • 투명한 엔티티
    • JPA는 지연 로딩을 투명하게 처리한다. → 메소드 구현부에 JPA와 관련된 어떤 코드도 사용하지 않음!
      class Member {
      		private Order order;
      
      		public Order getOrder() {
      				return order;
      		}
      }
  • 지연 로딩 사용
    //처음 조회 시점에 SELECT MEMBER SQL
    Member member = jpa.find(Member.class, memberId);
    
    Order order = member.getOrder();
    order.getOrderDaate(); //Order를 사용하는 시점에 SELECT ORDER SQL

1.2.4 비교

  • 데이터베이스는 기본 키의 값으로 각 로우(row)를 비교
  • 객체는 동일성(identity) 비교와 동등성(equality) 비교가 있다.
    • 동일성 비교 → == 비교, 객체 인스턴스의 주소 값을 비교
    • 동등성 비교 → equals() 메서드를 사용해 객체 내부의 값을 비교

  • MemberDAO
    class Member {
    		public Member getMember(String memberId) {
    				String sql = "SELECT .......";
    
    				//JDBC API, SQL 실행
    				**return new Member(...); //실행할 때마다 새로운 객체를 생성**
    		}
    }
  • 조회한 회원 비교 코드
    String memberId = "100";
    Member member1 = memberDAO.getMember(memberId);
    Member member2 = memberDAO.getMember(memberId);
    
    member1 == member2; //다르다

기본 키 값이 같은 회원 객체를 조회했기 때문에 데이터베이스에서는 같은 로우를 조회한다.
하지만, 객체 측면에서 볼 때 (메소드를 실행할 때마다 새로운 객체를 반환했으므로) 다른 객체다.

여기서도 패러다임의 불일치 문제를 해결해야 한다!


JPA와 비교

  • JPA는 같은 트랜잭션일 때 같은 객체가 조회되는 것을 보장한다.
    String memberId = "100";
    Member member1 = memberDAO.getMember(memberId);
    Member member2 = memberDAO.getMember(memberId);
    
    member1 == member2; //같다

🔥 1.3 JPA란 무엇인가?

  • JPA(Java Persistence API)는 자바 진영의 ORM 기술 표준이다.
    • 아래 그림과 같이 애플리케이션과 JDBC 사이에서 동작한다.
  • 그렇다면 ORM(Object Relational Mapping)은 무엇일까?
    • ORM은 이름 그대로 객체와 관계형 데이터베이스를 매핑한다는 뜻이다.
    • 쉽게 말해 아래 그림처럼, 객체를 컬렉션에 저장하듯 데이터베이스에 저장할 수 있게 해준다.
  • 객체를 조회할 때도 JPA를 통해 직접 조회할 수 있다.

ORM 프레임워크는 다양한 패러다임의 불일치 문제들도 해결한다.

  • 덕분에 객체 측면에서는 정교한 객체 모델링을 할 수 있고, 관계형 DB는 데이터베이스에 맞게 설게하면 된다.
  • 개발자는 데이터 중심인 관계형 데이터베이스를 사용해도 객체지향 애플리케이션 개발에 집중할 수 있다!

  • 자바 진영에는 다양한 ORM 프레임워크들이 있다.
    • 그 중에서 유명한 건 하이버네이트!

1.3.1 JPA 소개

과거 자바 진영

  • 과거에 자바는 엔터프라이즈 자바 빈즈(EJB)라는 표준 기술을 만들었다.
    • 그 안에는 엔티티 빈이라는 ORM 기술도 포함되어 있었다.
      • 하지만, 너무 복잡하고 성숙도가 떨어져 자바 엔터프라이즈(J2EE) 애플리케이션 서버에서만 동작함
    • 이때 하이버네이트가 등장! → 실용적이고 가벼움!
    • 이후, EJB 3.0에서 하이버네이트를 기반으로 새로운 자바 ORM을 만듬 → 이게 JPA
    ![](https://velog.velcdn.com/images/dongvelop/post/23f3cb93-0c04-47b0-b3ce-345db7045866/image.png)

1.3.2 왜 JPA를 사용해야 하는가?

  • 생산성
    1. JPA를 사용하면 자바 컬렉션에 객체를 저장하듯, JPA에게 객체를 전달하기만 하면 된다.
      jpa.persist(member);
    2. 이 기능들로 인해 데이터베이스 설계 중심의 패러다임을 객체 설계 중심으로 역전시킬 수 있다.

  • 유지보수
    1. 필드를 추가하거나, 삭제하는 과정시 추가로 작성(변경)해야 하는 코드를 JPA가 처리한다.
    2. 또한 객체지향의 장점을 살려 유연하고 유지보수하기 좋은 도메인 모델을 설계할 수 있게 한다.

  • 패러다임 불일치 해결
    1. JPA는 상속, 연관관계, 객체 그래프 탐색, 비교와 같은 패러다임의 불일치를 해결한다.

  • 성능
    1. JPA는 애플리케이션과 데이터베이스 사이에서 다양한 성능 최적화 기회를 제공한다.

  • 데이터 접근 추상화와 벤더 독립성

    1. 관계형 데이터베이스는 같은 기능도 벤더마다 사용법이 다른 경우가 많다.
    2. JPA는 그림처럼 추상화된 데이터 접근 계층을 제공해 특정 데이터베이스 기술에 종속되지 않는다!

  • 표준
    1. JPA는 자바 진영의 ORM 표준이다.


🔥 1.4 정리

Q&A

  • JPA는 학습 곡선이 높다고 하던데요?
    • 맞다. JPA를 사용하려면? → 객체와 관계형 데이터베이스의 매핑을 학습한 후 이해해야 한다.
    • 또한 JPA의 핵심인 영속성 컨텍스트에 대한 이해가 부족하면 SQL을 직접 사용하는 것보다 못한 상황이 벌어질 수 있다.
    • 사실 JPA가 근본적으로 어려운 이유는 ORM이 객체지향과 관계형 데이터베이스라는 두 기둥 위에 있기 때문이다!
      • 이 둘의 기초가 부족하면 어려울 수밖에 없다.
profile
백엔드 개발자로 등 따숩고 배 부르게 되는 그 날까지

0개의 댓글