JPA 1. SQL 중심적인 개발의 문제점

@t189216·2024년 2월 27일

Spring Boot

목록 보기
2/9

SQL 중심적인 개발의 문제점

애플리케이션은 객체지향, DB는 보통 관계형 DB(Oracle, MySQL, ...)를 많이 사용합니다. 그래서 객체를 관계형 DB에 관리합니다. 하지만 객체를 관계형 DB에 보관하고 사용하려면 엄청난 양의 DB용 언어인 query를 작성해야 합니다.

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

    ...
}
INSERT INTO MEMBER(MEMBER_ID, NAME) VALUES
SELECT MEMBER_ID, NAME FROM MEMBER M
UPDATE MEMBER SET ...

+ tel 추가됨

public class Member {
    private String memberId;
    private String name;
    private String tel;
    ...
}
INSERT INTO MEMBER(MEMBER_ID, NAME, TEL) VALUES
SELECT MEMBER_ID, NAME, TEL FROM MEMBER M
UPDATE MEMBER SET ... TEL = ?

필드를 하나하나 추가해야 합니다.

관계형 DB와 통신하려면 SQL을 사용해야 합니다. 때문에 SQL의 의존적인 개발을 피하긴 어렵습니다.

객체와 관계형 DB

객체 지향 프로그래밍 은 추상화, 캡슐화, 정보은닉, 상속, 다형성 등 시스템의 복잡성을 제어할 수 있는 다양한 장치들을 제공한다.

객체를 영구 보관하는 저장소로 다음과 같은 것들이 있습니다.

  • RDB(관계형 DB) ✅ 80~90% 이상이 선택
  • NosQL(MongoDB)
  • 데이터를 어떻게든 끌고와 File로 저장해도 됩니다.

객체 ➡️ SQL 변환 ➡️ RDB에 SQL로 전달 과정을 계속 반복해야 합니다.

객체와 관계형 데이터베이스의 차이

1. 상속

관계형 DB는 기본적으로 객체에서 생각하는 상속관계가 없습니다. 상속관계와 비슷하게 부모같은 테이블(슈퍼 타입)과 자식같은 테이블(서브 타입)로 분리해 필요할 때는 가져오는 방식으로 사용할 수 있습니다. 데이터베이스 테이블 설계 기법 중 하나로 슈퍼 타입, 서브 타입 관계 라고 합니다.

Album을 저장하려면..
1. Album 객체에서 데이터를 꺼내는 분해
2. INSERT INTO ITEM ...
3. INSERT INTO ALBUM ...

Album을 조회하려면..
1. 각각의 테이블에 따른 JOIN SQL 작성
2. 각각의 객체 생성
3. 데이터를 다 가져오고.. 부모 테이블 확인하고.. 데이터 다시 세팅하고...

하지만, 자바 컬렉션에서 조회하면?

Album album = list.get(albumId);

// 부모 타입으로 조회 후 다형성 활용
Item item = list.get(albumId);

2. 연관관계

  • 객체는 참조를 사용 : member.getTeam()
    Member와 Team이 연관관계가 있다면 member.getTeam() 또는 member.team 을 사용해서 Member와 연관된 Team을 꺼낼 수 있습니다.

  • 테이블은 외래 키 (Foreign Key)를 사용 : JOIN ON M.TEAM_ID = T.TEAM_ID
    참조가 없어 외래 키 (Foreign Key, FK)를 사용해 연관관계를 맺습니다.
    Team의 PK를 Mebmer가 TEAM_ID로 FK를 가지게 됩니다.

INSERT문을 쉽게 만들기 위해 객체를 테이블에 맞추어 모델링하면..

class Memebr {
    String id;        // MEMBER_ID 컬럼 사용
    Long teamId;      // TEAM_ID FK 컬럼 사용 //**
    String username;  // username 컬럼 사용
}

class Team {
    Long id;      // TEAM_ID PK 사용
    String name;  // NAME 컬럼 사용
}
INSERT INTO (MEMBER_ID, TEAM_ID, USERNAME) VALUES ...

객체다운 모델링은 아닙니다.

객체다운 모델링을 하면?

class Memebr {
    String id;        // MEMBER_ID 컬럼 사용
    Team team;        // 참조로 연관관계를 맺음 //**
    String username;  // username 컬럼 사용
}

class Team {
    Long id;      // TEAM_ID PK 사용
    String name;  // NAME 컬럼 사용
}

하지만, 객체답게 모델링을 하게 되면 DB에 INSERT하기 까다로워집니다.

INSERT INTO (MEMBER_ID, TEAM_ID, USERNAME) VALUES ...
// Team에 대한 참조만 있을뿐 TEAM_ID가 존재하지 않는다.

대안으로 member.getTeam()에서 id를 가져오면 PK를 FK로 사용할 수 있습니다. (복잡함)

객체다운 모델링 조회..

member와 team이 연관관계가 있는 상태로 가져오고 싶다면 member와 team 둘 다 조회를 해야 합니다. DB 테이블에서는 성능을 위해 보통 한 번에 조회를 합니다.

SELECT M.*, T.*
	FROM MEMBER M
    JOIN TEAM T ON M.TEAM_ID = T.TEAN_ID
public Member find(String memebrId) {
	// SQL 실행 ...
    Member member = new Member();
    // DB에서 조회한 회원 관련 정보를 모두 입력
    
    Team team = new Team();
    // DB에서 조회한 팀 관련 정보를 모두 입력
    
    // 회원과 팀 관계 설정
    member.setTeam(team);
    return memebr;
}

객체다운 모델링, 자바 컬렉션에서 관리 !

list.add(member);

Member member = list.get(memberId);
Team team = member.getTeam();

3. 객체 그래프 탐색

객체는 참조를 활용해 자유롭게 객체 그래프를 탐색할 수 있어야 합니다.

Java 객체 안에서 DB의 객체를 관리하는 것은 굉장히 까다로운 일입니다.

  • 서로 연관관계를 바라보는 방법이 다르기 때문
  • 처음에 실행하는 SQL에 따라 이 탐색 범위가 결정되어 버립니다. ➡️ 엔티티 신뢰 문제 발생

❗️ 엔티티 신뢰 문제

컨트롤러, 서비스, 리포지터리, DAO 등 계층의 아키첵처는 다음 계층을 믿고 쓸 수 있어야 합니다.

class MemberService {
    ...
    public void process() {
        Member member = memberDAO.find(memberId);
        member.getTeam();  // ??
        member.getOrder().getDelivery();  // ??
    }
}

memberDAO를 다른 곳에서 가져온 상황에서 객체 그래프를 자유롭게 탐색할 수 있을까요? DAO를 다 살펴봐야 알 수 있습니다. 하지만 그렇다고 모든 객체를 미리 로딩할 수는 없습니다. 어마어마한 양의 SQL 쿼리를 작성할 수 없기 떄문입니다.

사실 진정한 의미의 계층 분할이 어렵습니다.

4. 비교하기

String memberId = "100";
Member member1 = memberDAO.getMember(memebrId);
Member member2 = memberDAO.getMember(memebrId);

member1 == member2;  // 다르다

일반적으로 SQL을 사용하는 경우입니다. memberId가 100번인 회원을 두 번 꺼내올 때, 이 인스턴스는 다르다고 나옵니다.

class MemberDAO {
    public Member getMember(String memberId) {
        String sql = "SELECT * FROM MEMBER WHERE MEMBER_ID = ?";
        ...
        // JDBC API, SQL 실행
        return new Member(...);
    }
}

보통 SQL 쿼리를 짜고 데이터를 다 넣은 새로운 객체를 만들어서 그 안에 SQL 쿼리 결과를 넣어서 반환합니다. 결과적으로 member1와 member2의 데이터는 같지만 다른 인스턴스가 2개 생성됩니다.

비교하기 - 자바 컬렉션에서 조회하면 ?

String memberId = "100";
Member member1 = list.get(memberId);
Member member2 = list.get(memberId);

member1 == member2;  // 같다

자바 컬렉션에서 같은 인스턴스를 조회하게 되면 같은 참조로 나옵니다.

이론적으로 객체 지향적으로 설계하는 게 좋다고 하지만, SQL로 전환하는 과정에서 비용이 너무 많이 발생한다. 객체답게 모델링 할수록 매핑 작업만 늘어난다.

객체를 자바 컬렉션에 저장하듯이 DB에 저장하는 방법이 JPA, Java Persistence API 입니다.

↗️ JPA에 대해서

profile
Today I Learned

0개의 댓글