본문 바로가기
프로젝트/PongGame

Account Repository

by 히포파타마스 2022. 5. 10.

Account Repository

Account 정보를 DB에서 가져오는 AccountRepository를 만든다.

DB에서 엔티티를 가져올 때는 Spring Data JPA와 QueryDSL를 사용한다.

 

 

 

 

1. Spring Data JPA

■ AccountRepository

Spring Data JPA에 의해 인터페이스에 JpaRepository를 상속받도록 하면 자동으로 구현체가 생성된다.

때문에 AccountRepository는 인터페이스로 구현된다.

 

[AccountRepository]

public interface AccountRepository extends JpaRepository<Account, Long> {
    Optional<Account> findByUsername(String username);

    Optional<Account> findByNickname(String nickname);

    boolean existsByUsername(String username);

    boolean existsByNickname(String nickname);
}

Account의 조회를 위해 필요한 메서드들을 정의한다.

 

 

 

 

2. QueryDsl

paging이나 검색조건 등이 요구될때는 Spring Data JPA로 어노테이션을 사용해 직접 쿼리를 작성해줘도 되지만, 여기서는 편의를 위해 QueryDsl을 사용한다.

 

우선 QueryDsl을 사용하기 위해서는 Qclass를 생성해야 한다.

QueryDsl에서는 특정 엔티티를 조회할 때 해당 엔티티 클래스를 사용하지 않고 그 엔티티에 대응되는 Qclass를 사용한다.

ex) Account 대신 QAccount를 사용

 

앞서 build.gradle 설정에서 QueryDsl에 관한 설정을 했다면 다음과 같이 Qclass를 생성할 수 있다.

 

[build-clean]

우선 build-clean으로 build 정보를 초기화해준다.

 

 

[other-compileQuerydsl]

이후 other-compileQuerydsl을 실행하면 미리 설정해 놓은 위치에 Qclass가 생성된다.

 

 

 

■ AccountRepositoryCustom

QueryDsl을 사용하려면 QueryDsl을 구현할 인터페이스를 먼저 생성해야 한다.

 

[AccountRepositoryCustom]

public interface AccountRepositoryCustom {
    public QueryResults<Account> findAccountsByPage(AccountSearchCond accountSearchCond, Pageable pageable);
}

Account의 API에서 검색조건인 AccountSearchCond와 페이지 정보 pageable을 받아서 Account를 페이지로 조회하는 API가 있었다.

이 API를 구현하기 위해 DB에서 해당 조건으로 Account를 조회하는 메서드 findAccountsByPage를 정의한다.

 

 

 

■ AccountRepositoryImpl

앞의 AccountRepositoryCustom을 상속받아 해당 인터페이스에 정의된 메서드를 구현한다.

 

[AccountRepositoryImpl]

public class AccountRepositoryImpl extends QuerydslRepositorySupport implements AccountRepositoryCustom{

    private final JPAQueryFactory jpaQueryFactory;

    public AccountRepositoryImpl(EntityManager entityManager) {
        super(Account.class);
        this.jpaQueryFactory = new JPAQueryFactory(entityManager);
    }

    @Override
    public QueryResults<Account> findAccountsByPage(AccountSearchCond accountSearchCond, Pageable pageable) {
        JPAQuery<Account> query = jpaQueryFactory
                .selectFrom(account)
                .where(AccountNicknameContain(accountSearchCond.getNickname()));

        Objects.requireNonNull(getQuerydsl()).applyPagination(pageable, query);
        return query.fetchResults();
    }

    private BooleanExpression AccountNicknameContain(String nickname) {
        return nickname != null ? account.nickname.contains(nickname) : null;
    }
}

 

 

□ AccountRepositoryImpl

[AccountRepositoryImpl]

public class AccountRepositoryImpl extends QuerydslRepositorySupport implements AccountRepositoryCustom{   //[1]

    private final JPAQueryFactory jpaQueryFactory;   //[2]

    public AccountRepositoryImpl(EntityManager entityManager) {
        super(Account.class);   //[3]
        this.jpaQueryFactory = new JPAQueryFactory(entityManager);
    }

	.
    .
    .
    
}

● [1] : 상속

○ Querydsl의 쿼리 작성에 도움을 주는 QuerydslRepositorySupport를 상속(extends)받는다.

○ 구현할 인터페이스 AccountRepositoryCustom을 상속(implements)받는다.

 

● [2] : QueryDsl은 JPAQueryFactory를 사용해서 쿼리를 메서드-체인형식으로 작성한다.

● [3] :QuerydslRepositorySupport는 생성 초기값으로 Repository의 대상이 되는 엔티티를 필요로 한다.

 

 

□ findAccountsByPage

[findAccountsByPage]

@Override
public QueryResults<Account> findAccountsByPage(AccountSearchCond accountSearchCond, Pageable pageable) {
    JPAQuery<Account> query = jpaQueryFactory
            .selectFrom(account)
            .where(AccountNicknameContain(accountSearchCond.getNickname()));   //[1]

    Objects.requireNonNull(getQuerydsl()).applyPagination(pageable, query);   //[2]
    return query.fetchResults();   //[3]
}

● [1] :Account의 nickname이 accountSearchCond의 nickname을 포함(= like)하고 있는지를 판단하는 쿼리문을 where 절로 삽입한다.

● [2] : QuerydslRepositorySupport

○ getQuerydsl().applyPagination은 QuerydslRepositorySupport의 메서드로 pageable의 정보로 그에 맞는 쿼리문을 생성해서 추가해준다.

○ QuerydslRepositorySupport를 사용하지 않으면 pageable의 정보로 query.offset(), query.limits()를 직접 설정해 줘야 하며 sort 같은 부분은 내가 직접 구현해줘야 한다.

 

● [3] : API에서는 Account가 DTO로 반환되어야 하기 때문에 바로 Account가 담긴 Page로 반환하지 않고 fetchResultes()를 사용해서 QueryResults<Account>로 반환한다.

 

 

□ AccountNicknameContain

[AccountNicknameContain]

private BooleanExpression AccountNicknameContain(String nickname) {
    return nickname != null ? account.nickname.contains(nickname) : null;
}

● [1] : Account의 nickname이 매개변수로 받은 nickname을 포함하고 있는지를 표현하는 조건문인 BooleanExpression을 반환한다.

● [2] : QueryDsl의 Where 절은 null이 들어가면 무시하기 때문에 만약 검색조건이 없었을 경우(AccountSearchCond의 nickname이 null) null을 반환한다.

 

 

 

■ 적용

[QueryDsl 적용]

public interface AccountRepository extends JpaRepository<Account, Long>, AccountRepositoryCustom {

Spring Data Jpa가 적용된 AccountRepository에 QueryDsl을 사용한 클래스의 인터페이스인 AccountRepositoryCustom을 상속받는다.

이러면 AccountRepository의 구현체에 AccountRepositoryCustom을 구현한 AccountRepositoryImpl가 적용된다.

때문에 이후에 AccountRepositoryImpl에 구현된 findAccountsByPage()와 같은 메서드를 사용할 때는 AccountRepository를 사용하면 된다. 

 

 

 

'프로젝트 > PongGame' 카테고리의 다른 글

Account_exceptionHandler  (0) 2022.05.11
Account Validation  (0) 2022.05.10
Account API 명세  (0) 2022.05.05
EntityListeners  (0) 2022.04.28
LoginAccountIdArgumentResolver  (0) 2022.04.28

댓글