당니의 개발자 스토리
SQL 중심적인 개발의 문제점 본문
SQL 중심적인 개발의 문제점
이번 시간부터는 JPA에 대해서 소개를 해드리겠습니다.

이번 강의를 통해서 '아 JPA가 이런 거구나' 하고 배워보시면 됩니다.

제 소개는 앞에서 드렸구요.

먼저 여기서 두 가지를 말씀드릴 거에요. 우리가 보통 애플리케이션을 개발할 때는 SQL을 굉장히 많이 작성을 하죠. 그래서 이런 SQL 중심적인 개발의 문제가 뭔지 먼저 말씀드릴 거고요. 그리고 그 대안으로 JPA가 어떤 일들을 해주는지 또 JPA에 대해서 소개를 해드리도록 하겠습니다.

자 여러분 생각을 해보면 우리가 이 애플리케이션을 개발을 할 때는 보통 객체지향 언어로 개발을 합니다. Java, Scala, Kotlin, C# 등등 이런 객체지향 언어들로 개발을 하게 되겠죠.

자 그런데 이 데이터베이스 세계는 보통 관계형 DB를 많이 사용합니다. 물론 MongoDB나 관련된 NoSQL 기술들이 또 잘 되고 있긴 하지만, 그래도 거의 대부분은 관계형 DB에다가 데이터를 보관하실 거에요.

자 제가 왜 굳이 애플리케이션은 객체지향, 데이터베이스는 관계형 DB 라고 말씀드렸냐면 지금 우리가 사는 이 시대는, 여러분이 만든 이 객체 있죠. 이 Object를 관계형 DB에 보관하고 관리하는 시대에요.
그럼 그 데이터가 회원 데이터라고 하면, 회원 객체를 결국 어디에 보관하겠어요? 실제 실무에서 개발해야 되면 거의 관계형 DB에 다 보관하실 거에요.

자 그런데 이 객체를 관계형 DB에 보관하려면 또 Insert into 해가지고 쿼리를 엄청나게 작성을 해야 되죠. 생각을 해보면 이 회원 객체 데이터 하나를 저장하고 조회하고 수정하고 등등을 하려면 수많은 SQL을 사용을 해야 됩니다.
자 이게 바로 SQL 중심적인 개발의 문제점인데요.

SQL 중심적인 개발의 문제점에 대해서 자세히 설명 드릴게요.

일단 무한 반복되고 지루합니다. 생각해보시면 우리가 CRUD 라고 하죠. 기본적으로 데이터를 등록하고 조회하고 수정하고 삭제하고 이런 것들이 필요한데, 그러면 객체 하나를 DB에 보관하고 꺼내고 변경하고 삭제할 때마다 Insert Into, Update, Select, Delete 같은 쿼리를 계속 작성해야 되는 거죠.
그래서 Java 객체를 SQL로 바꾸고, SQL을 Java 객체로 바꾸고 이런 과정들을 계속 무한 반복해야 됩니다. 예를 들어서 회원 하나만 그러는 게 아니라 상품이 있다, 주문이 있다 그러면 그것마다 CRUD를 다 만들어야 되겠죠.
자 이제 예시를 하나 들어볼게요.

왼쪽처럼 회원이라는 클래스를 만듭니다. 그리고 필드들을 정의하겠죠. 그럼 이 객체를 데이터베이스에 저장을 하려면 어떻게 해야 돼요? 테이블에 값을 넣어주기 위해서 insert into Member 하고, 결국에는 select, update 하는 SQL을 우리가 리포지토리나 DAO에서 다 작성을 해야 되죠.
DAO란?

자 그리고 또 이런 문제도 있습니다.

여러분이 악덕 기획자를 만났어요. 갑자기 막 연락처를 추가해주세요. 그러는 거에요. 그러면 어떻게 해야 돼요? 연락처 필드를 insert, select, update, query에 전부 다시 다 집어넣어야 되죠. 그러다가 실수로 연락처 필드를 안 넣거나 해버리면, 예를 들어서 update 문에서 연락처 필드를 깜빡해서 등록이랑 조회는 되는데 고객이 막 연락처를 변경하니까 안되는 일들도 다반사로 생길 수 있습니다.
어쨌든 객체 필드를 하나 추가하게 되면 이 query문을 다 고쳐야 된다는 거에요.

자 그래서 어쨌든 우리는 결국 관계형 데이터베이스를 쓰고, 관계형 데이터베이스랑 통신하려면 SQL을 써야 되는 거죠. 그렇기 때문에 결과적으로 SQL의 의존적인 개발을 피하기는 어렵습니다.

자 그러면서 또 나오는 게 패러다임의 불일치 라는 게 있는데요. 여러분 생각해 보시면 객체랑 관계형 데이터베이스의 테이블 이라는 거는 뭔가 비슷한 것 같으면서도 굉장히 다릅니다. 이제 이 차이에 대해서 말씀을 해드릴게요.

여러분 객체지향 프로그래밍 이라는 것은 추상화, 캡슐화, 정보은닉, 상속, 다형성 같은 시스템의 복잡성을 제어할 수 있는 다양한 장치들을 제공합니다.
자 그래서 다시 처음부터 고민을 해보면,

이 객체라는 거는 rdb, 그러니까 관계형 데이터베이스에 저장을 해도 돼요. 그리고 MongoDB 같은 NoSQL에 저장을 해도 됩니다. 아니면 그냥 이 Object 안에 있는 데이터를 어떻게 다 끌고서 파일로 저장하셔도 돼요. 여기 있는 데이터들을 다 하나씩 읽어서 파일로 write 해도 되잖아요. 또 OODB 라고 이런 것도 있었는데 뭐 이런 거는 이제 망한 거여서 넘어가고, 암튼 객체를 영구 보관하는 다양한 저장소들이 있습니다.
자 그런데 만약에 여러분이 선택을 해야돼요. 내가 실무에서 개발하는데 이 회원 데이터, 회원 Object를 어딘가에 저장을 해야 돼요. 그럼 어디에 저장하시겠어요?

rdb, NoSQL, 파일, 물론 NoSQL을 쓸 수도 있는데 거의 한 80-90% 이상은 일단 관계형 데이터베이스를 선택을 할 거에요. 그리고 NoSQL은 약간 보조적인 수단으로 요즘 많이 쓰죠.

그래서 SQL을 피하고 싶어도 결국 현실적인 대안은 관계형 데이터베이스를 쓰긴 써야 되는 거에요.

그러면 이 과정이 참 지루하죠. 말씀드린 것을 도식화 시켜보면 회원을 저장하려고 합니다. 그럼 회원 객체를 어떻게 합니까? db에 저장하려면 Insert 문 다 만들어야 되죠. 그러니까 sql로 다 변환해야 되는 거예요. 조회할 때도 내가 sql을 select 문 다 조회한 다음에 그걸 객체로 바꿔야 되죠. 그래서 이 그림을 잘 보시면,

어쨌든 객체에 대한 데이터를 sql로 바꿔서, RDB에 객체를 전달해야 돼요.
근데 이걸 누가 한다?

바로 개발자인 여러분과 제가 하는 거죠. 개발자는 거의 sql 맵퍼로 일을 합니다. 이 맵퍼라는 건 뭐냐면,

중간에서 뭔가 맵핑해주는 걸 말하는 거예요. 객체랑 rdb 사이를 중간에서 뭔가 이렇게 해주는 거죠.
자 그래서 개발자는 거의 sql 맵퍼로 일을 하고 있는데, 결국 관계형 DB를 쓰다보니 이렇게 변환해주는 과정을 피하기가 되게 어렵습니다.

자 그러면서 이 객체와 관계형 데이터베이스의 차이에 대해서 말씀을 해드릴 건데요.
이게 앞서 말씀드린 이런 패러다임의 차이에 대해서 쭉 말씀드릴 거에요. 상속, 연관관계, 데이터 타입, 데이터 식별 방법 같은 크게 4가지, 물론 더 있긴 한데 크게 4가지의 차이가 있습니다.
자 제일 먼저 상속.

객체 지향을 배웠으면 상속은 기본적으로 다 아시죠. 예를 들어서 우리가 객체 기반으로 생각을 해보겠습니다. 상속으로 생각해봤을 때 예를 들어서 뭔가 상품을 팔아야 돼요. 그러면 Item이라는 거를 추상화 해서 부모로 두고 Id, 이름, 가격이라고 둘게요. 이것들은 공통요소니까.
그리고 그 밑에 Album, 영화, 그 다음에 책 이렇게 해서 해보겠습니다. 그러면 지금 왼쪽 그림에 나온 것처럼 객체 상속관계가 되겠죠.
그럼 여러분 데이터베이스 테이블에는 상속관계라는 게 있을까요? 이 데이터를 DB에다가 보관해야 되는데 이 상속관계를 DB에 저장할 수가 없잖아요. 그래서 관계형 데이터베이스는 기본적으로 우리가 객체에서 생각하는 상속관계는 없습니다. 물론 비슷한 게 있는 데이터베이스도 있긴 한데, 객체랑 완전히 똑같지는 않습니다.
관계형 데이터베이스에서도 이러한 상속 관계를 풀 수 있는 방법이 있긴 해요. 보통 어떻게 하냐면,

그림 오른쪽 Item을 보시면 ITEM_ID, NAME, PRICE 라고 되어있죠. 그러니까 부모같은 테이블을 만드는 거에요. 그리고 자식같은 테이블을 아래에다가 만드는 거죠. 이렇게 해서 데이터를 딱 분리해놓고, 필요할 때는 join해서 가지고 오고 약간 이렇게 푸는 건데, 이거를 뭐라 그러냐면 데이터베이스 테이블 설계 기법 중에 하나인데 슈퍼타입, 서브타입 관계라고 합니다.
그럼 이렇게 설계를 잘 했어요. 그러면 우리가 이제 Album을 저장하고 싶다. 라고 하면 어떻게 합니까?

처음에 어쨌든 이 Album 데이터를 꺼내야 돼요. 그래야 값을 넣을 거 아니예요.

자 그림을 보시면 지금 Album을 저장하려면 Item과 관련된 ITEM_ID와 이름, 가격이 필요하고 Album과 관련된 작곡가가 필요하죠.

그럼 어쨌든 이 Album 객체 데이터를 꺼내는 분해 작업을 하고, 그 다음에 Album을 저장할 때 데이터를 테이블 두 군데에 넣어야 돼요.
어디에?

Item 테이블이랑 Album 테이블. 그쵸? 지금 테이블이 나눠져 있잖아요.

그래서 insert into ITEM, insert into ALBUM 해가지고 query를 내가 직접 두 개를 작성해서 호출을 해줘야 됩니다.

그 다음에 조회를 봅시다. 차라리 저장하는 거는 그냥 하나하나 깔끔하게 넣으면 되는데 조회는 더 복잡해요.
자 각각의 테이블에 따른 join SQL을 작성을 해야 됩니다. 왜냐? Album을 조회한다는 거는

그림을 보시면 이 Album 뿐만 아니라, Item까지 같이 가져와야 돼요. 이 Album 인스턴스를 왼쪽에 있는 객체 Album으로 만들려면 상속 관계에서 부모인 Item과 관련된 데이터도 필요하고, Album 관련된 데이터도 다 필요하겠죠. 왜냐면 상속 관계도 되어있으니까.
그래서 Album 인스턴스를 만들 때 Item에 들어있는 Id, 이름, 가격 그리고 Album에 있는 작곡가, 이 4개의 데이터가 다 필요해요.
자 그럼 new Album 을 만들 때 어떻게 해야 합니까?

테이블 보세요. Item과 Album이 있죠. 이 Item 테이블에 있는 거랑 Album 테이블에 있는 두 가지 데이터가 다 필요하단 말이에요. 그러면 이 두 테이블을 Join 해야 돼요.

자 그래서 Join SQL을 작성을 합니다. 그 다음에 객체를 또 만듭니다. 그리고 그 데이터를 또 다 집어 넣어줘요.
그래서 이거를 코드로 작성하려면 얼마나 힘들겠어요. 데이터를 다 가져오고 또 부모 테이블에 뭐가 있는지 다 확인한 다음에, 또 데이터 다 세팅해주고 그런 과정들을 거쳐야 됩니다.

여러분 만약에 이번에는 Movie를 조회해야 돼요. 또는 Book을 조회해야 돼요. 그럼 어떻게 해야 될까요? Movie랑 Item을 또 Join 해야 되고 Book을 조회해야 되면 Book과 Item을 또 Join 해야 됩니다. 그냥 그 케이스마다 다 join 쿼리를 다 만들어 놔야 돼요. 그래서 되게 복잡해지죠.
그런데 이 중간 과정을 누가 한다? 개발자인 여러분과 제가 이 중간 과정을 다 해야 되는 겁니다.

자 그런데 여러분 이 Album을 그냥 Java 컬렉션에 저장한다고 생각해볼게요.

그러면 이 Album이라는 인스턴스 객체가 있어요. 이거를 내가 이 데이터베이스 테이블에 저장을 하다보니까 Insert into 쿼리도 두 번(ITEM, ALBUM) 날리고, 조회할 때는 막 Join해서 막 복잡하게 하고 하는데, 그냥 Java 컬렉션에다가 저장한다고 가정하면,

List면 list.add 해서 이 Album을 집어넣으면 끝나죠. 한 줄이면 끝나는 거잖아요. 이거는 객체 세상이니까 가능한 거죠.
지금 DB에다가 넣는 게 아니라 자바 컬렉션에 넣는 거잖아요. SQL을 작성할 필요도 없고 그냥 album에 데이터를 넣은 다음 list.add 해주면 끝납니다.

자 그리고 Java 컬렉션에서 조회하려면 컬렉션마다 좀 다르겠지만, 예를 들어서 그냥 Id로 조회할 수 있다고 하면, 컬렉션에다가 그냥 내가 찾는 Id를 넣어서 Album을 꺼낼 수 있겠죠. 또는 부모 타입으로 조회해서 다형성을 활용하는 것도 다 가능해지죠. 객체 세상이니까 다 가능한 겁니다.
제가 갑자기 왜 굳이 Java 컬렉션에서 하면 이렇게 한 줄로 된다는 말씀을 드릴까요? 뭔가 밑밥이 있는 거죠.

이제 연관관계에 대해서 말씀드려 볼게요.

객체랑 DB 테이블이랑 다른 것 중에 또 하나가 연관관계 라는 게 있는데요. 객체는 참조를 사용합니다. 그래서 객체는 member.getTeam() 이런 식으로 꺼낼 수 있죠.
자 그런데 테이블은 외래 키를 사용해서 Join을 해야 됩니다.
무슨 말이냐?

그림 위에 보시면은 객체 연관관계가 있죠. 이거는 Member랑 Team이랑 연관관계가 있으면 member.team 또는 member.getTeam()해서 Member와 연관된 Team을 꺼낼 수 있습니다.
자 그런데 테이블은 이런 객체 레퍼런스 라는 게 없어요. 참조가 없단 말이에요. 테이블은 외래 키 라는 Foreign key로 Join을 해서 연관관계를 맺게 됩니다. 그래서 Team의 PK를 가진 Member가 TEAM_ID를 FK로 가지고 있는거에요. 이렇게 Join 해서 하면 되죠.
자 그러면 이러한 참조의 차이 때문에 이제 어떤 문제가 발생이 되느냐?

지금 이 Member 객체랑 Team 객체를 우리가 테이블에 저장해야 돼요. 그럼 어떻게 하느냐?

보통 이 객체를 테이블에 맞춰서 모델링을 많이 합니다. Member 클래스를 보시면 Member에 String id가 있죠. 그리고 Team에 대한 참조를 가지고 있는 게 아니라, Team의 Id를 가지고 있죠. 왜 이렇게 설계하겠어요?
그래야 나중에 Insert 문 만들 때 이렇게 안 하면 되게 불편하겠죠. 어쨌든 객체를 테이블에 맞춰서 이렇게 모델링을 많이 하게 됩니다.

자 그래야 이렇게 insert 문을 짤 때 편해지는 거예요. 방금 말씀드린 것처럼 이렇게 해야 insert into member 해서 테이블에 저장할 때 뭐가 필요해요? MEMBER_ID가 필요하죠. 그 다음 Team의 FK도 필요하잖아요.

여기 그림을 보면 테이블 연관관계 하려고 하니까 Member를 저장하려면 MEMBER_ID, TEAM_ID, USERNAME 이렇게 필요하잖아요. 그럼 어떻게 하겠어요?

TEAM_ID를 꺼내려면 Member가 Long teamId를 가지고 있어야 된단 말이에요.

그러면 깔끔하게 하나씩 값을 넣고 저장을 하겠죠.

자 그런데 여러분 이게 객체다운 모델링은 아닐 수 있어요. 왜냐면 생각해보세요. 객체는 뭘로 연관관계를 맺는다? 객체는 참조로 연관관계를 맺는단 말이에요. 자 그래서 보시면, 이 클래스 Member에 뭐가 있습니까?

이전에는 teamId를 가지고 있었는데 객체 답게 모델링을 하게 되면,

Member는 team의 참조를 가지고 있게 됩니다. 레퍼런스 참조라고 얘기를 하죠. 그래서 필요하면 그냥 member.team 이나, member.getTeam()으로 해서 바로 Team을 꺼낼 수 있어야 되죠. 이렇게 해야 이게 객체다운 모델링인데 문제가 있어요.

이렇게 객체 답게 모델링을 하게 되면 데이터베이스에 insert 하기가 굉장히 까다로워집니다. insert 쿼리를 만들 때 이렇게 짜야돼요.
insert into member 인데 MEMBER_ID, TEAM_ID, USERNAME이 필요하죠. 지금 MEMBER_ID랑 USERNAME은 있는데 TEAM_ID가 없잖아요.

지금 Member 객체를 보시면 Member 클래스에 지금 Team에 대한 참조만 있지 Team의 Id가 없잖아요.
물론 대안이 있습니다. Member에서 Team을 조회 해요. 그러면 그 Team의 Id가 현재 Team의 Foreign Key가 되기 때문에 member.getTeam().getId()로 Id를 가지고 오면 그게 곧 Team의 PK를 가져온 거니까, 이걸 FK로 쓸 수 있겠죠. 그래서 로직에 member.getTeam().getId() 라고 적어야 되는데 이런 부분들이 굉장히 번잡해지죠.
자 그래서 뭐 등록하는 거야 어찌저찌 하면 되는데,

조회할 때가 참 쉽지 않습니다.

우선 이 그림에서 제가 지금 Member랑 Team이 연관관계가 있는 상태로 가지고 오고 싶단 말이에요. 그러면 어떻게 조회를 해야되느냐, 일단 Member랑 Team 둘 다 조회를 해야 돼요.
원래는 우리가 객체를 테이블에 맞춰서 모델링 하다가,

지금 이렇게 객체답게 모델링을 했단 말이에요. 그래서 Member가 Team이라는 참조로 연관관계를 맺도록 다 설계를 해놨어요.

자 그렇게 되면 조회할 때, 일단 데이터베이스 테이블에서는 성능을 위해서 보통 한 번에 조회를 하죠. 그러면 Member랑 Team을 Join해요. 그래서 Member랑 Team을 한 번에 가져옵니다. 그 다음에 SQL 실행을 하고, Member 객체 만들고 Team 객체를 만들어서, 데이터베이스에서 가져온 관련 데이터들을 다 세팅한 다음에 어떻게 합니까?
member.setTeam(team); 해가지고 Team을 또 세팅해줘야 됩니다. 그래서 Member와 Team의 관계를 설정해줘야 돼요. 이게 굉장히 번거롭습니다.
자 그런데 객체인 Member랑 Team을 DB에 보관하는 게 아니라,

그냥 Java Collection에서 관리한다고 생각해 볼게요.
그러면 Member랑 Team에 연관관계를 걸어 놨어요. 그 다음에 list.add 해서 Member만 넣으면 이 Member랑 Member와 연관관계가 있는 Team이 다 이 컬렉션에 들어가게 되겠죠. 물론 Member만 들어가지만 참조로 그 Team도 다 찾을 수 있겠죠. 그래서 이 한 줄로 그냥 Member를 딱 보관할 수 있는 거예요.
그럼 Member를 꺼내고 그 다음 Member와 연관된 Team이 필요하다고 할 때, SQL로 가져올 필요 없이 member.getTeam() 하면 그 Team도 바로 꺼낼 수 있는 거예요. Java 객체 세상 안에서는 이 한 줄로 다 가능해집니다.
그래서 우리가 db의 객체를 관리할 때는 굉장히 번거로워요. 왜냐? 이 객체랑 db간의 패러다임의 불일치들이 있기 때문에 서로 연관관계를 바라보는 방법이 달라요.
그런데 객체 세상 안에서는 Java Collection에 저장하기 때문에 어차피 객체 안에서잖아요. 객체 안에서 그냥 동작하는 거니까 Object 세상 안에서는 그냥 넣고 빼고 member.getTeam() 해서 꺼낼 수 있고 한 줄로 다 할 수 있는 거죠. 복잡한 변환 과정이 필요없습니다.
자 그리고 또 하나가 있습니다.

객체는 사실 자유롭게 객체 그래프 라는 걸 탐색할 수 있어야 돼요. 자 뭐냐면, member.getTeam(), member.order, orderItem.item, item.category 이렇게 그냥 참조로 쩜쩜쩜 해서 따라갈 수 있어야 되거든요.
자 그런데 데이터베이스에 객체를 보관하다 보면 어떤 문제가 생기냐면,

내가 처음에 어떤 sql을 실행해서 Member 객체를 만들었냐에 따라서,

이 탐색 범위가 결정이 되어버려요.

자 무슨 말이냐면 예를 들어서 query를 작성을 합니다. Member랑 Team을 조회하는 query를 작성을 했어요. 그래서 Member랑 Team을 Join으로 딱 연관관계 세팅을 해놨어요. 자 그러면 member.getTeam() 하면 이건 되겠죠. 그런데 member.getOrder()라고 해봐요.
물론 Member 안에 order라는 필드가 있습니다. 그런데 member.getOrder() 하면 거기는 null이겠죠. 왜냐면 내가 DB에서 조회를 안 했잖아요. 처음 실행하는 SQL에 따라서 이 탐색 범위가 딱 결정이 돼버려요.
우리가 보통 DAO나 리포지토리에 이걸 어디까지 세팅을 해놓느냐에 따라서 탐색 범위가 결정이 되버립니다.
이러면 어떤 문제가 생기냐면,

이 엔티티에 대한 신뢰 문제가 생겨요. 우리가 보통 계층 아키텍처라고 하죠. 우리가 컨트롤러, 서비스, 리포지토리 뭐 또는 DAO 이런식으로 이제 하는데 계층의 아키텍처라는 건 내가 다음 계층을 믿고 쓸 수 있어야 되거든요. 뭔가 데이터를 주면 이거에 대해서 믿고 쓸 수 있어야 돼요. 그래야 계층이라는게 성립을 하죠.
그래서 memberDAO.find() 해서 memberId로 Member를 딱 꺼냅니다. 여러분 생각해보세요. 이 memberDAO는 내가 만든 게 아니라 다른 개발자가 만들었어요.
자 그런데 내가 여기서 '어? memberDAO에 find가 있네?' 하고 memberId를 던졌어요. 그럼 Member 객체가 반환이 되겠죠. 그럼 내가 거기서 member.getTeam()이라고 할 수 있을까요? 또는 member.getOrder().getDelivery() 라고 이 객체 그래프를 자유롭게 탐색할 수 있을까요?
아마도 개발 많이 해보셨던 분들은 '아 이거는 여기 데이터가 어떻게 되어있는지 저 memberDAO의 find 안을 봐야 될 것 같은데' 라는 생각이 들죠. 맞아요. memberDAO의 find 안에서 SQL을 어떻게 했는지에 따라서 탐색범위가 달라지는 거예요. 그래서 다음 계층을 코드를 들어가서 까봐야 돼요.
나는 지금 서비스 로직을 짜는데, 이 DAO 코드를 다 열어봐야 돼요. DAO에서 어떤 쿼리를 가지고 세팅을 했는지를 봐야 '아 여기는 Team까지만 조회할 수 있겠구나' 이거를 다 할 수가 있습니다.

근데 그렇다고 이 모든 객체를 미리 다 로딩을 할 수 있을까요?

예를 들어서 믿고 쓰려면 내가 그냥 memberId를 넘겨서 Member 객체를 찾았어요. 그럼 member.getTeam()도 성공하고 member.getOrder()도 성공하고 member.getOrder().getDelivery()도 다 성공할 수 있을까요?
물론 Member가 주문도 있고 배송도 있고 Team도 있고 데이터를 가지고 있던 전제 하에 DAO 안에서 어떻게 쿼리를 작성해주냐, 이 문제예요.

여기서 그러면 데이터가 다 있는 유저는 이 객체 그래프 참조를 다 따라갈 수 있게 해줄 수 있을까요?
그래서 member.getOrder(), member.getOrder().getOrderItem() 해서 마음대로 참조할 수 있을까요?
이거는 어렵죠. 그러면은 SQL 쿼리가 어마어마하게 나와야겠죠. 그래서 막 다 Join 해서 무시무시한 쿼리가 나오고 성능도 안나오겠죠. 사용하지도 않는 데이터를 무조건 한번에 다 퍼올려놔야 되니까.
그래서 보통 어떻게 개발하냐면 이렇게 합니다.

우리가 SQL을 많이 쓰게 되면 memberDAO.getMember() 이런 걸 만들면 얘는 Member만 조회하는 거에요. 그 다음에 MemberDAO.getMemberWithTeam() 이라고 메서드 명을 만들어요. 그러면 걔는 Member랑 Team까지 세팅이 되있겠구나 하는 거죠.
자 여러분 여기까지는 괜찮은데 이 케이스가 지금 한 두개가 아니잖아요. Member랑 Order, Delivery 다 조회해야되면 memberDAO.getMemberWithOrderWithDelivery() 이렇게 짜야되는거에요. 그렇다고 모든 객체를 미리 로딩할 수는 없죠.
자 그래서 이제 말씀드리고 싶은 건,

진정한 의미의 계층 분할이 어려워요. 저는 이렇게 표현하는데 물리적으로는 분할이 되어 있으나 논리적으로는 엮여있다고 말합니다.
자 이제 비교하기 인데,

이거는 뭐냐면 우리가 일반적으로 SQL을 사용할 때는 이렇게 씁니다. 여러분 memberId가 100이라는 회원이 있어요. 그럼 이 100 이라는 회원을 제가 두 번 꺼내요. memberDAO.getMember() 해서 지금 두 번째 라인에서 100번을 넣어서 member1번을 꺼냅니다. 그 다음에 memberDAO.getMember() 해서 또 100번을 넣어서 member2번을 꺼냅니다. 그 다음에 우리가 == 비교라고 하죠. == 비교를 했을 때 이 인스턴스가 같을까요, 다를까요?
자 이 인스턴스는 당연히 다르다고 나올 거에요. 왜냐하면 보통 getMember() 라고 하면 어떻게 합니까? 밑에 로직을 적어 놨는데

보통 SQL query를 짜고 조회해온 데이터를 다 때려 박아서 만든 새로운 Member 객체를 반환하죠. 그래서 새로운 인스턴스가 생깁니다.
결과적으로 이 비교하기 에서는 member1과 member2의 데이터는 같지만 실제 인스턴스는 다른 거죠. 인스턴스가 2개 생성된 겁니다.
자 그런데 이 비교하기를 Java Collection에서 한다고 생각해 봅시다.

내가 데이터를 DB가 아니라 Java Collection에 보관을 해요. 예를 들어서 list.get 해서 100을 넣어요. 그러면 member1이 조회가 돼요. list.get 해서 100을 조회하게 되면 member2가 조회가 되는데 Collection에서 같은 인스턴스를 조회하게 되면, 걔는 같은 참조로 나오게 되겠죠. 진짜 똑같은 거예요.
그래서 Java Collection에서 조회를 하게 되면 얘는 == 비교를 했을 때 같다 라고 나옵니다. 이런 차이들이 생기는 거죠.

자 그래서 사실 드리고 싶은 말씀은 뭔가 객체 지향적으로 우리가 설계를 한다고 하는데, 앞서 말씀드렸듯이 상속관계도 쓰고 여러가지 참조도 쓰고 연관관계도 쓰고 하는데 객체 답게 모델링을 하면 할수록 이 맵핑 작업이라는 게 어마어마하게 늘어나요.
그래서 우리가 막 이론적으로는 되게 객체 지향적으로 설계하는게 좋다라고 하지만, 실제 해보면 그게 안 돼요. SQL로 전환하는 과정에 비용이 너무 많이 드는 거에요. 그래서 보통 이런 객체지향 설계들을 많이 포기를 하게 되죠.
자 여러분 이런 고민을 우리만 했겠습니까?

그래서 굉장히 많은 사람들이 객체를 Java 컬렉션에 저장하듯이, 컬렉션에 저장하고 빼고 하는 건 굉장히 단순하잖아요. 근데 이거를 DB에다가 넣었다 뺐다 하는 순간 쿼리를 다 짜야되고 불일치가 발생하고 막 할 일이 너무 많아지는 거죠.
그래서 '객체를 Java Collection에 저장하듯이 DB에 저장할 수는 없을까?' 이런 고민이 계속 있어 왔습니다.

그리고 앞서 설명드린 문제를 해결하는 게 바로 Java Persistence API, JPA라고 하는 기술입니다.
다음 시간에는 이 JPA에 대해서 소개를 해드리겠습니다.
'스프링 > 자바 ORM 표준 JPA 프로그래밍 - 기본편' 카테고리의 다른 글
| 영속성 컨텍스트 1 (0) | 2024.05.25 |
|---|---|
| Hello JPA - 애플리케이션 개발 (0) | 2024.05.25 |
| Hello JPA - 프로젝트 생성 (0) | 2024.05.25 |
| JPA 소개 (0) | 2024.05.24 |
| 강좌 소개 (0) | 2024.05.23 |