naspeciallist 2025. 5. 1. 22:42

 

본 글은 https://www.inflearn.com/course/ORM-JPA-Basic/dashboard 강의를 바탕으로 작성한 글입니다.

 

1. 기존 객체의 CRUD


public class Member {

private String memberId;

private String name;

private String tel;

}

 

이런 member클래스가 있다고 하자 그러면 이 데이터를 조회하기 위해서는 쿼리문을 각각 다음과 같이 작성해야 한다.

Insert into member(member_ID, Name, TEL) values 

select MEMBER_ID, NAME, TEL FROM MEMBER M 

UPDATE MEMBER SET … TEL = ?

 

 근데 만약 주소 필드를 추가로 작성해야 한다면 이미 작성한 쿼리마다 주소 필드에 대한 값을 추가해 줘야 해서 각각의 쿼리를 모두 수정을 해야 한다는 불편함이 있다. 그래도 어쨌든 관계형 SQL과 통신을 할려면 결국 SQL을 사용할 수 밖에 없다.

 

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

 

 

2. 객체와 테이블에서의 CRUD


 

 

객체를 영구보관하는 다양한 저장소들이 있다. 회원 오브젝트를 어딘가에는 저장을 해야한다. 현실적으로 우리가 할 수 있는 가장 최선의 선택은 관계형 데이터베이스이다.

 

결과적으로 개발자는 객체를 SQL로 변환하여 RDB에 저장하거나 불러오는 일을 하는 일이다.

 

객체의 상속관계는 기본적으로 관계형 데이터베이스는 존재하지 않는다 하지만 관계형 데이터 베이스에서는 슈퍼타입 서브타입 기법으로 객체의 상속관계를 구현할 수 있다.

 

DB에 만약 상속관계를 구현을 하게 된다면 데이터를 조회하는 과정이 훨씬 복잡해진다. 각각의 데이터를 모두 알아야 하고 각 테이블에 따른 조인 SQL을 작성하고 각각의 객체를 생성해야한다. 그래서 DB에 저장할 객체에는 상속 관계를 사용하지 않는다.

 

하지만 자바 컬렉션에 저장을 한다고 가정을 했을 때 list.add()라는 명령어로 저장을 할 수 있다.(객체라고 가정했을 때) 부모 타입으로 조회 후 다형성을 활용 할 수 있다.

객체는 참조를 사용해서 연관관계를 맺는다. 테이블은 외래키를 사용해서 조인을 해야한다.

테이블은 참조라는게 없다 외래키를 사용한다. 그래서 FK를 가지고 있다. 이런것 때문에 문제가 발생한다.

만약 멤버객체랑 팀 객체를 테이블에 저장을 한다고 할 때 객체를 테이블에 맞춰서 모델링을 많이 하게 된다.

 

이렇게 하는 이유는 나중에 insert문 같이 쿼리문을 짤 때 수월하게 하기 위해서 이다. 하지만 이건 객체다운 모델링이 아니다. 왜냐하면 객체는 참조관계로 연관관계를 맺기 때문이다.

객체답게 모델링하게 될려면 team id대신 team이라는 참조를 가지고 있어야한다.

 

이렇게 객체다운 모델링을 하게 되면 데이터베이스에 인서트하기가 굉장히 까다로워진다.

 

만약에 객체에서 참조관계인 Team의 ID를 가져올려고 이렇게 insert문을 구성하였다. 하지만 이렇게 되면 문제가 생긴다. 맴버 객체를 보면 멤버 클래스에 지금 팀에 대한 참조만 있지 팀의 아이디가 없는 상태가 된다. 여기서 팀의 아이디를 가져올려면 멤버에서 멤버 아이디를 조회한 뒤 그걸 이제 가져와야한다. 조회 할 때도 마찬가지로 훨씬 복잡해진다.

조회할 때 먼저 멤버랑 팀을 조회한 뒤 그거를 멤버랑 팀을 한번에 가져오게 된다. 그 다음에 멤버객체 만들고 팀객체만들고 각 데이터를 모두 세팅해 준뒤에 그 다음에 member.setTeam을 통해 객체만드는 데서 또 팀을 세팅을 해줘야한다.

 

만약 이걸 java 컬랙션이 보관한다고 했을 때 멤버랑 팀에 연관관계가 걸려있고 list.add에서 멤버만 넣으면 이 멤버랑 멥버와 같이 연계있는 팀이 다 이컬렉션에 결국 들어가게 된다. 그래서 코드 한줄로 멤버를 보관할 수 있게 된다.

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

 

그 다음에 멤버를 조회할 때도 멤버를 꺼내고 그 다음 멤버와 연관된 팀이 필요하다고 하면 member.getTeam()이라는 코드 한 줄로 해결이 가능해진다.

 

위처럼 객체와 db와의 패러다임의 불일치 때 문에 db에서 객체를 관리하게 되면 훨씬 복잡해진다. 연관관계를 바로보는 관점이 다르기 때문이다. 하지만 java안에 객체의 세상에서는 복잡한 변환가정 없이 자바 컬렉션안에 저장하기 때문에 객체안에서 동작을 하게 된다.

 

또한 객체는 객체 그래프 탐색이라는 걸 하게 된다. 객체는 자유롭게 그래프 탐색을 할 수 있게 되어야 한다. 하지만 데이터베이스에서 데이터를 객체로 보관하게 되면 처음 sql을 실행해서 이 멤버객체를 만들었냐에 따라 탐색범위가 결정되버린다.

 

SELECT M.* , T.*
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID

member.getTeam(); //OK
member.getOrder(); // null

 

예를들어서 위와 같은 코드가 있다. 멤버랑 팀의 연관관계를 세팅해 놓았다. 따라서 만약에 memeber.getTeam()을 하게 되면 쿼리문 안에 team라는 연관관계를 설정했기 때문에 조회가 된다 하지만 getOrder()를 했을 때 member내부에 order가 있을 지라도 내가 위 쿼리문에서 order를 조회하지 않았기 때문에 null 값이 나오게 된다.

따라서 처음 실행하는 SQL문에 따라 탐색범위가 결정이 되어버린다.

 

이렇게 되면 엔티티에 대한 신뢰문제가 발생하게 된다.

보통 계층의 아키텍쳐라는게 있는데 계층의 아키택쳐는 다음의 계층에 대한 신뢰가 있어야 사용을 할 수 있다. 하지만 다음의 계층에 데이터가 어떻게 되는지 직접 보지 않고는 알 수 가 없기 때문에 다음 계층을 신뢰 할 수가 없게 된다.

그렇다고 쿼리문을 복잡하게 작성하여 모든 객체를 미리 로딩할 수도 없다 그래서 보통은 상황에 따라 동일한 회원 조회 메서드를 여러개 생성하여 작성하게 된다.

이렇게 되면 진정한 의미에 계층 분할이 어렵게 된다.

 

비교할 때도 문제가 발생하게 된다.

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

member1 == member2; //다르다

class MemberDA) {

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

 

우리가 보통 sql을 사용할 때는 다음과 같이 쓰게 된다. 100번이라는 아이디가 있을 때 member에서 getMember()를 꺼내서 쓰면 member1과 member2가 다르게 된다. 밑에 sql로직 처럼 데이터를 가지고 있는 인스턴스가 서로 다르기 때문에 데이터는 같지만 실제로는 다르다고 나오게 된다.

 

하지만 자바 컬렉션에서 조회를 하게 되면 같다고 나오게 된다.

 

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

member1 == member2; //같다

 

컬렉션에서는 같은 인스턴스를 조회하게 되면 같은 참조로 나오게 되기 때문 == 비교를 했을 때 같다고 말할 수 있다.

 

객체답게 모델링 할 수록 매핑작업만 늘어나게 된다.

 

객체를 자바 컬렉션에 저장하듯이 DB에 저장을 할 수 있을까 라는 고민을 하게 되었고 그렇게 해서 나온게 JPA이다.