본문 바로가기
프로그래밍/JPA

01. JPA 소개

by 직장인 투자자 2022. 1. 8.

개요 & SQL을 직접 다룰때 발생하는 문제점.

관계형 데이터베이스를 사용하는 자바 애플리케이션 개발 시, SQL을 통한 데이터 처리는 매우 지루하고, 반복적인 작업이 되었다.

이에 생산성을 높이기 위한 방법으로 Mybatis(SQL은 직접 작성하나, 맵핑을 위한 편리한 방법을 지원함)나 스프링의 JDBC 같은 SQL매퍼를 사용해 코드를 많이 줄일 수 있었지만, 코드 작성뿐 아니라, 유지보수의 측면에서는 여전히 문제가 발생하였다.

테이블에 새로운 필드가 추가될 경우..

아래와 같은 구조의 Member 테이블이 존재하고, Member테이블에 email필드 추가 시 발생하는 유지보수성 작업 예시.

public class Member {
    private String memberId;
    private String name;
    ...

    ---[ADD]--- 
    private String email;
}

public class MemberDAO {
    private Member find(String memberId) {...}
}
  1. 회원 조회용 SQL 작성
---[BEFORE]--- 
sql = "SELECT MEMBER_ID, NAME FROM MEMBER M WHERE MEMBER_ID = ?"
---[AFTER]--- 
sql = "SELECT MEMBER_ID, NAME, EMAIL FROM MEMBER M WHERE MEMBER_ID = ?"
  1. 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);
...

---[ADD]--- 
String email = rs.getString("EMAIL");
member.setEmail(email);

간단한 조회에 대해서만 예시를 만들어보았으나, EMAIL을 조건문으로 활용하기 위해서는 추가 수정이 필요하며, CRUD 전작업에 대해서도 수정이 필요할 것입니다..

만약 위에서 하나의 과정이라도 누락된다면 아래와 같은 코드에서 에러가 발생하게 될 것이고,

개발자는 이미 작성된 코드(find)를 믿을 수 없는 상황이 발생하고, 서비스 또한 직접적인 타격을 받을 것입니다.

Member member = memberDao.find("memberId1234");
print(member.getEmail().toLowerCase()); //NPE

또한 유지보수가 아니더라도, 위와 같은 SQL 맵핑의 반복적인 작업은 매우 비생산적인 작업일 것입니다. (연관 객체가 있을 경우 특히!)

SQL을 직접 다룰 때 발생하는 문제점을 요약하자면

  • 진정한 의미의 계층 분할이 어렵고, 엔티티를 신뢰할 수 없다. (이미 짜인 SQL 맵핑 코드에 대해 신뢰할 수 없는 상황이 발생하여, 내부 로직을 확인하는 작업이 요구된다.)
  • SQL에 의존적인 개발을 피하기 어렵다. (객체나 로직의 수정이 발생할 경우, 상황에 맞도록 SQL의 수정이 불가피하다.)

JPA와 문제 해결

JPA를 사용하면 객체를 데이터베이스에 저장하고 관리할 때, 개발자가 직접 SQL을 작성하는 것이 아닌, JPA가 제공하는 API를 통해 컨트롤하게 됩니다.

그렇게 되면 JPA는 개발자 대신 적절한 SQL을 생성하여 데이터베이스 전달합니다. (유지보수가 매우 편리해지며, 객체를 신뢰할 수 있게 된다.)

  • JPA를 통해 조회한 객체를 조회하여, 수정된 데이터를 db에 반영 (자세한 내용은 3장에서...)
String memberId = "helloId";
Member member = jpa.find(Member.class, memberId); //조회

member.setName("이름변경"); //수정
  • 연관된 객체 조회 (자세한 내용은 8장에서...)
    Member class에 Team필드가 추가될 경우, 추가적인 SQL수정 작업이 필요 없게 된다.
public class Member {
    private String memberId;
    Team team;
}

public class Team {
    private name;
    private supporter;
}

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

패러다임의 불일치

객체지향 프로그래밍은 추상화, 캡슐화, 정보은닉, 상속, 다형성 등의 다양한 패러다임을 가지고 있다.

하지만 관계형 데이터베이스는 추상화, 상속, 다형성과 같은 개념이 없고 데이터 중심의 구조적인 사고를 요구한다.

이처럼 서로 목적이 다른 두 가지를 연관시켜, 객체 구조를 테이블 구조에 저장하는 데에는 한계가 있고, 이러한 불일치 문제를 개발자가 중간에서 해결해주어야 한다.

그것을 대신해주는 것이 JPA가 하는 일이다.

상속관계 에서의 불일치

abstract class Item {
    Long id;
    String name;
    int price;
}

class Album extends Item {
    String artist;
}
  • 위의 상황을 관계형 데이터베이스의 관점에서 본다면, Album 객체를 저장하기 위해서는, Album과 Item객체를 분해해서 SQL을 작성해야 한다.
INSERT INTO ITEM ...
INSERT INTO ALBUM ...

조회하는 것 또한 간단하지 않다. Album을 조회하기 위해서는 Join 해서 조회 후, sql결과와 Album객체와의 맵핑이 필요하다.

  • 만약 객체의 관점에서 본다면, Join이나 상속관계를 어렵지 않게 저장해낼 수 있다.
Album album = new Album();
album.set(new Item());

save(album); // JPA라면 jpa.persist(album)
  • 또한 Album을 조회할 때 Join과정에서도 패러다임의 차이점을 발견할 수 있다.
    객체는 연관 객체에 대해 단방향 접근만 허용하지만, 관계형 DB는 양방향 접근이 가능하다.
Album album = new Album();
album.getItem(); //정방향 허용
Item.getAlbum(); //ERROR. 역방향 허용하지 않음.
SELECT A.*, I.*
FROM ALBUM A
JOIN ITEM I ON A.ID = I.ID;


SELECT A.*, I.*
FROM ITEM I
JOIN ALBUM A ON I.ID = A.ID

JPA의 지연 로딩

언제 사용할지 모르는 자식 객체에 대해, 부모 객체 조회시점에 포함하지 않고, 사용할 경우에만 로딩시킨다.

class Parent {
    Long id;
    Child child;
}

Parent parent = jpa.find(Parent.class, parent_id);// SELECT PARENT 조회 쿼리만 실행.

Child child = parent.get child();// 실제 사용시점에 조회. SELECT CHILD

JPA 조회된 데이터의 비교

아래 두 데이터는, 실제 내부 필드의 값은 모두 같으나, 두 번의 조회로 인해 생성된 다른 객체이다.

parent_id = 123;
Parent parent1 = parentDAO.getParent(parent_id);
Parent parent2 = parentDAO.getParent(parent_id);

print(parent1 == parent2); // print(False); 

JPA는 이러한 상황에서 같은 트랜잭션일 경우 같은 객체가 조회되는 것을 보장한다.

parent_id = 123;
Parent parent1 = jpa.getParent(Parent.class, parent_id);
Parent parent2 = jpa.getParent(Parent.class, parent_id);

print(parent1 == parent2); // print(True); 

정리

객체 모델과 관계형 DB 모델은 지향하는 패러다임이 서로 다르고, 이 차이를 극복하기 위한 코드 작성은 매우 비생산적이다. 이를 해결하기 위한 결과물이 바로 JPA이다.

반응형

'프로그래밍 > JPA' 카테고리의 다른 글

05장. 연관관계 매핑기초  (4) 2022.01.22
04장. Entity Mapping  (6) 2022.01.18
03장. 영속성 관리  (2) 2022.01.14
02. JPA 시작  (10) 2022.01.11

댓글