스프링부트

[SpringBoot] Spring Data JPA를 사용한 CRUD

민석삼 2025. 3. 10. 10:33

Spring Data JPA v.s. JPA

Spring Data JPA는 복잡한 JPA 코드를 스프링과 함께 쉽게 사용하도록 도와주는 라이브러리

위의 함수들은 복잡한 JPA 코드를 감싼 코드임

 

전체 틀

Spring Data JPA: JPA 사용

JPA는 규칙일 뿐이고, 이를 구현한 Hibernate가 코드 역할을 하며

Hibernate는 내부적으로 jdbc를 사용한다.

 

그래서 우리는 Srping Data JPA를 사용하면서 JDBC를 사용하고 있는 것


Service에서 JpaRepository를 상속받은 Repository의 함수를 이용해서 CRUD 기능을 구현해보자

사용할 repository 클래스가 JpaRepository를 상속받게 하면, 해당 repository는 굳이 @Repository를 붙이지 않아도, jpaRepository를 상속한 것만으로 스프링 빈으로 관리됨

왜? Spring Data JPA가 JpaRepository를 상속한 인터페이스를 찾아 자동으로 구현체를 생성해 스프링 빈으로 등록하기 때문

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

<User, Long>은, <Entity 객체, Id의 타입>

 

저장(Create)

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

JpaRepository의 save 함수를 사용. 이때 객체(User)의 생성자에 각 변수를 넣어주면 끝

 

SQL을 직접 작성하지 않고 UserRepository를 가져와서 save라는 메소드를 호출한 것만으로 함수 작성 완료

save메소드에 객체를 넣어주면 INSERT SQL이 자동으로 날아감.

 

조회(Read, Retrieve)

    public List<UserResponse> getUsers() {
        return userRepository.findAll().stream().map(UserResponse::new).collect(Collectors.toList());
    }
    
    //람다 사용 전
//  return userRepository.findAll().stream().map(user -> new UserResponse(user.getId(), user.getName(), user.getAge())).collect(Collectors.toList());

findAll(): 자동으로 SQL을 날려 해당 테이블의 모든 데이터를 가져오는 함수. 반환값은 List<User>

stream().map(UserResponse::new) : findAll함수로 가져온 각 <User>를 <UserResponse>로 바꿔줌

collect(Collectors.toList()) : 위 map함수 결과를 반환해서 List로 반환해주는 문법

 

수정(Update)

    public void updateUser(UserUpdateRequest request) {
        // select * from user where id = ?;
        //Optional<User>
        User user = userRepository.findById(request.getId())
                .orElseThrow(IllegalArgumentException::new);
        user.updateName(request.getName());
        userRepository.save(user);
    }

findById(): Id를 기준으로 객체를 찾고, 없다면 null을 반환. (반환값은 객체 또는 null을 가질 수 있는 Optional)

.orElseThrow() : findById()함수의 결과가 null이라면 예외를 던지기

null이 아니고 해당 Id를 가진 객체가 존재하여, user에 객체가 저장되면 객체에 만들어둔 updateName()함수로, 객체의 값 -을 업데이트 

userRepository.save(User) : 유저의 이름이 바뀌어 있는 걸 확인하고, 바뀌어 있으면 바뀐 값을 기준으로 Update SQL이 날아가서 DB에 반영됨

더보기

나의 의문.. 왜 여기선 updateName()함수를 직접 만들어서 쓰는가?

JPA의 JpaRepository에는 기본적인 CRUD 메서드는 있찌만 특정 필드만 변경하는 기능은 기본적으로 제공되지 않음

삭제 (Delete)

Update는 유저 존재 여부를 id를 기준으로 체크했다면, Delete는 이름 기준으로 체크하도록 해줄 것

JpaRepository에는 특정 필드의 값으로 존재 여부를 조회하는 함수가 없음. 어떻게 만들지??

 

함수 이름을 규칙에 맞게 작성하면 알아서 SQL이 조립된다.

public interface UserRepository extends JpaRepository<User, Long> {

    Optional<User> findByName(String name);
}

일단 UserRepository 인터페이스에 해당 함수를 선언

    public void deleteUser(String name) {
        // select * from user where name = ?;
        User user = userRepository.findByName(name).orElseThrow(IllegalArgumentException::new);
        if (user == null) {
            throw new IllegalArgumentException();
        }

        userRepository.delete(user);
    }

서비스 코드

Update와 비슷한 구조. 이름을 기준으로 데이터를 가져오고 반환값이 null이라면 예외를 null이 아니라면 userRepository.delete(user)를 통해 db에 반영함

 

기본함수

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

 

다양한 Spring Data JPA 쿼리

By 앞에 들어갈 수 있는 구절

find 1건을 가져온다. 반환타입은 객체 또는 Optional<객체 타입>
findAll 쿼리의 결과물이 N개인 경우 사용. 반환타입은 List<타입>
exists 쿼리 결과가 존재하는 지 확인. 반환 타입은 boolean
count SQL의 결과 개수를 센다. 반환 타입은 long

 

By 뒤에: 필드

여기 붙는 필드 이름으로, SELECT 쿼리의 where문이 작성된다 (ByAge는 'where age = ?' 이런식)

각 구절은 and, or로 조합할 수도 있음

List<User> findAllByNameAndAge(String name, int age); 

'SELECT * FROM user WHERE name = ? and age = ?;'와 같은 뜻

 

필드명 뒤에 들어가는 것

GreaterThan 초과
GreaterThanEqual 이상
LessThan 미만
LessThanEqual 이하
Between 사이에
StartsWith ~로 시작하는
EndsWith ~로 끝나는

 

예시

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

findAll: 전체검색결과가 N개 들어가고

byAge: 필드이름 age기준으로 찾아라

매개변수 이름은 어떤 게 들어가던 크게 상관 없다

'SELECT * FROM user WHERE age BETWEEN ? AND ?;'