밍쎄의 코딩공간

[스프링 부트 핵심 가이드] - 08. 데이터베이스 연동 본문

개발서적/IT

[스프링 부트 핵심 가이드] - 08. 데이터베이스 연동

밍쎄 2023. 9. 18. 16:49

[스프링 부트 핵심 가이드]

 8장에서는 Spring Data JPA에서 제공하는 기능들을 더 알아보고 다양한 활용방법을 볼 수 있다.


JPQL

- JPA QueryLanguage의 줄임말로 JPA에서 사용할 수 있는 쿼리를 의미한다. 

- JPQL의 문법은 SQL과 매우 비슷해서 데이터베이스 쿼리에 익숙하다면 어렵지 않게 사용이 가능하다.

- SQL과 차이점은 테이블이나 칼럼의 이름을 사용하는 것과 달리 JPQL은 밑 같이 엔티티 객체를 대상으로 수행하는 쿼리이기 때문에 매핑된 엔티티의 이름과 필드의 이름을 사용한다.

 

쿼리 메서드의 주제 키워드

- find...By

- read...By

- get...By

- search...By

- stream...By

- exists...By

 : 특정 데이터가 존재하는지 확인하는 키워드

- count...By

 : 조회 쿼리를 수행한 후 쿼리 결과로 나온 레코드의 개수를 리턴

- delete...By, remove...By

 : 삭제 쿼리를 수행

- ...First<number>..., ...Top<number>...

: 쿼리를 통해 조회된 결괏값의 개수를 제한하는 키워드

 

...으로 표시한 영역에는 도메인<엔티티>을 표현할 수 있다. 그러나 리포지토리에서 이미 도메인을 설정한 후에 메서드를 사용하기 때문에 중복으로 판단하여 생략하기도 한다.

 

정렬 처리하기

 일반적인 쿼리문에서 정령을 사용할 때는 ORDER BY 구문을 사용한다. 쿼리 메서드도 정령 기능에 동일한 키워드가 사용 된다.

해석 : 상품정보를 이름으로 검색한 후 상품 번호로 오름차순 정렬을 수행

 

- 정렬 테스트 코드

페이징 처리를 담당하는 PageRequest에는 정렬과 관련된 Sort타입을 파라미터로 전달할 수 있다
-> Sort는 한 개 , 여러개의 필드값을 이용해 순차정렬(asc)나 역순정렬(desc)을 지정할 수 있다
또한 Sort의 and()를 이용해 여러 개의 정렬 조건을 다르게 지정할 수 있다

 

import com.example.entity.Memo;
import com.example.repository.MemoRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;


@SpringBootTest
public class MemberRepositoryTest {

    @Autowired
    MemoRepository memoRepository;

    @Test
    public void testPageDefault(){

         // 정렬
        Sort sort = Sort.by("mno").descending();
        
        Sort sort2 = Sort.by("memoText").ascending();
        
        Sort sortAll = sort.and(sort2); // and를 이용한 연결
        
    
        // 1페이지 10개
        Pageable pageable = PageRequest.of(0,10,sort);

        Pageable pageable2 = PageRequest.of(0,10,sortAll);

        Page<Memo> result = memoRepository.findAll(pageable);

        Page<Memo> result2 = memoRepository.findAll(pageable2);

        System.out.println(result);

        result.get().forEach(memo -> {
            System.out.println(memo);
        });

        System.out.println(result2);
        result2.get().forEach(memo -> {
            System.out.println(memo);
        });

    }
}

 ORDER BY 사용

Hibernate: 
    select
        memo0_.mno as mno1_0_,
        memo0_.memo_text as memo_tex2_0_ 
    from
        tbl_memo memo0_ 
    order by
        memo0_.mno desc limit ?
Hibernate: 
    select
        count(memo0_.mno) as col_0_0_ 
    from
        tbl_memo memo0_
Page 1 of 10 containing com.example.entity.Memo instances
---------------------------------------------------------------
// and 사용한 쿼리
Hibernate: 
    select
        memo0_.mno as mno1_0_,
        memo0_.memo_text as memo_tex2_0_ 
    from
        tbl_memo memo0_ 
    order by
        memo0_.mno desc,
        memo0_.memo_text asc limit ?
Hibernate: 
    select
        count(memo0_.mno) as col_0_0_ 
    from
        tbl_memo memo0_
Page 1 of 10 containing com.example.entity.Memo instances

- 정렬 결과

Memo(mno=100, memoText=Memo...100)
Memo(mno=99, memoText=Memo...99)
Memo(mno=98, memoText=Memo...98)
Memo(mno=97, memoText=Memo...97)
Memo(mno=96, memoText=Memo...96)
Memo(mno=95, memoText=Memo...95)
Memo(mno=94, memoText=Memo...94)
Memo(mno=93, memoText=Memo...93)
Memo(mno=92, memoText=Memo...92)
Memo(mno=91, memoText=Memo...91)

 

페이징 처리

 데이터베이스와 레코드를 개수로 나눠 페이지를 구분하는 것을 의미한다. 흔히 볼 수 있는 웹 페이지에서 각 페이지를 구분해서 데이터를 제공할 때 그에 맞게 데이터를 요청하는 것이라고 생각하면 된다.

 

페이징 처리를 위한 쿼리 메서드 예시

 위와 같이 리턴 타입으로 Page를 설정하고 매개변수에는 Pageable타입의 객체를 정의한다.

페이징 쿼리 메서드를 호출하는 방법

 위 코드에서는 메서드를 호출할 때 리턴 타입으로 Page 객체를 받아야하기 때문에 Page<Product>로 타입을 선언해서 객체를 리턴받았다. 그리고 Pageavle 파라미터를 전달하기 위해 PageRequest클래스를 사용했다.

 

 

@Query 어노테이션 적용하기

 JPQL을 정의할 수 있는 어노테이션

1. JPA의 @NamedQuery에 정의된 쿼리를 정의하여 사용할 수 있다.

@Entity
@NamedQuery(name="Member.findAll", query="select m from Member m")
public class Member {
 ...
}

public interface MemberRepository extends JpaRepository<Member, Long> {

	@Query(name = "Member.findAll")
	List<Member> findAll();
}

위 예제와 같이 NamedQuery의 이름과 레퍼지포리의 메서드 이름이 같은 경우 @Query 어노테이션 생략 가능.

 

2. 컬럼 리스트 조회하기

@Query("select m.username from Member m")
List<String> findUsernameList();

3. DTO로 조회하기

@Query("select new study.datajpa.repository.MemberDto(m.id, m.username, t.name) from Member m join m.team t")
List<MemberDto> findMemberDto();

 

 DTO로 직접 조회 하려면 JPA의 new 명령어를 사용해야 한다. 그리고 다음과 같이 생성자가 맞는 DTO가 필요하다. (JPA와 사용방식이 동일하다.)

 

4. 파라미터 바인딩 - @Param

import org.springframework.data.repository.query.Param

public interface MemberRepository extends JpaRepository<Member, Long> {

 	@Query("select m from Member m where m.username = :name")
	Member findMembers(@Param("name") String username);
 
}

 

기본적인 QueryDSL 사용하기

- Querydsl 사용 방법

    @Test
    @DisplayName("Querydsl 테스트")
    public void startQuerydsl() {
        JPAQueryFactory queryFactory = new JPAQueryFactory(em);	// (1)
        QMember m = new QMember("m");	// (2)
        //QMember.member 를 static import를 하여 사용하는 것을 권장
        Member result = queryFactory
                .selectFrom(m)
                .where(m.username.eq("member1"))	// (3)
                .fetchOne();
        Assertions.assertThat(result.getUsername()).isEqualTo("member1");
    }
  • (1) : 쿼리 및 DML 절 생성을 위한 팩토리 클래스, EntityManager를 파라미터로 넘겨줘야 한다.
  • (2) : Querydsl에서 사용하기 위한 Q-Type 생성, 파라미터로 별칭(alias)을 넘겨줄 수 있다.
  • (3) : 자동으로 파라미터 바인딩을 해준다.

 

 JPQL은 쿼리를 작성 시 문자로 작성하는데 만약 오타가 있으면 런타임 시점에서 오류가 발견된다. 그에 반해 Querydsl은 컴파일 시점에 오류를 잡아주기 때문에 편리한 장점이 있다.

 

 EntityManager는 스프링 프레임워크에서 트랜잭션마다 별도의 영속성 컨텍스트를 제공해주기 때문에 여러 스레드에서 동시에 같은 EntityManager에 접근이 가능하다. JPAQueryFactory는 생성할 때 EntityManger를 파라미터로 받아 생성되어 사용하기 때문에 동시성 문제가 해결된다.

 

- Q-Type

 

 Q-Type 인스턴스를 사용하는 방법은 다음 2가지 방법이 있다.

QMember qMember = new QMember("m"); //별칭 직접 지정
QMember qMember = QMember.member; //기본 인스턴스 사용

 

 직접 별칭을 넣어서 사용해도 되지만 보통 기본 인스턴스에서 생성된 별칭을 사용하는 것을 권장한다. (별칭을 직접 지정하는 방법은 같은 테이블을 조인하는 경우에 쓰인다)

@Generated("com.querydsl.codegen.DefaultEntitySerializer")
public class QMember extends EntityPathBase<Member> {

    ...

    public static final QMember member = new QMember("member1"); // 별칭 member가 자동으로 생성된다.

    ...

}

위에서 작성한 테스트 코드를 static import를 이용해 기본 인스턴스 별칭을 사용하면 다음과 같다.

@Test
public void search() {
    JPAQueryFactory queryFactory = new JPAQueryFactory(em);
    Member result = queryFactory
            .selectFrom(member)
            .where(member.username.eq("member1")
            .fetchOne();
    assertThat(findMember.getUsername()).isEqualTo("member1");
}

Querydsl으로 실행되는 JPQL은 다음 설정을 추가하여 볼 수 있다.

spring.jpa.properties.hibernate.use_sql_comments: true
 /* select
        m 
    from
        Member m 
    where
        m.username = ?1 */ select
            member0_.member_id as member_id1_1_,
            member0_.age as age2_1_,
            member0_.team_id as team_id4_1_,
            member0_.username as username3_1_ 
        from
            member member0_ 
        where
            member0_.username=?

 

 


기본적인 QueryDSL 참고 사이트

https://jddng.tistory.com/334#list1

 

Querydsl - Querydsl 기본 문법

Querydsl 기본 문법 Querydsl 사용 방법 Q-Type 검색 조건 쿼리 결과 조회 정렬 페이징 집합 조인 - 기본 조인 조인 - on절 조인 - 페치 조인 서브 쿼리 Case 문 상수, 문자 더하기 기본 문법을 테스트하기

jddng.tistory.com

 

728x90