JPA를 통한 데이터 전달

goose_bumps·2024년 7월 9일

기존에는 SQL 쿼리를 사용하여 CRUD API를 만들었었다.
하지만, 코드가 몇 백개인 상황에서 기존에 사용하고 있던 DB가 변경되어 다른 DB를 사용해야 할 경우 모든 API에 적어놓은 SQL 쿼리를 변경하는 것은 상당한 작업이 될 것이다.
JPA를 사용하면 이 문제를 해결할 수 있다. 즉, 쿼리 대신 JPA를 사용하여 데이터 전달을 하는 것이다.

1. JpaRepository 상속받기

기존에 있던 UserRepository는 UserJdbcRepository로 파일명을 변경하고 UserRepository라는 이름의 인터페이스를 생성하여 JpaRepository를 상속받아 보자.
*파일명 변경 시 Refactor All을 선택하면 의존성 주입한 모든 클래스에서도 클래스명이 변경된다

package com.group.libraryapp.domain.user;

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User,Long> {
}

이 때 테이블 매핑 객체인 User와 id의 타입인 Long을 적어주어야 한다.

다음은 기존의 UserService의 이름을 UserServiceV1으로 바꾸고 UserServiceV2라는 새로운 클래스를 만들어보겠다.

2. API(저장,조회,업데이트,삭제) 생성

기존에 있던 API 대신 JPA를 사용하는 새로운 API를 UserServiceV2에 만들어보겠다.
그에 앞서 UserRepository의 객체인 userRepository를 필드로 선언해야 한다.

UserRepository userRepository;

1) 저장 API

    public void saveUser(UserCreateRequest request){
        userRepository.save(new User(request.getName(),request.getAge()));
    }

JPA에 save 메서드가 존재하는데 User 객체를 만들어 save 메서드를 호출하기만 하면 실제로 DB에 저장된다.
UserRepository가 JpaRespository를 상속받기 때문에 해당 메서드를 호출할 수 있는 것이다.
UserCreateRequest 인스턴스에 저장된 name,age를 getter를 통해 반환하여 User 객체 생성자 파라미터에 입력하고 이를 DB에 저장하는 것이다.
(User 객체는 테이블과 매핑되기 때문에 User 객체에 저장은 곧 테이블에 저장되는 것이다)

이렇게 save를 하게 되면 저장된 User 객체가 반환되는데 id를 사용할 수도 있다.

    public void saveUser(UserCreateRequest request){
        userRepository.save(new User(request.getName(),request.getAge()));
    }
    System.out.println(request.getId());

2) 조회 API

조회는 findAll 이라는 메서드를 사용하여 만들 수 있다.

    public List<UserResponse> getUser(){
        return userRepository.findAll().stream()
                .map(user -> new UserResponse(user.getId(),user.getName(),user.getAge())).collect(Collectors.toList());
    }

findAll 메서드를 사용하면 모든 유저 데이터를 조회하는 SQL이 날라가고 그 결과로 List가 반환되며 이것이 UserResponse로 변경되어 전달되는 것이다.

3) 업데이트 API

업데이트는 2번의 쿼리를 전달한다. 첫 번째는 id를 통해 User 유무를 확인하고 없으면 IllegalArgumentException을 던진다.
있다면 updateName을 통해 이름을 업데이트 하고 마지막으로 save로 업데이트 된 데이터가 저장된다.

  public void updateUser(UserUpdateRequest request){
      User user = userRepository.findById(request.getId()).orElseThrow(IllegalArgumentException::new);
      user.updateName(request.getName());
      userRepository.save(user);
  }

하지만, User 클래스에 updateName이라는 메서드는 없기 때문에 새로 만들어주어야 한다.

  public void updateName(String name) {
      this.name = name;
  }

현재까지 사용한 JPA의 기능을 정리해보자

  • save : 주어진 객체를 저장하거나 업데이트
  • findAll : 주어지는 객체가 매핑된 테이블의 모든 데이터를 가져옴
  • findById : id를 기준으로 특정 데이터 1개를 가져옴

4) 삭제 API

삭제는 업데이트와 비슷하게 name을 기준으로 데이터를 찾고 찾으면 해당 데이터를 삭제하는 순으로 작동하면 된다.

  public void deleteUser(String name){
      User user = userRepository.findByName(name);
      if(user == null){
          throw new IllegalArgumentException();
      }
      userRepository.delete(user);
  }

findByName을 사용하여 SQL쿼리를 대신 날리고 해당 name과 일치하는 User 데이터가 있는지 확인하고 없을 경우 IllegalArgumentException을 throw한다.
있으면 delete 기능을 사용하여 삭제한다.

이때 findByName이 존재하지 않는 메서드라고 경고문이 뜨는데 실제로 존재하지 않는 메서드인 것은 맞다.

UserRepository에 다음 메서드를 만들어줘야 한다.

여기서 의문점이 생긴다. 인터페이스는 추상 메서드만 만들 수 있을 텐데(default 메서드 제외), 구현부가 없지 않나? 그러면 어떻게 기능을 구현할까?

여기서 JPA의 기능이 나오는데 findByName이라는 이름만으로 SQL쿼리가 조합이 되는 것이다. SELECT*FROM user WHERE name = ? 라는 쿼리가 저 findByName만으로 만들어지는 것이다.

여기까지 API를 모두 JPA를 이용하여 데이터를 전달하는 방식으로 만들었다.
UserController에 UserServiceV1을 모두 UserServiceV2로 바꾸면 된다.

3. 쿼리 조합

삭제 API에서 사용한 findByName과 같은 조합을 자세히 알아보자.

1) By 앞에 올 수 있는 것들

  • find : 데이터 1건 가져옴. 반환 타입은 객체 또는 Optional<타입>
  • findAll : 쿼리의 결과물이 n개인 경우 사용. List<타입>을 반환
  • exists : 쿼리 결과가 존재하는지 여부를 boolean형으로 반환
  • count : SQL결과의 개수를 세어 Long 타입으로 반환

예를 들어, name을 기준으로 해당 데이터가 있는지 확인하고 싶을 경우 existsByName으로 조합하면 된다.

그렇다면 방금 전 삭제 API도 다음과 같이 만들어 볼 수 있다.


      if(userRepository.existsByName(name) == false){
          throw new IllegalArgumentException();
      }
      userRepository.delete(userRepository.findByName(name));

name을 기준으로 해당 데이터가 있는지 유무를 확인하고 없을 경우 false를 반환하여 IllegalArgumentException을 throw 한다.
물론, UserRepository에도 existsByName을 만들어주어야 한다.

2) By 뒤에 올 수 있는 것들

  • And 또는 Or
  • GreaterThan : 초과
  • GreaterThanEqual : 이상
  • LessThan : 미만
  • LessThanEqual : 이하
  • Between : 사이
  • StartWith : ~로 시작
  • EndWith : ~로 끝

And 나 Or을 사용해서 기준이 되는 것을 중복할 수 있는데 예를 들어, findByNameAndAge 나 findByNameOrAge처럼 name과 age를 기준으로 할 수 있다.
특정 범위를 지정하여 찾는 것도 가능하다.

List<User> findAllBetween(int startAge, int endAge);

이렇게 조합하면 SELECT*FROM user WHERE age BETWEEN ? AND ?; 쿼리의 기능을 가지는 것이다.

0개의 댓글