Spring Data JDBC와 Spring Data JPA는 Spring Data라는 패밀리 그룹에 포함되어 있다.
Spring Data라는 추상화 된 데이터 액세스 접근 방식으로 구성되어 있기 때문에 Spring Data JDBC와 Spring Data JPA라는 기술은 사용하는 방식이 거의 유사하다.
즉, Spring Data JPA는 Spring Data 패밀리 기술 중 하나로, JPA 기반의 데이터 액세스 기술을 좀 더 쉽게 사용할 수 있게 해주기 때문에 데이터 액세스 계층 구현에 있어 개발 시간을 단축 시켜줄 수 있다.
JPA vs Hibernate ORM vs Spring Data JPA
- JPA
JPA는 엔터프라이즈 Java 애플리케이션에서 관계형 데이터베이스를 사용하기 위해 정해놓은 표준 스펙 또는 사양이다.
해당 기술은 무엇이며, 해당 기술은 어떻게 구현해서 사용하는지 적어둔 기술 명세라고 생각하면 된다.
⠀- Hibernate ORM
JPA라는 표준 스펙을 구현한 구현체다. 실제로 사용할 수 있는 API라고 보면 된다.
⠀- Spring Data JPA
JPA 스펙을 구현한 구현체의 API(일반적으로 Hibernate ORM이다.)를 좀 더 쉽게 사용할 수 있게 해주는 모듈이다.
Spring Data JDBC에서 사용하는 엔티티 매핑 애너테이션은 JPA의 엔티티 매핑 애너테이션과 유사하지만 모듈 자체가 다르다.
그렇기 때문에 Spring Data JDBC에서 사용한 애너테이션을 제거하고 JPA에 맞는 애너테이션을 추가해야 한다.
@NoArgsConstructor
@Getter
@Setter
@Entity
public class Member {
...
// (1)
@Enumerated(value = EnumType.STRING)
@Column(length = 20, nullable = false)
private MemberStatus memberStatus = MemberStatus.MEMBER_ACTIVE;
@Column(nullable = false)
private LocalDateTime createdAt = LocalDateTime.now();
@Column(nullable = false, name = "LAST_MODIFIED_AT")
private LocalDateTime modifiedAt = LocalDateTime.now();
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
...
...
...
// (2)
public enum MemberStatus {
MEMBER_ACTIVE("활동중"),
MEMBER_SLEEP("휴면 상태"),
MEMBER_QUIT("탈퇴 상태");
@Getter
private String status;
MemberStatus(String status) {
this.status = status;
}
}
}
(1)은 회원의 상태를 저장하기 위해 추가된 enum 필드이다.
처음 회원이 등록될 때 기본 값은 MemberStatus.MEMBER_ACTIVE
이며, 이후에 회원의 활동이 없거나 탈퇴한 경우, MEMBER_SLEEP(휴면 상태)
이나 MEMBER_QUIT(탈퇴 상태)
로 상태를 변경할 수 있다.
(2)는 (1)에서 정의된 memberStatus
에서 사용하는 MemberStatus enum
이다.
MEMBER_ACTIVE(활동중)
, MEMBER_SLEEP(휴면 상태)
, MEMBER_QUIT(탈퇴 상태)
로 구성되어 있다.
@NoArgsConstructor
@Getter
@Setter
@Entity
public class Coffee {
...
// (1)
@Enumerated(value = EnumType.STRING)
@Column(length = 20, nullable = false)
private CoffeeStatus coffeeStatus = CoffeeStatus.COFFEE_FOR_SALE;
@Column(nullable = false)
private LocalDateTime createdAt = LocalDateTime.now();
@Column(nullable = false, name = "LAST_MODIFIED_AT")
private LocalDateTime modifiedAt = LocalDateTime.now();
// (2)
public enum CoffeeStatus {
COFFEE_FOR_SALE("판매중"),
COFFEE_SOLD_OUT("판매중지");
@Getter
private String status;
CoffeeStatus(String status) {
this.status = status;
}
}
}
(1)은 커피의 상태를 저장하기 위해 추가된 enum 필드이다.
커피 정보가 처음 등록될 때 기본 값은 CoffeeStatus.COFFEE_FOR_SALE
이다. 만약 해당 커피를 판매할 수 없는 경우, COFFEE_SOLD_OUT(판매중지)
로 상태를 변경할 수 있다.
(2)는 (1)에서 정의된 coffeeStatus
에서 사용하는 CoffeeStatus enum
이다.
COFFEE_FOR_SALE(판매중)
과 COFFEE_SOLD_OUT(판매중지)
, 두 개의 커피 상태를 사용할 수 있도록 구성되어 있다.
// Chapter JPA 엔티티(Entity) 매핑 코드와 동일
Order 엔티티 클래스의 코드는 이전 코드와 동일하다.
public interface MemberRepository extends JpaRepository<Member, Long> { // (1)
Optional<Member> findByEmail(String email);
}
CrudRepository를 상속하는 대신 JpaRepository 를 상속한다.
public interface CoffeeRepository extends JpaRepository<Coffee, Long> { // (1)
Optional<Coffee> findByCoffeeCode(String coffeeCode);
// (2)
// @Query(value = "FROM Coffee c WHERE c.coffeeId = :coffeeId") // (2-1)
// @Query(value = "SELECT * FROM COFFEE WHERE coffee_Id = :coffeeId", (2-2)
@Query(value = "SELECT c FROM Coffee c WHERE c.coffeeId = :coffeeId") // (2-3)
Optional<Coffee> findByCoffee(long coffeeId);
}
(1) : MemberRepository와 마찬가지로 JpaRepository를 상속했다.
(2) : JPA는 복잡한 검색 조건을 지정하기 위한 몇 가지 방법을 제공한다.
◼ JPQL을 통한 객체 지향 쿼리 사용
◽ JPA에서는 JPQL이라는 객체 지향 쿼리를 통해 DB 테이블을 조회할 수 있다. JPQL은 엔티티 클래스의 객체를 대상으로 객체를 조회한다.
JPQL 문법을 사용해 객체를 조회하면 JPA가 내부적으로 JPQL을 분석하여 적절한 SQL을 만들고 DB를 조회한다. 그리고 조회한 결과를 엔티티 객체로 매핑한 후 반환한다.
⠀
◽ (2-3)은 JPQL을 사용해 coffeeId
에 해당하는 커피 정보를 조회하고 있다.
JPQL 쿼리문은 SQL과 유사하지만 차이점이 있다.
JPQL은 객체를 대상으로 한 조회로, COFFEE
테이블이 아닌 Coffee 클래스 객체를 지정해야 한다. 또한, coffee_id
칼럼이 아닌 coffeeId
필드를 지정해야 한다.
따라서 SELECT c FROM Coffee c WHERE c.coffeeId = :coffeeId
에서 Coffee
는 클래스명이고 coffeeId
는 Coffee 클래스의 필드명인 것이다.
c
는 Coffee 클래스의 별칭이다. SQL에서 사용하는 *
이 아닌 c
로 필드를 조회한다. (2-3)은 (2-1)처럼 SELECT c
를 생략한 형태로 사용 가능하다.
⠀
◼ 네이티브 SQL을 통한 조회
Spring Data JDBC에서와 마찬가지로 JPA 역시 네이티브 SQL 쿼리를 작성해서 사용할 수 있다.
(2-2)의 nativeQuery
애트리뷰트의 값을 true
로 설정하면 value 애트리뷰트에 작성한 SQL 쿼리가 적용된다.
Spring Data JDBC의
@Query
vs Spring Data JPA의@Query
Spring Data JDBC에서 사용하는@Query
애너테이션과 Spring Data JDBC에서 사용하는@Query
애너테이션은 이름은 같지만 패키지 자체가 다르다.
만일 Starter 모듈이 둘 다 의존 라이브러리에 포함이 되어 있다면 패키지 경로를 혼동하지 않도록 주의해야 한다.
⠀
- Spring Data JDBC의 @Query 애너테이션 패키지 경로
import org.springframework.data.jdbc.repository.query.Query
⠀- Spring Data JPA의 @Query 애너테이션 패키지 경로
org.springframework.data.jpa.repository.Query
public interface OrderRepository extends JpaRepository<Order, Long> { // (1)
}
MemberRepository와 마찬가지로 JpaRepository를 상속한다.
// 코드 생략
// 코드 생략
// 코드 생략
데이터 액세스 기술을 Spring Data JDBC에서 Spring Data JPA로 바꾸는 실습을 해봤는데, 코드 자체가 크게 바뀐 부분은 없었다.
애플리케이션이 특정 기술에 강하게 결합되지 않게 Spring이 추구하는 PSA(일관된 서비스 추상화)를 통해, 개발자가 일관된 코드 구현 방식을 유지하고 기술의 변경이 필요할 때 최소한의 변경만을 할 수 있게 지원한다.
Spring Data JPA는 Spring Data 패밀리 기술 중 하나로써, JPA 기반의 데이터 액세스 기술을 좀 더 쉽게 사용할 수 있게 해준다.
JPA는 엔터프라이즈 Java 애플리케이션에서 관계형 데이터베이스를 사용하기 위해 정해 놓은 표준 스펙(사양 또는 명세, Specification)이다.
Hibernate ORM은 JPA라는 표준 스펙을 구현한 구현체이다.
Spring Data JPA는 JPA 스펙을 구현한 구현체의 API(일반적으로 Hibernate ORM)를 조금 더 쉽게 사용할 수 있도록 해주는 모듈이다.
Spring에서는 애플리케이션이 특정 기술에 강하게 결합되지 않도록 Spring이 추구하는 PSA(일관된 서비스 추상화)를 통해, 개발자는 일관된 코드 구현 방식을 유지하도록 하고 기술의 변경이 필요할 때 최소한의 변경만을 하도록 지원한다.
JpaRepository를 상속하면 CrudRepository 기능을 포함한 JPA에 특화된 확장 기능들을 사용할 수 있다.
JPQL은 JPA에서 지원하는 객체 지향 쿼리로써 데이터베이스의 테이블을 대상으로 조회 작업을 진행하는 것이 아니라 엔티티 클래스의 객체를 대상으로 객체를 조회한다.
JPQL의 문법을 사용해서 객체를 조회하면 JPA가 내부적으로 JPQL을 분석해서 적절한 SQL을 만든 후에 데이터베이스를 조회하고, 조회한 결과를 엔티티 객체로 매핑한 뒤에 반환한다.
📕 JPA H2 데이터베이스 및 서버 실행시 자동 Insert
📄 JPQL – How to Define Queries in JPA and Hibernate
📄 Reference Documentation