JpaRepository의 사용법 그리고 Repository

2023-05-25
ꞏ
유영진
ꞏ
show5116

JpaRepository

Spring Data JPA를 사용하면 기본적인 CRUD쿼리는 직접 작성하지 않고 공통 인터페이스를 통해 사용합니다.
JpaRepository는 공통 인터페이스로서 save(), saveAll(), findById(), delete() 등등.. 간단한 메서드가 이미 정의되어 있습니다.
해당 메서드들의 구현체는 SimpleJpaRepository에 구현되어 있어서 인터페이스를 생성하는 것 만으로도 CRUD가 가능합니다.

java
1@Repository
2@Transactional(readOnly = true)
3public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
4 ...
5 @Transactional
6 public <S extends T> S save(S entity) {
7 if (entityInformation.isNew(entity)) {
8 em.persist(entity);
9 return entity;
10 } else {
11 return em.merge(entity);
12 }
13 }
14}

위의 코드는 실제 구현체에서 save 메서드에 대한 구현 로직입니다.

기본 사용법

  1. Entity 생성
java
1@Entity
2@Getter
3@NoArgsConstructor(access = Accesslevel.PRIVATE)
4@AllArgsConstructor(access = Accesslevel.PROTECTED)
5@Builder
6public class Test {
7 @Id
8 @GenerationValue(straregy = GenerationType.IDENTITY)
9 private Long id;
10
11 private String name;
12
13 public updateName(String name) {
14 this.name = name;
15 }
16}
  1. Repository 새엇ㅇ Repository를 생성합니다.
    이 때 Bean에 등록할 @Repository 어노테이션은 사용하지 않아도 됩니다.
    구현체 클래스에서 이미 어노테이션이 선언 되어있기 때문에 상속받는 것 만으로도 Bean에 올라갑니다.
java
1// 제네릭 좌측은 Entity 클래스를, 우측에는 해당 Entity의 Id 타입을 넣어줍니다.
2public interface TestRepository extends JpaRepository<Test, Long>
  1. 메서드 사용
java
1@Service
2@RequiredArgsConstructor
3public class TestService {
4 private final TestRepository testRepository;
5
6 @Transactional(readOnly = true)
7 public Test getTestEntity(long id) {
8 Optional<Test> testWrapper = testRepository.findById(id);
9 Test test = testWrapper.orElseThrow();
10 }
11
12 @Transactional
13 public void insertTestEntity(String name) {
14 Test test = Test.builder()
15 .name(name).build();
16 testRepository.save(test);
17 }
18
19 @Transactional
20 public void updateTestEntity(long id, String newName) {
21 Optional<Test> testWrapper = testRepository.findById(id);
22 Test test = testWrapper.orElseThrow();
23 test.updateName(id);
24 }
25
26 @Transactional
27 public void deleteTestEntity(long id) {
28 testRepository.deleteById(id);
29 }
30}

위의 예시처럼 select, insert, update, delete문을 간단하게 구현 가능합니다.
이외에도 페이징 쿼리나 bulk 연산 쿼리들도 기본 메서드로 구현되어 있습니다.
각 기능들에 대해선 추후에 포스팅 하겠습니다.

메서드 정의하기

JpaRepository는 특수한 명명 규칙으로 메서드를 정의하여 사용할 수 있습니다.
특정 검색조건이나 orderBy, 혹은 특정row 갯수만 조회한다던가 메서드 이름으로 여러 방법으로 활용할 수 있습니다.
이에 대해서도 추후에 자세하게 포스팅하겠습니다.

java
1public interface TestRepository extends JpaRepository<Test, Long> {
2 public List<Test> findAllByName(String name);
3}

@Query 어노테이션

@Query 어노테이션을 이용해서 JPQL 문법을 활용해 쿼리를 직접 작성할 수 있습니다.
nativeQuery = true 속성을 통해 nativeQuery도 사용 가능합니다.

java
1public interface TestRepository extends JpaRepository<Test, Long> {
2 @Query("select t.username from Test t")
3 public List<String> findAllName();
4
5 @Query("select t from Test t where t.name = :name")
6 public Optional<Test> findTestByName(String name);
7}

JpaRepository의 계층

Repository-interface size:800*400

위에서 사용한 JpaRepository는 PagingAndSortingRepository를 상속받았고, 그 위에는 CrudRepository 최종적으로는 Repositry interface로부터 상속받았습니다.
JpaRepository는 최하층으로서 CrudRepository에선 Crud 메서드를, PagingAndSortingRepository에선 Paging 쿼리를 그리고 JpaRepository에선 flush메서드 등이 구현됩니다.

최 상단인 Repository를 상속받으면 구현이 되어있는 메서드가 전혀 없습니다.
따라서 사용할 메서드를 직접 선언해줘야만 합니다.

java
1public interface TestRepository extends Repository<Test, Long> {
2
3 public Test save(Test test);
4
5 public void delete(Test test);
6
7 public Optional<Test> findById(Long id);
8}

실제 JpaRepository에서 사용하는 메서드와 같은 타입, 같은 이름으로 선언해주면 동일한 로직을 수행하는 메서드가 구현되게 됩니다.

Repository를 사용하면 좋은 이유

그렇다면 왜 Repository 인터페이스를 언급했을까요?
실제로 JpaRepository를 사용해도 서비스에는 문제는 없습니다.
하지만 JpaRepository를 사용할 경우에는 실제로 사용하지 않는 메서드들이 너무 많이 만들어집니다.
그에 따라 Repository를 사용할 경우 상대적으로 얻게되는 장점들이 있습니다.

  1. 필요한 메서드들만 구현하고 외부로 노출

    repository만 보더라도 어떤 역할들을 하는지 한눈에 알아볼 수 있게 됩니다.
    또한 IDE에서는 해당 메서드들을 사용하고 있는 위치를 쉽게 추적도 가능하게 해줍니다.

  2. 메서드의 재사용성

    필요한 메서드들만 구현할 경우 메서드들의 재사용성이 늘어납니다.
    이에 따라서 쿼리 캐싱의 가능성이 높아지게 됩니다.

이러한 점들로 인하여 Repository를 사용하면 조금 더 Repository의 역할에 집중할 수 있게 해줍니다.
물론 JpaRepository를 상속받으면 코드의 수도 적어지고 구현하기 쉬워지니 무엇이 무조건 맞다고는 할 수 없습니다.