[JPA] Spring Data JPA

Bam·2025년 5월 25일
0

Spring

목록 보기
67/73
post-thumbnail

Spring Data JPA

Spring Data JPA는 스프링 프레임워크 환경에서 JPA를 편리하게 사용할 수 있도록 만들어주는편리하게 사용할 수 있게 만들어주는 추상화 라이브러리입니다.

Spring Data JPA를 사용하지 않았을 때 기본적인 CRUD 수행을 위해서는 다음과 같은 Repository를 작성해주어야했습니다.

@Repository
public class MemberRepository {

	@PersistenceContext
    EntityManager em;
    
    public void save(Member m) {
    	em.persist(m);
    }
    
    public Member findOne(Long id) {
    	return em.find(Member.class, id)
    }
    
    public List<Member> findAll() {
    	return em.createQuery("SELECT m FROM Member m",Member.class)
        			.getResultList();
    }
    
    public void updateName(Long id, String newName) {
        Member member = findOne(id);
        member.setName(newName);
    }

    public void delete(Long id) {
    	em.remove(member);
    }
}

위와 같이 기본적이고 공통적인 CRUD는 어떤 Repository를 다루던지 간에 필요로하는 경우가 많습니다.

당연히 이런 공통적인 부분을 매번 작성하는 것은 번거롭기 때문에 Spring Data JPA는 이런 공통 CRUD를 공통 인터페이스라는 개념을 통해 제공하여 구현 없이 인터페이스 implements만으로 CRUD를 처리할 수 있도록 만들어줍니다.

Spring Data JPA를 사용하기 위해서는 spring-data-jpa 라이브러리를 추가해야합니다.

그럼 지금부터 Spring Data JPA가 제공하는 기능들에 대해 알아보도록 하겠습니다.


공통 인터페이스

Spring Data JPA는 처음 예시로 보여준 것처럼 간단한 CRUD를 공통으로 처리하게 해주는 공통 인터페이스를 제공하고 있습니다.

공통 인터페이스는 JpaRepository<T, ID> 인터페이스를 상속하여 사용합니다.
이때 제네릭 T는 엔티티 클래스를 지정하고 ID는 엔티티 클래스의 식별자 필드의 타입을 지정하면 됩니다.

@Entity
public class Member {
	
    @Id
    private Long id;
}

public interface MemberRepository extends JpaRepository<Member, Long> {}

위와 같이 인터페이스를 상속하기만 하면 공통 CRUD 기능을 사용할 준비가 완료됩니다.

공통 인터페이스에서 제공하는 주요 메소드들은 다음과 같습니다.

메소드설명
save()새로운 엔티티는 INSERT, 존재하는 엔티티는 UPDATE를 수행
findOne()엔티티 하나를 조회
getOne()엔티티 하나를 프록시로 조회
findAll()모든 엔티티를 조회
delete()엔티티 삭제

소개된 메소드들 외의 메소드들은 공식 문서에서 확인하실 수 있습니다.

save()의 경우 메소드에 전달하는 엔티티에 DB 내에 존재하지 않으면 INSERT를 수행하고 존재하는 경우 UPDATE를 수행한다는 점에 유의해주세요.


쿼리 메소드

당연히 공통 인터페이스에서 제공하는 메소드들만으로는 서비스의 모든 쿼리를 처리할 수 없기 때문에 Spring Data JPA는 쿼리를 개발할 수 있는 기능을 제공하며 이를 쿼리 메소드라고 합니다.

메소드 이름으로 쿼리 생성

Spring Data JPA의 가장 신기한 기능이라고 볼 수도 있습니다. 인터페이스 내부에 규칙에 맞는 메소드 이름으로 정의하기만 하면 적절한 JPQL 쿼리를 생성해서 실행해줍니다.

public interface MemberRepository extends JpaRepository<Member, Long> {

	Member findByMemberName(String name);
}

위와 같이 메소드를 정의하고 실행하면 다음 JPQL을 실행합니다.

SELECT m FROM Member m WHERE m.name = ?1	//1번 위치에 파라미터로 전달한 문자열 삽입

메소드 이름으로 쿼리를 생성하고자 할 때 다음과 같은 규칙을 지켜야합니다.

키워드생성되는 JPQL
DistinctSELECT DISTINCT (SELECT는 예시임)
And... WHERE x.lastname = ?1 AND x.firstname = ?2
Or... WHERE x.lastname = ?1 OR x.firstname = ?2
Is / Equals... WHERE x.firstname = ?1
Between... WHERE x.startdate BETWEEN ?1 AND ?2
LessThan... WHERE x.age < ?1
LessThanEqual... WHERE x.age <= ?1
GreaterThan... WHERE x.age > ?1
GreaterThanEqual... WHERE x.age >= ?1
After... WHERE x.startdate > ?1
Before... WHERE x.startdate < ?1
IsNull... WHERE x.age IS NULL
IsNotNull / NotNull... WHERE x.age IS NOT NULL
Like... WHERE x.firstname LIKE ?1
NotLike... WHERE x.firstname NOT LIKE ?1
StartingWith / StartsWith... WHERE x.firstname LIKE ?1 (prefix%)
EndingWith / EndsWith... WHERE x.firstname LIKE ?1 (%suffix)
Containing / Contains... WHERE x.firstname LIKE ?1 (%keyword%)
OrderBy… WHERE x.age = ?1 ORDER BY x.lastname DESC
Not... WHERE x.lastname <> ?1
In... WHERE x.age IN ?1
NotIn... WHERE x.age NOT IN ?1
True... WHERE x.active = TRUE
False... WHERE x.active = FALSE
IgnoreCase... WHERE UPPER(x.firstname) = UPPER(?1)

공식 문서 참조 자료

메소드 이름으로 지원되는 주요 접두어는 다음과 같습니다.

접두어역할
find...By엔티티(리스트)를 조회
get...By, read...By, query...By조회 (find와 동일)
count...By개수 반환 (long)
exists...By존재 여부 반환 (boolean)
delete...By조건 기반 삭제 (int or void)

한 가지 주의할 점은 엔티티의 필드명이 바뀌면 인터페이스 메소드 이름도 함께 변경해주어야 한다는 것 입니다.


JPA NamedQuery

메소드 이름으로 JPA NamedQuery를 호출할 수도 있습니다.

JPA NamedQuery는 다음 코드와 같이 쿼리에 이름을 부여해서 사용하는 방식을 의미합니다.

@Entity
@NamedQuery(
	name = "Member.findByName",
    query = "SELECT m FROM Member m WHERE m.name = :name"
)
public class Member {

	@Id
    private Long id;
    
    private String name;
}

위와 같이 선언된 NamedQuery를 리포지토리에서 메소드 이름을 통해 호출할 수 있습니다.

@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {

	Member findByName(@Param("name") String name);
}

@Param은 이름 기반 파라미터(:)를 사용할 때 파라미터에 매핑할 인자를 지정합니다.


@Query

@Query를 이용해서 리포지토리에 직접 쿼리를 정의할 수도 있습니다.

@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
	
    @Query("SELECT m FROM Member m WHERE m.name = :name")
	Member findByName(@Param("name") String name);
}

계속해서 예제에 이름 기반 파라미터 바인딩을 사용했는데, 위치 기반 파라미터 바인딩도 사용할 수 있습니다.
다만 이전에 설명했듯이 코드 가독성에선 이름 기반 파라미터 바인딩 방식이 더 좋으므로 이름 기반 파라미터 바인딩을 우선해서 사용하는 것을 권장드립니다.

@Modifying

@ModifyingSpring Data JPA를 사용할 때 @Query에서 INSERT, UPDATE, DELETE 쿼리를 작성하고 실행할 때 반드시 붙여야하는 어노테이션입니다.

왜냐하면 @Query는 기본적으로 SELECT 쿼리만 사용할 수 있기 때문입니다. 그래서 데이터를 조작하는 INSERT, UPDATE, DELETE를 사용하게 된다면 명시적으로 알려줘야 합니다.

또한 데이터 조작 후 트랜잭션을 통해 DB 반영을 해야하므로 @Transactional도 함께 사용해야합니다. 일반적으로 @Transactional 인터페이스 메소드보다는 서비스 레이어에 붙여서 하나의 작업 단위를 이루도록 만들어줍니다.


커스텀 리포지토리

보통 Spring Data JPA를 사용하면 인터페이스만 정의하게 되고 그 구현체는 따로 정의하지 않게 됩니다. 만약 인터페이스에 정의한 메소드를 구현해야하는 경우 필요한 메소드만 구현할 수 있도록하는 기능을 제공 하고 있습니다.

먼저 직접 구현하고자하는 메소드를 가진 인터페이스를 정의합니다.

public interface MyCustomRepository {
	public Member findMemberCustom();
}

다음으로 위 인터페이스를 구현한 구현 클래스를 작성합니다. 이때 주의할 점은 Repository 이름 + Impl이라는 이름으로 구현 클래스를 정의해야 JPA가 사용자 정의 구현 클래스로 인식하고 동작하게 됩니다.

//반드시 리포지토리 이름  + Impl로 정의
public class MemberRepositoryImpl implements MyCustomRepository {
	
    @Override
    public Member findMemberCustom() {}
}

그리고 다음과 같이 리포지토리에서 사용자 정의 리포지토리를 상속받으면 커스텀 리포지토리를 JPA에서 사용할 수 있게 됩니다.

@Repository
public interface MemberRepository
		extends JpaRepository<Member, Long>, MyCustomRepository {}

0개의 댓글