JPA 소개

윤용운·2022년 3월 23일
1

JPA_스터디

목록 보기
1/9
post-thumbnail

1장. JPA 소개

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

반복, 반복, 그리고 반복

  • 객체를 데이터베이스에 CRUD하려면, 너무 많은 SQL과 JDBC API 코드를 작성해야 한다.
// SQL
// SELECT MEMBER_ID, NAME FROM MEMBER M WHERE MEMBER_ID = ?
// JDBC API 사용
ResultSet rs = stmt.executeQuery(sql);
// 조회 결과 매핑
String memberId = rs.getString("MEMBER_ID");
String name = rs.getString("NAME");

Member member = new Member();
//getter && setter.....
// 반복, 반복, 반복.......
  • 데이터베이스는 객체 구조와는 다른 데이터 중심의 구조를 가지므로, 객체를 데이터베이스에 직접 저장하거나 조회할 수 없다.

SQL에 의존적인 개발

  • 데이터베이스에 변경이 생기거나, 수정을 해야 하는 경우, 많은 코드를 수정하여야 한다.

    ex) 컬럼 추가 -> CRUD 코드 변경

  • 데이터 접근 계층을 사용해 SQL을 숨겨도, 결국 DAO를 열어서 어떤 SQL이 실행되는지 확인을 하여야 한다.
  • SQL에 의존하게 됨으로써 개발자들이 엔티티를 신뢰하고 사용할 수 없고, DAO를 열어 어떤 SQL이 실행되고, 어떤 객체들이 함께 조회되는지 일일히 확인해야 한다.

JPA와 문제 해결

  • JPA 사용 시, 객체를 데이터베이스에 저장, 관리 할 때, SQL을 작성하는 것이 아닌 JPA가 제공하는 API를 사용하여 관리가 가능하다.
    • persist 메소드 호출 시, JPA가 객체와 매핑정보를 보고 적절한 INSERT SQL을 생성하여 DB에 전달한다.
    // 저장
    jpa.persist(member);
    • find 메소드 호출 시, 역시 적절한 SELECT SQL을 생성하여 DB에서 조회하고, 결과를 객체를 생성하여 반환해준다.
    // 검색
    String memberId = "member1";
    Member member = jpa.find(Member.class, memberId);
    • JPA는 별도의 수정 기능은 지원하지 않지만, 객체를 조회하여 값을 변경하면 트랜잭션 커밋 시 DB에 적절한 UPDATE SQL이 전달된다.
    // 수정
    Member member = jpa.find(Member.class, memberId);
    member.setName("new name");
    • 연관된 객체를 사용하는 시점에서 적절한 SELECT SQL을 실행한다. 따라서 연관된 객체를 마음껏 조회할 수 있다.
    Member member = jpa.find(Member.class, memberId);
    Team team = member.getTeam();

패러다임의 불일치

객체와 관계형 데이터베이스는 지향하는 목적도 다르고, 표현하는 방법도 다르며, 이런 문제는 개발자가 중간에서 해결해야 한다.

상속

  • 객체는 상속이라는 기능을 가지고 있지만, 테이블은 상속이 없다.
abstract class Item {
	Long id;
    String name;
    int price;
}

class Album extends Item {
	String artist;
}

class Movie extends Item {
	String director;
    String actor;
}

class Book extends Item {
	String author;
    String isbn;
}
  • Album객체, 혹은 Movie 객체를 저장하려면 이 객체를 분해해서 2개의 SQL을 생성해야 한다. 자식 타입에 따라서 DTYPE이라는 필드 역시 추가해야 한다.
INSERT INTO ITEM ...
INSERT INTO ALBUM ...
  • JPA는 위의 문제를 개발자 대신 해결해준다.
// persist() 메소드를 사용하여 객체 저장
jpa.persist(album);

// JPA는 객체를 ITEM, ALBUM 두 테이블에 나누어 저장
INSERT INTO ITEM ...
INSERT INTO ALBUM ...

// find() 메소드를 사용하여 객체 조회
String albumId = "id100";
Album album = jpa.find(Album.class, albumId);

// ITEM, ALBUM 두 테이블을 조인해서 필요한 데이터를 조회, 반환
SELECT I.*, A.*
	FROM ITEM I
    JOIN ALBUM A ON I.ITEM_ID = A.ITEM_ID

연관관계

  • 객체는 참조를 사용하여 다른 객체와 연관관계를 가지고, 참조에 접근하여 연관된 객체를 조회하지만, 테이블은 외래 키를 사용하여 다른 테이블과 연관관계를 가지고, 조인을 사용해서 연관된 테이블을 조회한다.
  • 객체는 참조가 있는 방향으로만 조회할 수 있지만, 테이블은 외래키를 사용하여 반대로도 조회가 가능하다(JOIN)
  • 객체를 테이블에 맞추어 모델링하면 다음과 같이 바뀌게 된다.
// java
class Member {
	
    String id;			// MEMBER_ID
    Long teamId;		// TEAM_ID (FK) -> 객체에서는 참조가 불가능
    String username;	// USERNAME
}

class Team {
	Long id;			// TEAM_ID (PK)
    String name;		// NAME
}
  • 위의 코드는 Team 객체를 참조할 수 없으므로, 참조를 사용하게 변경 되어야 한다.
// java
class Member {
	
    String id;			// MEMBER_ID
    Team team;			// 참조로 연관관계를 맺는다.
    String username;	// USERNAME
    
    Team getTeam() {
    	return team;
    }
}

class Team {
	Long id;			// TEAM_ID (PK)
    String name;		// NAME
}
  • 위의 코드 역시 객체를 테이블에 저장하거나 조회하기가 쉽지 않다. 객체는 team 필드로 연관관계를 가지고, 테이블은 TEAM_ID 외래키로 연관관계를 가지기 때문이다. 따라서, 개발자가 중간에서 변환 역활을 해야 한다.
// 저장
member.getId();				// MEMBER_ID (PK)
member.getTeam().getId();	// TEAM_ID (FK)
member.getUsername();		// USERNAME

// 조회
public Member find (String memberId) {

	// SQL 실행...
    ...
	Member member = new Member();
	... // DB에서 조회한 회원 관련 정보 입력
	Team team = new Team();
	...
	member.setTeam(team);
    ... // 팀 관련 정보 입력
    member.setTeam(team); // 회원과 팀 관계설정
    return member;
}
  • JPA에서는 다음과 같이 해결한다.
// team의 참조를 외래 키로 변환해서 적절한 INSERT를 실행
member.setTeam(team);	// 회원과 팀 연관관계 설정
jpa.persist(member);	// 회원과 연관관계를 함께 저장

// 조회 역시 외래키를 참조로 변환해준다.
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam();

객체 그래프 탐색

  • 객체는 그래프를 탐색할 수 있어야 하지만, SQL에 따라 불가능한 상황이 존재한다.
SELECT M.*, T.*
	FROM MEMBER M
    JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
// member.getOrder(); => NULL
  • SQL을 직접 다루게 된다면 SQL에 따라 객체 그래프를 어디까지 탐색할 수 있는지 정해지고, 그렇다고 모든 객체 그래프를 어플리케이션 메모리에 올려 둘 수도 없다. 따라서, 상황에 따라 메소드를 여러개 작성해야 되는 경우가 생기게 된다.
memberDAO.getMember();						// Member 조회
memberDAO.getMemberWithTeam();				// Member, Team 조회
memberDAO.getMemberWithOrderWithDelivery();	// Member, Order, Delivery 조회
  • JPA에서는 연관된 객체를 사용하는 시점에서 적절한 SQL을 실행한다.

    실제 객체를 사용하는 시점까지 데이터베이스 조회를 미룬다(지연로딩)

// 처음 조회 점에서 SELECT MEMBER
Member member = jpa.find(Member.class, memberId);

Order order = member.getOrder();
order.getOrderDate();	// Order를 사용하는 시점에 SELECT ORDER

Member와 Order를 항상 함께 사용하면, 동시에 조회하는 것이 효과적이다.

  • 연관된 객체를 즉시 함께 조회할지, 아니면 실제 사용되는 시점에 지연해서 조회할지 역시 간단한 설정으로 정의가 가능하다.

비교

  • 데이터베이스는 PK값으로 각 row를 구분하고, 객체는 동일성(==)과 동등성(equals())로 비교한다.
  • JDBC API 사용 시, 같은 로우를 조회해도 다른 인스턴스가 반환된다.
Member member1 = memberDAO.getMember(memberId);
Member member2 = memberDAO.getMember(memberId);

// member1 == member2???
// 새로운 인스턴스를 생성해서 반환하기 때문에, 동일성 비교는 안된다.
  • JPA에서는 같은 트랜잭션일 떄, 같은 객체가 조회되는 것을 보장한다.
Member member1 = jpa.find(Member.class, memberId);
Member member2 = jpa.find(Member.class, memberId);

member1 == member2

JPA란 무엇인가?

  • ORM(Object-Relational Mapping)이란 말 그대로 객체와 관계형 데이터베이스를 매핑한 것이다. 이를 사용해 단순한 SQL전달 뿐 아니라 위에서 말한 다양한 패더라임의 불일치도 해결을 할 수 있다. JAVA 진영에서는 Hibernate 프레임워크가 가장 많이 사용된다.

JPA를 사용하는 이유

  • 생산성
    JPA 사용시, 자바 컬렉션에 객체를 저장하듯 JPA에 객체를 전달하면 된다. 반복적인 작업들(INSERT SQL 작성, JDBC API 사용) 등은 JPA가 대신 처리해준다. DDL문 또한 자동으로 생성해주는 기능도 있어서, DB설계 중심의 패러다임을 객체 설계 중심의 패러다임으로 바꿀 수 있다.
  • 유지보수
    기존에는 엔티티에 필드 하나만 추가해도 많은 것들을 수정해야 하지만, JPA는 알아서 처리해주므로 유지보수 해야되는 코드 수가 줄게 된다.
  • 페러다임의 불일치 해결
    상속, 연관관계, 객체 그래프 탐색, 비교하기와 같은 패러다임의 불일치 문제를 해결해준다.
  • 성능
    다양한 성능 최적화 기회를 제공한다.
    Member member1 = jpa.find(memberId);
    Member member2 = jap.find(memberId);
    같은 트랜잭션 안에서 같은 회원을 조회하면, 처음만 SQL을 전달하고, 두번째는 조회한 회원 객체를 재사용한다.
  • 데이터 접근 추상화와 벤더 독립성
    어플리케이션과 DP 사이에 추상화된 데이터 접근 계층을 제공해서 어플리케이션이 특정 DB에 종속되지 않도록 한다.

Reference

  • 자바 ORM 표준 JPA 프로그래밍 (김영한)

0개의 댓글