본문 바로가기
Back-End/Spring

TIL 230731 : QueryDSL 어렵진 않은데 너무 복잡해 (1. ORM 발전 흐름)

by 우인입니다 2023. 8. 1.

QueryDSL로 기존의 코드를 리팩토링 해보는 과제가 있었다.

그런데 명확하게 친절하고 Oneway한 튜토리얼, 가이드는 없었고 무수히 많은 방법들이 혼재되어 오히려 헷갈리게 됐다.

이번 TIL은 이 혼재된 정보들을 정리하며 하나씩 체화해보려 정리해나가는 글이다.

 

어떤 것들이 혼란스럽게 했나

QueryDSL을 쓰는 코드가 뭔데? (장점, 단점, 쓰는 문법, 세팅법)

QClass

Predicate

QueryDslPredicateExecutor

JPQL, RAW JPA, jdbc, DAO, Mybatis, ORM, ... 미묘하게 다른 용어들과 기술들.

 

몇가지씩 그룹해서 하나씩 아래 정리해본다.

 


ORM의 발전 흐름

 

1. DB가 생겼다

 : 서버를 개발하는 사람들은 DB에 접근하려고 코드를 짜기 시작했다. 하지만 DB는 하나만 있는게 아니다.

 

2. DB가 여러 개가 생겼다

: 내가 MySQL에 맞춰 개발을 하고 있는데 갑자기 다른 DB로 바뀌면? 야근각

 

3. DB들에게 맞춰서 통역해주는 중개사 등장. ex) jdbc

 : Java Database Connectivity의 줄임말. 말부터 벌써 DB를 연결해주는 친구다. 이제는 DB회사들은 이 중개사, jdbc를 준비해준다. 개발하는 입장에서는 중개사만 바꾸면 된다. 중개사는 Driver.

 

4. 중개사들이랑 일하는 것도 반복적이고 귀찮다. QueryMapper 등장 ( jdbc Template, Mybatis)

 : 핵심 로직을 위해 반복적이고 귀찮은 작업들은 눈에 뻔히 보인다. 이를 위해 템플릿 등장.

String sql = "UPDATE memo SET username = ? WHERE id = ?";
jdbcTemplate.update(sql, "wooin", 1);

쿼리를 직접 써서 알아서 날려주는 기능까지 구현이 됐다.

 

 

 

5. 여전히 쿼리를 직접 써야함. SQL쓰고 고치고 힘들다

 

User 객체를 DB에 저장하려면 어떻게 해야할까?

1. DB 테이블을 직접 만들고

2. SQL insert 쿼리 작성

3. jdbc로 쿼리 날리기

4. 결과를 직접 받아주기 (ex. RowMapper)

 

여기서 User에 닉네임을 추가한다면?

1. SQL직접 수정

2. 결과를 받을 때에도  Dto에 수정

 

결과적으로 굉장히 SQL에 의존적이다. -> 써둔 SQL이 있어서 이거를 수정을 직접 해야한다.

 

 

6. ORM등장. java는 JPA

: Sql문 알아서 좀 써줘.

Object-Relational Mapping의 약자 ORM의 등장.

 

객체를 DB에 저장하고 불러오고 수정하고 등등의 일들을 할때 공통적으로 수행해야했던 일들을 대신해주게 된다.

그 ORM 중 Java의 ORM 기술 표준 명세가 JPA이다. (Java Persistence API)

 

 

6-1. JPA는 표준 명세이고, 이를 실제로 구현하는 구현체는 Hibernate

: 우선 Hibernate는 프레임워크다. 스프링 부트에서는 이를 사실상 표준으로서 사용 중이다.

 

 

 

 

6-2. Transaction 사용하기

 

EntityManagerFactory emf = Persistence.createEntityManagerFactory("memo");
EntityManager em = emf.createEntityManager();
EntityTransaction et = em.getTransaction(); // EntityManager 에서 EntityTransaction 을 가져옵니다.

    et.begin(); // 트랜잭션을 시작합니다.

    try { 
        ...
        em.persist(memo);
        et.commit();
		...	
    } finally {
        em.close(); // 사용한 EntityManager 를 종료합니다.
    }

    emf.close(); // 사용한 EntityManagerFactory 를 종료합니다.

영속성 컨텍스트를 설정하기 위해 위와 같이 직접 명령어를 입력해줘야 한다.

트랜잭션의 개념을 통해 한 트랜잭션 내에서 모든 SQL이 성공했을 때에만 commit함으로써 DB상에 반영되도록 할 수 있고, 이는 데이터의 무결성과 정합성을 보장한다.

 

 

7. SpringData Jpa로 업그레이드

SpringData를 보면 Common파트가 있고 각 갈래로 나뉘어져 최적화 되어있다.

 

 

Repository부터 기본적인 인터페이스들을 상속받고 JPA최적화되어있는 JpaRepository가 이를 상속받고 있다. 이 인터페이스를 상속받은 Repository인터페이스는 SimpleJpaRepository 구현체 빈이 등록되고 사용할 수 있다.

 


그래서 지금은

// 변경 전
@Repository
public class UserRepository {

  @PersistenceContext
  EntityManager entityManager;

  public User insertUser(User user) {
    entityManager.persist(user);
    return user;
  }

  public User selectUser(Long id) {
    return entityManager.find(User.class, id);
  }
}

위와 같이 직접 EntityManager를 주입받아서 사용했던 RAW JPA에서

 

// 변경 후
public interface UserRepository extends JpaRepository<User, Long> {
  
}

JpaRepository만 상속받음으로서 DB로부터 데이터를 받아올 수 있게 됐다.

 

 


역으로 거슬러 올라가보기

 

1. 저렇게 간편하게 명시해줌으로써 DB상의 데이터를 서버에 객체로 가져오고 그 반대의 방향도 간편하게 해줄 수가 있다.

 

2. 원래는 DB상의 트랜잭션을 직접 열어주고 닫아줬어야 했다. (EntityManagerFactory, EntityManager, EntityTransaction)

 

3. ORM덕분에 바로 객체상태로 DB상에 쿼리를 자동으로 날려준다.

 

4. 쿼리를 생성해서 날려줬어야 했다. 또 DB로부터 받은 데이터는 Row이고 이를 객체로 다시 받아줘야 했다. (jdbc, RowMapper)

 

5. 근데 또 쿼리가 각 DB마다 달랐다. 이걸 연결해주는게 jdbc

 


결론적으로 저렇게 딱 한줄의 쿼리메소드로 인해서 내부적으로

트랜잭션을 관리해주고,

쿼리를 생성해주고 날려주고,

DB에 맞게 조정해준다.

 

 


이어서

내일은 이 SpringData JPA를 이용해서 어떻게 다양하게 쿼리를 날리고 데이터를 받는지 몇가지 더 알아보기로 한다.

 

  • 쿼리메소드
  • 네임드 쿼리
  • Predicate
  • QueryDSL

 

 

 


더 궁금한 점

 

- @Repository를 붙였을 때 일어나는 일. 그리고 이 어노테이션은 스프링에서 제공하는 것인데 Raw JPA로 봐야하는 건지.

- Spring 내부 작동방식