본문 바로가기

Spring Boot

Spring Data JPA : Repository 작성

 

 

Spring Data JPA

Repository

Controller - Service - Repository - DataBase 의 Repository가 맞다

 

JPA를 사용하지 않았을 때의 Repository 작성은 매우 힘들었다.

직렬화, 반직렬화, stream에서 map()을 사용할때 이 map에 뭐가 많이 들어가고 등등..

공부는 많이 되었고 머릿속에도 조금 남아있지만 이 Repository를 계속 작성하는데에는 수고가 많이 든다.

 

그래서 Spring Data JPA에서는 DB - Repository 접근에 대한 인터페이스, JpaRepository를 제공하고 있다.

이 인터페이스는

CrudRepository - > PagingAndSortingRepository - > JpaRepository로 이어지는 상속구조를 가진다.

package org.springframework.data.jpa.repository;

import java.util.List;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.ListCrudRepository;
import org.springframework.data.repository.ListPagingAndSortingRepository;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.query.QueryByExampleExecutor;

@NoRepositoryBean
public interface JpaRepository<T, ID> 
                   extends ListCrudRepository<T, ID>, 
                           ListPagingAndSortingRepository<T, ID>, 
                           QueryByExampleExecutor<T> {
    // 생략
}

 

위 코드는 JpaRepository 인터페이스다.

import로 Sort, Crudrepository, ListPagingAndSortingRepository 를 받고 있으며

마찬가지로 extends를 통해 ListCrudRepository, ListPagingAndSortingRepository를 상속받고있다.

 

ListCrudRepository는 CrudRepository를 상속받고 있으며 얘네들은 기본적인 CRUD를 제공해준다.

ListPagingAndSortingRepository는 PagingAndSortingRepository를 상속받고 있으며 얘네들을 통해 페이징, 정렬을 할 수 있다.

 

따라서 Repository 계층에서 JpaRepository를 상속받으면 CRUD + 페이징 + 정렬 기능을 쓸 수 있다!

 

Entity

@Entity
@Getter
@Setter
@Table(name = "member")
public class Member {
	
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    
    @Column
    private String username;
    
    @Column
    private int age;
}

 

Repository, 상속만 해도 기본적인 CRUD를 사용할 수 있다

public interface MemberRepository extends JpaRepository<Member, Long> {

}

 

※ extends JpaRepository<Entity 클래스명, PK의 타입>

PK의 타입을 적을때 기본형을 적으면 안되고 참조형을 적어야한다. 가령 entity에서는 long타입이면 참조형인 Long을 써야함

null값의 유무를 판단하기 위해 고려한 것으로 보인다.

기본형은 null값이 들어올 수 없지만 참조형은 가능하니까

 

 

동작 원리에 대해서

1. @EnableJpaRepositories가 자동으로 Spring Bean으로 등록된다

  - Spring Boot에서는 @SpringBootApplication 내부에서 @EnableJpaRepositories가 자동으로 활성화된다

  - @EnableJpaRepositories가 동작하면 JpaRepositoriesRegistrar가 import되어서 Repository관련 Bean 등록이 시작된다..

 

2. SimpleJpaRepository가 내부적으로 구현체로 사용된다.

  - Repository의 내부 구현체로 기본적으로 제공된다.

  - JpaRepositoryFactory는 인터페이스를 보고 내부적으로 SimpleJpaRepository로 연결한다고 한다.(Factory -> Repository)

  - **SimpleJpaRepository는 EntityManager를 사용해서 모든 DB 접근 로직을 수행한다고 함.

  - save(), findById(), delete() 등이 SimpleJpaRepository에 정의되어있다.

 

3. 프록시 패턴으로 런타임에 Spring이 구현체를 만들어서 주입한다.

※프록시 패턴(Proxy Pattern)에 대해서는 컴퓨터 프로그래밍이므로 따로 공부해야겠음

  - Spring AOP 기반 동적 프록시를 생성한다

  - JpaRepositoryFactory가 만든 프록시 객체가 실제 개발자???에게 주입된다

  - 프록시 내부에서는 SimpleJpaRepository를 위임해서 실행한다

  - 개발자는 인터페이스만 정의하고 실제 로직은 프록시 + SimpleJpaRepository가 처리한다!!

 

 

Service

@Service
@RequiredArgsConstructor
public class BasicIndexDataService implements IndexDataService {
    private final IndexInfoRepository infoRepository;
    private final IndexDataRepository dataRepository;
    private final IndexDataMapper mapper;   
}

 

이런식으로 Repository를 바로 쓸 수 있다

 

 

JpaRepository의 기본 제공 메서드

save() 저장 및 수정에 쓰임(PK 유무에 따라 구분가능?)
findById() ID로 단건(단 하나)을 조회한다, ID로 찾기
findAll() 전체 조회하기. 보통은 쓰지 않고 조건문을 달아서 쓰던지 한다.
count() 전체 갯수를 조회한다, 엔티티의 갯수라던지
delete() 단건 삭제
deleteAll() 전체를 삭제한다
existsById() 존재 여부를 확인한다. 보통 dataRepository.existsById().orElseThrow(() -> new Exception()); 형태로 쓴다
findAll(Pageable) 조회를 하는데, 페이징 처리된 조회를 한다
finaAll(sort) 정렬된 조회를 한다

 

예시

Member member = new Member("B1uffer", 30);
memberRepository.save(member); // 저장

Optional<Member> result = memberRepository.findById(1L); // 해당 id를 가진 member을 찾는다
List<Member> members = memberRepository.findAll(); // 모~든 member를 찾는다
long count = memberRepository.count(); // memberRepository에 있는 갯수를 조회한다
boolean exists = memberRepository.existsById(1L); true 혹은 false, id를 가진 member가 있는지 조회
memberRepository.deleteById(1L); // id에 해당하는 member를 삭제한다

// 아래는 페이징과 정렬임
// PageRequest 타입의 객체를 만드는데 PageRequest로 만듬, 정렬도 안에서 해버림
PageRequest pageRequest = PageRequest.of(0, 10, Sort.by("username"));
Page<Member> page = memberRepository.findAll(pageRequest);

 

 

쿼리 메서드 작성 규칙 : Repository에서 사용할 수 있음

Spring Data JPA에서는 메서드명을 통해 SQL을 자동으로 생성하는 기능을 제공한다.

메서드 이름이 곧 쿼리임

다르게 말하면, 메서드 명을 다르게 적으면 쿼리가 맞질 않아서 Repository와 DB가 서로 연결되지 않는다.

키워드 의미
findBy ~를 통해 조회한다
countBy 뒤에 오는 무언가가 들어있는 갯수를 조회함
deleteBy ~가 들어있는 것을 삭제함
existsby 있는가? 없는가? 의 존재여부(boolean)
TopN 상위 N개 쿼리문
First 첫번째

 

예시

// username을 포함하고 있는 Member들을 찾는다
List<Member> findByUsername(String username);
// SELECT m FROM Member AS m WHERE m.username = ?1 < - ?1이 뭐임

// GraterThan도 쿼리문이다. 이런게 있음
List<Member> findByAgeGreaterThan(int age);
// SELECT m FROM Member AS m WHERE m.age > ?1

// 인자로 받은 username과 age가 모두 같은 Member를 찾는다. 홍길동 30살 이런거
List<Member> findByUsernameAndAge(String username, int age);
// SELECT m FROM Member AS m WHERE m.username = username, m.age = age;

// 맨 위에 있는 member 3개를 찾는데, 나이에 대해서 내림차순으로 정렬한다.
List<Member> findTop3ByOrderByAgeDesc();
// SELECT m FROM Member AS m ORDER BY m.age DESC LIMIT 3;

 

* JpaRepository만 써도 기본적인 CRUD는 충분하다

* 복합쿼리는 QueryDSL, @Query()로 해결할 수 있다. << 공부가 더 필요함

* 메서드명으로 해결 가능한 범위 : 단순한 조건 + And/Or/GreaterThan정도.. 그 이상은 위의 복합쿼리처리를 해야함

* 페이징, 정렬은 Pageable, sort를 적극적으로 활용한다 << 페이지네이션도 있고, 슬라이도 있다고 합니다.

* Optional 반환 구현을 통해서 NPE를 방지할 수 있음. << 이거 매우 중요해보임. NPE 발생했었음.. ㅋㅋ