본문 바로가기
Study/spring

자바 ORM 표준 JPA 프로그래밍(1) - SQL을 직접 다룰 때 발생하는 문제점, 패러다임 불일치

by 유경호 2020. 11. 16.
반응형

SQL 매퍼(Mapper)를 사용하면 발생할 수 있는 문제점

  • 데이터베이스와 객체간의 매핑 코드 작성이 반복적으로 이뤄짐
  • CRUD용 SQL이 반복적으로 작성된다.
  • SQL에 의존적이기 때문에 테이블에 새로운 요구사항이나 칼럼이 추가되면 관련된 모든 SQL 코드에 수정이 요구됨.

JPA를 사용하면 해결할 수 있는 문제점

  • CRUD SQL을 작성할 필요가 없다.
  • 조회된 결과를 객체에 매핑하는 작업을 자동으로 처리한다.
  • SQL이 아닌 객체 중심으로 개발하여 생산성과 유지보수가 편리해짐
  • 데이터베이스 교체에 대한 비용이 줄어듬
  • SQL을 작성하지 않고 객체간의 관계만 명확히하여 데이터베이스 로직을 처리할 수 있어 비지니스 로직에 더욱 집중할 수 있게 해줌.

SQL을 직접 다룰 때 발생할 수 있는 문제점

관계형 데이터베이스는 가장 대중적이고 신뢰할 수 있는 데이터 저장소이다.

자바로 개발되는 어플리케이션의 대부분이 관계형 데이터베이스를 사용한다.

자바로 작성한 어플리케이션은 JDBC API를 사용해서 SQL을 데이터베이스에 전달한다.

반복적인 작업

SQL을 직접 다룰 때의 문제점을 보기 위해 회원 테이블과 아래와 같은 객체들이 이미 존재한다고 가정해본다.

회원 객체

1
2
3
4
5
public class Member {
    private String memberId;
    private String name;
    ...
}
cs

 

회원용 DAO

1
2
3
4
public class MemberDAO {
    pulbic Member find(String memberId) { ... }
    public void save(Member member) { ... }
}
cs

 

회원 객체는 회원 테이블의 칼럼과 매핑시킬 객체이다. 그리고 회원용 DAO는 회원 객체를 데이터베이스에 관리할 객체이다.

 

MemberDAO에 find() 메소드를 완성하여 회원 조회 기능을 만들고자 한다면 아래와 같은 절차로 개발을 진행해야한다.

  1. 회원 조회용 SQL을 작성한다
    String sql = "SELECT MEMBER_ID, NAME FROM MEMBVER M WHERE MEMBER_ID = ?";
  2. JDBC API를 사용해서 SQL을 실행한다.
    ResultSet rs = stmt.executeQuery(sql);
  3. 조회 결과를 Member 객체로 매핑한다.
    String memberId = rs.getString("MEMBER_ID");
    String name = res.getString("NAME");

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

또한, MemberDAO에 save 메소드를 완성하여 회원 등록 기능을 만들고자 하면 아래와 같은 절차로 개발이 된다.

  1. 회원 등록용 SQL을 작성한다
    String sql = "INSERT INTO MEMBER(MEMBER_ID, NAME) VALUES(?,?)";
  2. 회원 객체의 값을 꺼내서 등록 SQL에 전달한다.
    pstmt.setString(1, member.getMemberId());
    pstmt.setString(2, member.getName());
  3. JDBC API를 사용하여 SQL을 실행한다.
    pstmtm.executeUpdate(sql);

위와 같은 절차를 지나고서야 CRUD 중 C(Create)와 R(Read) 기능이 개발이 된다. 그럼 나머지 U, D는 어떨까 생각해보라. 아마도 SQL을 작성하고 JDBC API를 사용하는 비슷한 일을 반복해야 할 것이다. 또한 회원 테이블이 아닌 다른 테이블에 대한 CRUD를 작성할 때는 어떨까? 마찬가지로 위와 같은 비슷한 일을 또 반복하여 개발해야 될 것이다.

 

데이터베이스는 객체 구조와 다른 데이터 중심 구조를 가지므로 객체를 데이터베이스에 직접 저장하거나 조회할 수 없다. 따라서 개발자가 자바와 데이터베이스 사이에서 SQL과 JDBC API를 사용해서 변환 작업을 직접 해주어야 한다.

이는 매우 반복적인 일이며 테이블이 늘어나면 늘어날수록 해야하는 똑같은 작업은 늘어난다. 이는 데이터베이스 접근 계층(DAO)을 개발하는 개발자들에겐 상당히 지루한 노동이다.

패러다임의 불일치

객채와 관계형 데이터베이스는 지향하는 목적이 서로 다르므로 둘의 기능과 표현 방법이 다르다.

관계형 데이터베이스는 데이터 중심으로 구조화되어 있고, 집합적인 사고를 요구한다. 그리고 객체지향에서 이야기하는 추상화, 상속, 다형성 같은 개념이 없다.

이런 객체와 관계형 데이터베이스 사이의 패러다임 불일치 문제를 통해 발생하는 문제들을 살펴보자.

상속

객체에는 상속이라는 기능을 가지고 있지만 테이블은 상속이라는 기능이 없다.

 

JPA와 상속

JPA는 상속과 관련된 패러다임의 불일치 문제를 개발자 대신 해결해준다. 개발자는 마치 자바 컬렉션에 객체를 저장하듯이 JPA에게 객체를 저장하면 된다.

연관관계

객체는 참조를 사용해서 다른 객체와 연관관계를 가지고 참조에 접근해서 연관된 객체를 조회한다.

반면에 테이블은 외래 키를 사용해서 다른 테이블과 연관관계를 가지고 조인을 사용해서 연관된 테이블을 조회한다.

1
2
3
4
5
6
7
8
9
10
class Member {
    Team team;
    String id;
    String username;
}
 
class Team {
    Long id
    String name;
}
cs

 

객체는 참조가 있는 방향으로만 조회할 수 있다. member.getTeam()은 가능하지만 team.getMember()는 참조가 없으므로 불가능하다. 반면에 테이블은 외래 키 하나로 MEMBER JOIN TEAM도 가능하지만 TEAM JOIN MEMBER도 가능하다.

 

JPA와 연관관계

JPA는 연관관계와 관련된 패러다임의 불일치 문제를 해결해준다.

 

JPA와 객체 그래프 탐색

JPA는 연관된 객체를 사용하는 시점에 적절한 SELECT SQL을 실행한다. 이 기능은 실제 객체를 사용하는 시점까지 데이터베이스 조회를 미룬다고 해서 지연 로딩이라고 한다.

1
2
3
4
5
// 처음 조회 시점에 SELECT MEMBER SQL
Member member = jpa.find(Member.class, memberId);
 
Order order = member.getOrder();
order.getorderDate(); // Order를 사용하는 시점에 SELECT ORDER SQL
cs

 

위 코드를 보면 마지막 줄의 order.getOrderDate() 같이 실제 Order 객체를 사용하는 시점에 JPA는 데이터베이스에서 ORDER 테이블을 조회한다.

 

다만 Member를 사용할 때마다 Order를 함께 사용한다면 이렇게 한 테이블씩 조회하는 것보다는 Member를 조회하는 시점에 SQL 조인을 사용해서 Member와 Order를 함께 조회하는 것이 효과적이다.

JPA는 연관된 객체를 즉시 함께 조회할지 아니면 실제 사용되는 시점에 지연해서 조회할지를 간단한 설정으로 정의할 수 있다.

비교

데이터베이스는 기본 키의 값으로 각 로우(row)를 구분한다.

객체는 동일성(identity) 비교와 동등성(equality) 비교라는 두 가지 비교 방법이 있다.

  • 동일성 비교는 == 비교, 객체 인스터스의 주소 값을 비교한다.
  • 동등성 비교는 equals() 메소드를 사용해서 객체 내부의 값을 비교한다.

따라서 기존의 방식으로 데이터베이스에서 데이터를 조회해 객체로 담았을 때, 동일한 키 값으로 조회한 로우(row)여도 객체를 통해 동일성 비교를 하기는 어렵다. 객체 측면에서는 각 객체는 다른 인스턴스이기 때문이다.

 

JPA와 비교

JPA는 같은 트랜잭션일 때 같은 객체가 조회되는 것을 보장한다. 그러므로 다음 코드에서 memeber1과 member2는 동일성 비교에 성공한다.

1
2
3
4
5
String memberId = "100";
Member member1 = jpa.find(Member.class, memberId);
Member member2 = jpa.find(member.class, memberId);
 
member1 == member2 // 같다.
cs

객체 비교하기는 분산 환경이나 트랜잭션이 다른 상황까지 고려하면 더 복잡해진다.

정리

객체 모델과 관계형 데이터베이스 모델은 지향하는 패러다임이 서로 다르다. 문제는 이 패러다임의 차이를 극복하려고 개발자가 너무 많은 시간과 코드를 소비한다는 점이다.

JPA는 패러다임의 불일치 문제를 해결해주고 정교한 객체 모델링을 유지하게 도와준다.

반응형