지금까지 본 내용을 정리해보자.
@Entity, @Column 등의 Annotation을 사용하여 DDL을 직접 SQL로 작성하는 것이 아닌, Java Class 작성만으로 테이블을 만들었다.
em.persist를 사용하여 SQL을 작성하지 않고 메소드 호출 만으로 Record를 저장했다.
em.flush로 Java instance를 SQL로 변환하여 DB에 전송했다.
또한 em.find 로 DB에서 Record를 가져올 때, Java instance로 변환한 적이 없는데, Jpa에 의해 알아서 Java instance 형태로 반환 받을 수 있었다.
Jpa는 ORM이라 불리며, Object Relational Mapping의 약자다.
여기서 Object란 Java instance를 뜻하고, Relational Mapping은 Record로의 변환을 생각하면 된다.
| id | name | age |
| 1 | 'user' | 10 |
분명 우리가 DB로부터 가져온 것은 Record라 불리는 위와 같은 형태의 데이터였지만,
User user = em.find(User.class, 1L);
em.find로 찾아온 데이터는 자동으로 User instance로 변환되었다.
Record에서 Instance로 변환이 필요한 것은 DB와 Application의 데이터 구조 상의 차이 때문이며, ORM 없이 SQL을 직접 전송했다면 이런 변환 작업들을 수동으로 해줬어야 할 것이다.
변환 작업의 자동화로 인해 SQL을 사용하지 않고 Java Code만 사용해서 SQL <-> instance 변환, instance <-> SQL 변환이 가능해진 것이다.
물론 변환 작업만 해줬다면 Jpa는 ORM이 아니라 그냥 Mapper라고 불렸을 것이다.
ORM은 여기서 더 나아가서 DB와의 interaction을 마치 Java의 객체 다루듯 작업하는 것을 가능케 한다.
Record와 Instance의 구조적인 차이를 간단하게 알아봤으니, 생성 방법에 대한 차이도 한 번 알아보자.
id가 1인 User Record를 변경하고 싶은 경우 다음과 같이 SQL을 작성하면 된다.
insert into "user" (id, name)
values ('xxx', 1);
이것이 DB의 데이터 생성 방식이며, Java에서는 다음과 같이 표현할 수 있다.
User user = new User(1L, "xxx");
이렇듯 편의를 위해 Entity라고 부르던 Record, Instance는 데이터 구조, 문법 차이로 인해 호환되지 않았으며,
이러한 변환 및 저장을 Jpa에게 위임하기 위해 em.persist도 호출할 필요가 있었다.
User user = new User(1L, "xxx");
em.persist(user);
Jpa 덕분에 새로운 Record를 저장할 때, 단 한 줄의 SQL도 사용하지 않고 오직 Java Code만으로 DB에 데이터를 저장하는 것이 가능해졌다.
SQL로 "user" Record의 name Column을 변경하는 간단한 코드다.
update "user"
set name = "xxx"
where id = 1;
이를 JPQL로 표현하면 다음과 같다.
em.createQuery("update User u set u.name = :name where u.id = :id")
.setParameter("name", "xxx")
.setParameter("id", 1L)
.executeUpdate();
JPQL을 사용해도 Record 변경이 가능하지만, Java에서 Instance의 객체 필드를 변경하는 방법과는 차이가 존재한다.
User user = new User();
user.name = "xxx"; // 1. 재할당
user.setName("xxx"); // 2. setter 호출
user.changeName("xxx"); // 3. custom method 호출
Java는 위와 같이 재할당이나 setter 혹은 method 호출을 통해 필드를 변경할 수 있기 때문이다.
Jpa를 사용하면 SQL, JPQL을 작성하지 않고, Java Instance를 변경하는 것 만으로 SQL로 변환 및 전송이 가능하다.
자세한 원리는 다음 장에서 보겠지만, Java Code만으로 Record를 변경하는 로직은 다음과 같다.
User user = new User(); // 1. User Instance 생성
// id는 자동으로 1이 들어간다고 가정
user.setName("user"); // 이름 설정
em.persist(user); // 2. Persistence Context에 user instance 저장
em.flush(); // 3. SQL 변환 후 DB에 SQL 전송
user.setName("xxx"); // 4. user.name 변경
em.flush(); // 5. user.name에 변경 사항 발생 -> DB에 변경에 대한 SQL 전송
user.setName("xxx")로 단순히 instance의 필드를 변경했을 뿐인데, em.flush를 호출하면 자동으로 다음과 같은 SQL이 생성 및 전송된다.
Hibernate:
update
"user"
set
name=?
where
id=?
마치 Record를 직접 조작하는 행위를 Java로 작성하는 느낌이 든다.
이로써 Instance <-> Record의 차이를 자동으로 매핑하여 어느 정도 극복하였다.
Jpa가 완벽하진 않기에 모든 로직이 이렇게 물 흐르듯 자연스러운 매핑이 되진 않지만, 가능한 경우에는 SQL을 작성할 필요성 자체를 줄여줄 수 있을 것이며, 이는 생산성의 증가로 이어진다.
물론 이런 건 안 된다.
User user = new User(); // 1. User Instance 생성
// id는 자동으로 1이 들어간다고 가정
user.setName("user"); // 이름 설정
em.persist(user); // 2. Persistence Context에 user instance 저장
em.flush(); // 3. SQL 변환 후 DB에 SQL 전송
user = null; // 4. user를 null로 설정해도 DB에서 제거되지 않음
em.flush();
user를 의도가 아닌 실수로 null 설정해서 데이터를 날려먹을 수 있는 위험성이 생산성 증가보다 훨씬 타격이 크기 때문에 막아 놓은듯 하다.
ORM이 Record 생성과 변경에 있어서 Java instance를 다루듯 매우 자연스럽게 Record까지 조작할 수 있는 방법을 제공해준다는 것을 인지하게 되었다.
삭제는 앞서 봤듯 위험성 때문에 지원이 안 되고, 조회는 다양한 조건이 붙게 되면 Java만으로 표현하기에는 복잡한 경우가 많기 때문에 JPQL 혹은 추후 보게 될 Querydsl을 사용하는 편이다.
간단한 조회 로직들은 Data Jpa 등이 기본적으로 제공하는 메소드 호출로 간단하게 처리가 가능하다.