[JPA] - Jpa란

Gates·2022년 4월 11일
0

JPA

목록 보기
1/1
post-thumbnail

안녕하세요. 오늘은 Jpa에 대해서 알아보겠습니다.
Jpa는 Java Persistence API의 약자로, 해석하면 자바 영속성 API 라고 부를 수 있습니다.
이렇게 부르면 어려우실거라 생각하므로 자세히 설명을 하도록 하겠습니다.
JPA를 설명하기 위해서는 JPA가 나온 배경부터 설명을 드려야 할것 같습니다.

JAVA 진영의 웹 개발은 SpringFramework가 꽉 잡고 있었습니다. SpringFramework를 개발할 때 DB는 주로 SQL Mapper를 사용하는데, 가장 많이 사용하는 SQL Mapper는 아파치 재단의 iBatis(지금의 MyBatis)입니다. iBatis는 xml 형식의 sql문을 Java의 Interface와 맵핑을 시켜서 SQL문을 실행시키는 역할을 수행했습니다. 간단한 코드를 작성해보겠습니다.

public interface UserDao{
  List<UserDto> getUserList();
}

Dao는 Data Access Object의 약자로, mybatis의 xml 파일 내의 id값을 식별하여 어떤 SQL문과 맵핑시킬지 결정하는 인터페이스입니다.
이런 Dao인터페이스가 있을 때 아래와 같은 xml 파일의 SQL문을 맵핑 시켜줍니다.

<select id="getUserId" resultType="UserDto">
  select 
     user_id as userId
    ,user_nm as userNm
    ,...
  from user
</select>

그러다 문득 개발자들은 생각을 하게 됩니다.

객체 지향 언어인 JAVA로 개발하는데 왜 비즈니스 로직보다 SQL을 짜는데 더 많은 시간을 소요할까?
테이블 설계는 열심히 하면서 제대로 된 객체 모델링은 왜 하지않을까?

참 아이러니하죠? 분명 난 JAVA 개발자이고 객체지향 개발을 하는데 DBA도 아니고 테이블 만들고 SQL문 짜는데 대부분의 시간을 소요하게 된다니...
저도 현업이지만 실제 현업에서 Mybatis로 SpringFramework 개발자들도 공감하는 내용일겁니다. 실제 페이지 작업, 코드작업보다 SQL문 짜는게 더 오래걸렸습니다.
객체는 단순히 DB의 데이터를 전달하는 역할밖에 하지 못하는 겁니다. 객체지향의 장점을 희생하면서 얻을 수 있는 이점일까요? 많은 개발자들은 아니라고 생각했습니다.

이런 딜레마를 해결하기 위해 ORM이 등장하였고 JAVA진영의 ORM 표준이 JPA입니다. ORM은 또 무엇일까요? ORM은 Object Relational Mapping의 약자로 객체-관계 맵핑으로 해석하시면 되겠습니다. 어렵다는 생각이 드실텐데 전혀 어렵게 생각하실 필요가 없습니다. 단순히 우리가 사용하는 객체(Object)와 DB의 테이블을 맵핑하는 기술입니다.

기존 MyBatis를 사용할 때와 다를게 뭐가 있냐고요? 예를 들어 봅시다.
학생을 저장하고 가져오는 있다고 생각해봅시다. 학생 클래스는 아래와 같이 정의합니다.

@Getter
@Setter
public class Student {
  private String userId   ;
  private String userNm   ;
  private String password ;
  private int    grade    ;
}

이후에 학생을 저장하기 위한 학생 테이블을 만듭니다.
테이블 정의는 아래와 같습니다.

create table student (
  user_id  varchar(32) not null,
  user_nm  varchar(64) not null,
  password text		   not null,
  grade    integer     not null,
  constraint pk_student_user_id 
    primary key (user_id)
);

테이블을 만들었으면 추가, 수정, 조회 기능까지도 만들어야겠죠?
아래는 추가, 조회 SQL문입니다.

-- insert 
insert into student
  (user_id, user_nm, password, grade)
  values 
    ('hkd1443', '홍길동', {hash값}, 1);
-- select
select  user_id as userId
       ,user_nm as userNm
       ,password as password
       ,grade as grade
from student;

이정도만 해도 이미 객체 모델링, 테이블 설계, SQL문 작성 등의 작업을 거칩니다. 이상하죠? 이미 객체 모델링을 했는데 테이블까지 설계를 해야한다? 어찌보면 Student 클래스는 student 테이블의 데이터를 가져오는데 굳이 다르게 설계할 필요가 있을까요?
심지어 이건 예시로 조회, 추가만 구현해놓은 상태입니다. 실제 코드라면 정보를 수정하는 sql문도 있을 것이고, 비밀번호만 수정하는 sql문도 있을 것이고, 삭제하는 sql문도 있을 것입니다.
생각만 해도 오래걸리겠죠? 이 뿐만이 아닙니다. 만약 요구사항 중에 학생은 학과에 필수로 소속되어야 하고, 화면에서도 학과를 보여줘야 한다고 변경되었다고 하겠습니다. 그럼 Student 클래스는 다음과 같이 변경되겠죠.

 @Getter
@Setter
public class Student {
  private String     userId     ;
  private String     userNm     ;
  private String     password   ;
  private int        grade      ;
  private Department department ;
  
  public Department {
    ...
    private String departmentName;
    ...
  }
}

SQL문도 일부 변경을 해야겠죠. (학생 테이블 재정의 및 학과 테이블 정의는 생략하겠습니다. )

select  user_id as userId
       ,user_nm as userNm
       ,password as password
       ,grade as grade
from student student
join department dept on student.team_id = dept.team_id;

여기서 기존 체계의 문제가 발견되었습니다. Student에서 Deptartment를 사용할 수 있을지 없을지는 전적으로 SQL에 달려있습니다. 즉, Dao로 데이터 접근 계층을 사용해서 SQL을 숨겨도 어쩔수 없이 Dao를 열고 해당하는 메소드 이름을 찾고, xml에서 Dao 메소드 이름과 같은 SQL을 찾아야 합니다.
이처럼 JAVA 코드가 SQL문에 의존하게 되어 바람직한 소프트웨어의 기준인 낮은 결합도가 풀리게 되어 결합도가 높아지게 됩니다. 이런 높아진 결합도로 인해 Student의 필드가 한개 증가하면 select, insert, update 문을 일일히 수정해야 하는 문제가 발생합니다. 아까도 말씀드렸듯이, 이런 예시코드이기 때문에 실제 현업에서는 아래와 같은 작업을 진행해야 합니다.

  • 학생 테이블 재정의
  • 학과 테이블 정의
  • 학생 추가, 수정 SQL문 수정

단순히 클래스와 필드가 한개 추가되었을 뿐인데 수정할 부분이 너무나 많습니다.
JAVA 애플리케이션에서 SQL을 직접 다룰 때 발생하는 문제점을 요약하면 다음과 같습니다.

  • 진정한 의미의 계층 분할이 어렵다
  • 엔티티(비즈니스 요구사항을 모델링한 객체)를 신뢰할 수 없음
  • SQL에 의존적인 개발을 피하기 어려움

JPA는 이런 문제를 해결하기 위해 개발되었습니다.
위와 같은 프로세스를 JPA을 사용해 구현하면 다음과 같이 구현할 수 있습니다. (실제로는 학생 클래스에 UserName, UserId처럼 넣지 않고 이 객체의 Name, Id라는 의미를 부여하기 위해 Name, Id 이런식으로 사용합니다. )

Student student = new Student();
student.setUserId("hkd1443");
student.setUserName("홍길동");
student.setPassword(encoded);
student.setGrade(1);
// 저장
student = studentRepository.save(student);
// 수정
student.setUserName("김길동");
studentRepository.save(student);
// 조회
Student foundStudent = studentRepository.findByUserName("김길동");

위 코드에서 부서를 추가해보겠습니다.

Department department = new Department();
department.setDepartmentName("컴퓨터공학부");

Student student = new Student();
student.setUserId("hkd1443");
student.setUserName("홍길동");
student.setPassword(encoded);
student.setGrade(1);
student.setDepartment(department);
// 저장
student = studentRepository.save(student);
// 수정
student.setUserName("김길동");
studentRepository.save(student);
// 조회
Student foundStudent = studentRepository.findByUserName("김길동");
Department foundDepartment = foundStudent.getDepartment();

어떤가요? 훨씬 간편해졌나요? 객체 모델링과 테이블 설계를 제대로 된 객체를 설계함으로써 한번에 이루어졌습니다!
이게 바로 ORM입니다. DB와 객체를 따로 두지 않고 DB와 객체를 맵핑하여 DB데이터와 객체 데이터를 일치화 시키는 기술입니다.

물론 JPA라고 장점만 있는 것이 아닙니다. 단점 중 대표적인 한 개를 말씀드리자면, N+1 문제가 있습니다. 지금은 애플리케이션을 실행시키지 않아 로그를 안찍어 실제 JPA 내부에서 어떻게 작동하는지 모르지만, 실제 로그를 찍어보면 Student 엔티티를 조회할 때 Department까지 JOIN을 통해서 조회가 됩니다. 그러면 그만큼의 성능 이슈가 발생할 수 있습니다. N+1 문제는 이런 문제에서 발생하는 이슈입니다. 만약, Department를 조회한다면 Department에는 여러 Student 엔티티가 존재할 것입니다. 이럴 때 Department를 1번 조회했는데 연관관계가 있는 Student를 N번 조회한다고 하여 N+1 문제라고 부릅니다.
나는 분명 Department 정보가 필요해 Department를 조회했는데 사용하지 않을 다수의 Student까지 조회가 되는 현상입니다. 물론 해결책은 있습니다. 다만, JPA에 대해서 깊게 학습하지 않으면 모르는 내용이므로 자신이 무엇을 배우는지, 내부에서 어떻게 작동하는지는 어느정도 알아야 할것입니다.

정리를 해보자면, JAVA 객체와 관계형 DB는 서로 지향하는 패러다임이 매우 다릅니다. 이 다른 패러다임의 간극을 좁히기 위해서 개발자는 너무 많은 시간과 코드를 소비합니다. 더 어려운 문제는 정교한 객체 모델링을 할수록, 패러다임의 불일치 문제가 더 커지고 그에 따라 소모해야 하는 비용도 더 많아집니다. 결국, 객체 모델링은 점점 원래의 의미가 퇴색되어 DB 중심의 모델로 변해갑니다. JAVA 진영에서는 이 문제에 대한 숙제를 안고 있었고, 해결하기 위해 많은 노력을 기울여 왔습니다. 그리고 그 결과물이 JPA입니다. JPA는 패러다임의 불일치 문제를 해결해주고, 정교한 객체 모델링을 유지하게 해줍니다. 그에 따라 문제점이 발생하지만, JPA는 그에 대한 해결책 또한 제시하고 있습니다.

참고문헌
[1] 김영한, 자바 ORM 표준 JPA 프로그래밍(서울,에이콘출판,2015),51

profile
어제보다 성장한 개발자의 DEBUG 로그

0개의 댓글