[자바 ORM 표준 JPA 프로그래밍] JPA 소개

may.log·2022년 3월 18일
1
post-thumbnail

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

1. 무한 반복, 지루한 코드

JPA를 사용하지 않고,SQL과 JDBC를 사용해서 회원 객체를 생성하고, 회원을 찾는 코드는 다음과 같다.

public class Member{
	private String memberId;
    private String name;
}
public class MemberDAO {
	public Member find(String memberId){...}
}

MemberDAO의 find() 메소드 작성해보자.

  1. 회원 조회용 SQL을 실행한다.
    SELECT MEMBER_ID, NAME FROM MEMBER M WHERE MEMBER_ID = ?
  1. JDBC API를 사용해서 SQL을 실행한다.
    ResultSet rs = stmt.executeQuery(sql);

  2. 조회 결과를 Member 객체로 매핑한다

String memberId = rs.getString("MEMBER_ID");
String name = rs.getString("NAME");

Member member = new Member();
member.setMemberId(memberId);
member.setName(name);

회원 조회하는 기능을 만들었다.
등록, 수정, 삭제하는 기능도 SQL을 작성하고, JDBC API를 사용하는 비슷한 일을 반복해야 할 것이다.
데이터베이스는 객체 구조와는 다른 데이터 중심의 구조를 가지므로 객체를 데이터베이스에 직접 저장하거나 조회할 수 없다.
따라서 개발자가 객체 지향 애플리케이션과 데이터베이스 중간에서 SQL과 JDBC를 사용해서 변환 작업(객체 -> 데이터 구조)을 직접 해주어야 한다. 이 작업을 JPA가 대신 해줘서 개발자는 객체지향 개발에 더 집중할 수 있다.

2. SQL에 의존적인 개발의 문제점

🔎 1) 등록 코드 변경

위에 작성한 Member 필드에 연락처 컬럼을 추가하면, insert, select, update, delete SQL 쿼리도 맞춰서 다 수정해야 하는 불편함이 있다.

public class Member{
	private String memberId;
    private String name;
    private String tel;	//연락처 컬럼 추가 
}
String sql = "SELECT MEMBER_ID, NAME, TEL FROM MEMBER WHERE MEMBER_ID = ?";

Member 클래스에 연락처 컬럼을 추가해서 SELECT 쿼리도 그에 맞게 수정한다.

String tel = rs.getString("TEL");
member.setTel(tel)

Member 객체를 매핑할 때도 그에 맞게 수정해야 하는 불편함이 있습니다.

🔎 2) 연관된 객체

회원은 어떤 한 팀에 필수로 소속되어야 한다는 요구사항이 추가되었다.

class Member{
	private String memberId;
    private String name;
    private String tel;
    private Team team;	//추가
    
    //Team 객체 반환 
    public Team getTeam(){
    	return team;
    }
}

//추가된 팀
class Team{
	private String teamName;
}

member.getTeam(); 을 실행하면 값이 항상 null이다.
MemberDAO 클래스를 열어서 Member를 조회하는 find() 메소드를 보니 위에 작성한 회원만 조회하는 "SELECT MEMBER_ID, NAME, TEL FROM MEMBER M WHERE MEMBER_ID = ?" SQL을 그대로 유지했다.
회원과 연관된 팀을 함께 조회하는 findWithTeam()메소드에 다음과 같은 sql문을 추가하자.

public class MemberDAO {
	public Member find(String memberId){...}
    public Member findWithTeam(String memberId){...}
}

<회원 + 팀 조회 : Member, Team 조인>

SELECT M.MEMBER_ID, M.NAME, M.TEL, M.TEAM_ID, T.TEAM_NAME 
FROM MEMBER M
JOIN TEAM T
	ON M.TEAM_ID = T.TEAM_ID

결국 MemberDAO 클래스를 열어서 SQL을 확인하고 나서야 null 값이 나오는 원인을 알 수 있었고,
회원 조회 코드를 MemberDAO.find()에서 MemberDAO.findWithTeam()으로 변경해서 문제를 해결해야 한다.

정리:
Member 객체가 연관된 Team 객체를 사용할 수 있을지는 사용하는 SQL에 달려있다.
데이터 접근 계층을 사용해서 SQL을 숨겨도 어쩔 수 없이 DAO를 열어서 어떤 SQL이 실행되는지 확인해야 한다는 문제가 있다. = 엔티티를 신뢰할 수 없다

3. 정리

  • 진정한 의미의 계층 분할이 어렵다.
  • 엔티티를 신뢰할 수 없다.
  • SQL에 의존적인 개발을 피하기 어렵다.

4. JPA와 문제 해결

JPA를 사용하면 객체를 데이터베이스에 저장하고 관리할 때, 개발자가 직접 SQL을 작성하는 것이 아니라 JPA가 제공하는 API를 사용하면 된다. 그러면 JPA가 개발자 대신에 적절한 SQL을 생성해서 데이터베이스에 전달한다.

jpa.persist(member);	//저장 -> .persist()
jpa.find(Member.class, "helloId");	//조회 -> find()

Member member = jpa.find(Member.class, memberId);
member.setName("변경");	//수정

Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam();	//연관된 객체 조회
, sql문 확인 필요 X

수정 기능-> 객체를 조회해서 값을 변경하면 트랜잭션 커밋할 때 update SQL이 데이터베이스에 전달됨. 변경 감지(Dirty Checking)

📌 패러다임의 불일치

1. 상속



Album 객체를 저장하려면 이 객체를 분해해서 다음 두 SQL을 만들어야 한다.

INSERT INTO ITEM...
INSERT INTO ALBUM...

JDBC API를 사용해서 이 코드를 완성하려면 부모 객체에서 부모 데이터만 꺼내서 ITEM용 INSERT SQL을 작성하고, 자식 객체에서 자식 데이터만 꺼내서 ALBUM용 INSERT SQL을 작성해야 하는데, 작성해야 할 코드량이 만만치 않다.
조회할 때도 ITEM과 ALBUM 테이블을 조인해서 조회한 수 ALBUM 객체를 생성해야 한다.
이런 과정이 모두 패러다임 불일치를 해결하려고 소모하는 비용이다.

2. JPA와 상속

JPA는 상속과 관련된 패러다임의 불일치 문제를 개발자 대신 해결해준다.
저장

조회

3. 연관관계

객체는 참조를 사용해서 다른 객체와 연관관계를 가지고 조회한다.
반면에 테이블은 외래 키를 사용해서 다른 테이블과 연관관계를 가지고, 조인을 사용해서 조회한다.
객체는 참조가 있는 방향으로만 조회 가능하다. member.getTeam()은 가능하지만 반대 방향인 team.getMember()은 불가능하다.
반면에 테이블은 외래 키 하나도 MEMBER JOIN TEAM, TEAM JOIN MEMBER이 가능하다.

class Member {

	String id;
	Team team;
	String username;

}

class Team{

	long id;
    String name;
 }

JPA는 team의 참조를 외래 키로 변환해서 적절한 INSERT SQL을 데이터베이스에 전달한다. 객체를 조회할 때 외래 키를 참조로 변환하는 일도 JPA가 처리해준다.

member.setTeam(team)	//회원과 팀 연관관계 설정
jpa.persist(member)		//회원과 연관관계 함께 저장

Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam();

4. 객체 그래프 탐색

객체는 자유롭게 객체 그래프를 탐색할 수 있어야 한다. 하지만 처음 실행하는 SQL에 따라 탐색 범위가 결정된다.
예를 들어, MemberDAO에서 member객체를 조회할 때 아래의 SQL을 실행하면 member.getTeam()은 성공하지만, member.getOrder()을 null을 반환한다.

즉, SQL을 직접 다루면 처음 실행하는 SQL에 따라 객체 그래프를 어디까지 탐색할 수 있는지 정해진다.

MemberService는 memberDAO를 통해 member객체를 조회했지만, 이 객체와 연관된 Team, Order, Delivery 방향으로 객체 그래프를 탐색할 수 있는지는 이 코드만 보고 전혀 예측할 수 없다! 결국 DAO 코드를 열어서 SQL를 직접 해결해야 한다.

5. JPA와 객체 그래프 탐색

그렇다면 JPA는 이 문제를 어떻게 해결할까?
JPA는 연관된 객체를 사용하는 시점에 적절한 SELECT SQL을 실행한다. 따라서 JPA를 사용하면 객체 그래프를 마음껏 탐색할 수 있다.(지연로딩)

6. 비교


member1, 2는 같은 데이터베이스 로우에서 조회했지만, 객체 측면에서 볼 때 둘은 다른 인스턴스이다.

7. JPA와 비교


JPA는 같은 트랜잭션일 때 같은 객체가 조회되는 것을 보장한다.

📌 JPA란 무엇인가?

JPA는 자바 진영의 ORM 기술 표준으로 애플리케이션과 jdbc 사이에서 동작한다.


JPA를 사용해서 객체를 저장하는 코드는 다음과 같다.

jpa.persist(member);

조회할 때도 JPA를 통해 객체를 직접 조회하면 된다.

JPA를 사용해서 객체를 조회하는 코드는 다음과 같다.

Member member = jpa.find(memberId);

ORM 프레임워크는 단순히 SQL을 개발자 대신 생성해서 데이터베이스에 전달해주는 것뿐만 아니라 패러다임 불일치 문제들도 해결한다.
따라서 객체 측면에서 정교한 객체 모델링이 가능하고, 관계형 데이터베이스는 데이터베이스에 맞도록 모델링하면 된다. 그리고 둘을 어떻게 매핑해야 하는지 매핑방법만 알려주면 된다.
즉 개발자는 객체지향 애플리케이션 개발에 더 집중할 수 있다!

이 글은 김영한님의 자바 ORM 표준 JPA 프로그래밍 책을 참고하였습니다.

0개의 댓글