스프링데이터 + JPA/QueryDSL

7. JPQL vs QueryDSL

sdafdq 2023. 11. 28. 10:19

일단 JPQL로 한번 짜보고, 똑같은 걸 QueryDSL로 짜서 비교해 볼거임.

 

@Test
public void startJPQL(){
    String sql = "select m from Member m where m.username = :username";
    Member result = em.createQuery(sql, Member.class)
            .setParameter("username", "member1")
            .getSingleResult();

    assertThat(result.getUsername()).isEqualTo("member1");
}

뭐 그냥 JPQL이다.

select
    m1_0.member_id,
    m1_0.age,
    m1_0.team_id,
    m1_0.username 
from
    member m1_0 
where
    m1_0.username=?

 

 

@Test
public void startQuerydsl(){
    JPAQueryFactory query = new JPAQueryFactory(em);
    QMember m = QMember.member;

    String usernameForFind = "member1";

    Member result = query.select(m)
            .from(m)
            .where(m.username.eq(usernameForFind))
            .fetchOne();

    assertThat(result.getUsername()).isEqualTo(usernameForFind);
}

QueryDSL이다.

select
    m1_0.member_id,
    m1_0.age,
    m1_0.team_id,
    m1_0.username 
from
    member m1_0 
where
    m1_0.username=?

 

QueryDSL은 사용하기 위해서는 JPAQueryFactory라는 것이 필요하다.

이건 내부적으로, 예를 들어 저렇게 fetchOne()하면 쿼리를 보내고 가져오는, 저런 EntityManager를 내부적으로 사용하기 위해 EntityManager를 인자로 받는다.

 

QMember는 https://qwefdg3.tistory.com/905

이걸 보면 알다 시피 Member 엔티티에 대한 정보를 저장해 놓은 클래스? 라고 보면 된다.

 

그러니까 일단, QueryDSL에서는 저게 Member라는 엔티티를 지칭한다고 보면 된다.

 

보면 이제,

뭐 똑같다.

쿼리 팩토리를 시작으로 

select m from m where m.username = "member1"

하고 fetchOne()

fetchOne 부분이 em에서 getSingResult 이런거와 동일하다고 볼 수 있다.

 

그리고 여기서 좀 중요한 부분이,

파라미터 바인딩을 해 주는데,

저게 내부적으로 jdbc의 prepare statement방식,

https://qwefdg3.tistory.com/81

을 사용하기 때문에 sql에서 뭐 "select * from member where member.username = " + param

이런 식으 + 로 문자열을 이어붙이는 게 아니라 바인딩 시키는 거라 인젝션 공격에서 안전하다.

jpa의 setParameter도, QueryDSL의 인자방식도 이 prepare statement 방식을 사용한다.

 

 

장점이,

메소드? 그러니까 문자열이 아닌 자바코드로 짜기 때문에 컴파일 시점에서 오류를 다 잡아준다는 점, 뭐 파라미터 바인딩도 그냥 인자로 넣어주면 내부적으로 다 jdbc의 prepareStatement()함수 이용해서 다 해주고,

또 코드 어시스턴스 기능도(emmet) 좋다. 그냥 있지 않을까? 하면 다 자동완성해서 리스트로 보여준다.

 

 

 

그리고, 저거 JPAQueryFactory 필드로 빼도 된다고 한다.

그러니까 이게 생각을 해 보면 

Member result = query.select(m)
        .from(m)
        .where(m.username.eq(usernameForFind))
        .fetchOne();

뭐지? JPAQueryFactory에 계속 뭔가를 해 나가는 것이므로, 뭔가 JPAQueryFactory가 상태를 저장하게 되지는 않을까? 할 수도 있는데, 당장 저 select 메소드만 들어가 봐도,

public <T> JPAQuery<T> select(Expression<T> expr) {
    return query().select(expr);
}
@Override
public JPAQuery<?> query() {
    if (templates != null) {
        return new JPAQuery<Void>(entityManager.get(), templates);
    } else {
        return new JPAQuery<Void>(entityManager.get());
    }
}

new로 준다.

 

보통 저렇게 시작하는 쿼리들이 아마 new로 줄 것이다.

그래서 동시성 문제는 괜찮을 것이다.

 

JPAQueryFactory 자체가 상태를 저장하고 있지 않을 것이고,

사용될 때 마다 저렇게 query()메소가 실행되고 시작되며 저것만 사용하게 될 것이다.

 

그리고 EntityManager 내부적으로 동시성 문제도 해결된다고 한다.

이 부분은, 결국 QueryDSL도 QueryDSL > JPA > JdbcTemplate 내부적으로 Jdbc Template를 사용하는데,

Jpa, 즉 EntityManager를 활용하는 Spring Data Jpa, QueryDSL은 모두 트랜잭션 범위에서 진행되어야 하는데,

Jdbc는 트랜잭션의 접근에 대해

https://qwefdg3.tistory.com/569

 

그러니까,

트랜잭션이 시작되면 트랜잭션 매니저가 그 커넥션을 트랜잭션 동기화 매니저 라는 곳에 보관해 놓는다.

즉, 그 커넥션이 커넥션 풀 중에서 사용중 이라는 상태가 되는 것이다.

 

저 커넥션이 트랜잭션 동기화 매니저의 쓰레드 로컬이라는 곳에 저장이 되는데, 쓰레드 로컬이란 그 쓰레드만 접근할 수 있는 저장소이다.

그렇기에 동시성 문제에서, 다른 쓰레드가 접근하게 저 커넥션을 사용할 수는 없다. 

그 커넥션은 커넥션 풀에서 '사용중'이라는 꼬리표가 붙은 상태이고, 그러므로 다른 쓰레드에서 호출할 때 커넥션풀에서 커넥션을 요청했을 때 그 커넥션은 주어지지 않으며, 또한 쓰레드 로컬이란 해당 쓰레드에서만 접근할 수 있는 저장소에 저장되므로, 다른 쓰레드가 접근할 수 있는 여지가 없다.

 

그래서 커넥션이 쓰레드 별로 구분이 된다.


저렇게 커넥션에 대한 동시성은 해결이 되었는데,

또 영속성 컨텍스트에 대한, 즉 EntityManager 자체에 대한 동시성 문제가 해결이 되지 않았다.

이 부분은

https://www.inflearn.com/questions/158967/%EC%95%88%EB%85%95%ED%95%98%EC%84%B8%EC%9A%94-entitymanager%EC%97%90-%EB%8C%80%ED%95%B4-%EA%B6%81%EA%B8%88%ED%95%9C-%EC%A0%90%EC%9D%B4-%EC%9E%88%EC%96%B4-%EC%A7%88%EB%AC%B8-%EB%82%A8%EA%B9%81%EB%8B%88%EB%8B%A4

 

안녕하세요, EntityManager에 대해 궁금한 점이 있어 질문 남깁니다. - 인프런 | 질문 & 답변

JPA 기본편 강의와 같이 듣고 있습니다.좋은 강의 항상 감사드립니다.(질문 도중 제가 잘못 이해 하고 있는 부분이 있다면 말씀주시기 바랍니다.)다름 아니라 EntityManager는 요청이 들어올 때 생성

www.inflearn.com

여기에 대략 나와있다.

 

보니까 스프링 워크가 의존성을 주입해 줄 때, EntityManager 그 자체가 아니라 프록시로 한번 씌운 EntityManager를 준다는 것이다.

실제로 em.getClass() 해서 출력해 보니까

엔티티 : class jdk.proxy2.$Proxy117

이렇게 프록시로 나왔다.

 

아마 저 프록시가 영속성 컨텍스트를 관리하고 있을테고, EntityManager에 대한 로직은 저 프록시 내부에 있는 실제 싱글톤 Bean인 EntityManger를 통해 수행하겠지.

 

 

'스프링데이터 + JPA > QueryDSL' 카테고리의 다른 글

9. 검색 조건 쿼리  (0) 2023.11.28
8. Q타입  (0) 2023.11.28
6. 예제 도메인 모델  (0) 2023.11.28
5. H2 DB 설치 및 설정  (0) 2023.11.27
4. 연관 라이브러리  (0) 2023.11.27