티스토리 뷰
Spring MVC - Controller + Service
[Spring]Spring MVC - Controller 클래스 구조 생성 및 설계
[Spring]Spring MVC - Controller 클래스에 핸들러 메서드(Handler Method) 구현
[Spring]Spring MVC - Controller 클래스에 ResponseEntity 적용
[Spring]Spring MVC - Controller 클래스에 DTO 적용
[Spring]Spring MVC - DTO 유효성 검증(Validation)
[Spring]Spring MVC - DI를 통한 API 계층 ↔ 서비스 계층 연동
[Spring]Spring MVC - 매퍼(Mapper)를 이용한 DTO 클래스 ↔ 엔티티(Entity) 클래스 매핑
[Spring]Spring MVC - @ExceptionHandler를 이용한 예외처리
[Spring]Spring MVC - @RestControllerAdvice를 이용한 예외처리
Spring Data JDBC
[Spring]JDBC(Java DataBase Connectivity)
[Spring]Spring Data JDBC, Spring Data JDBC 사용법
[Spring]Spring Data JDBC - 도메인 엔티티&테이블 설계
Spring Data JPA
[Spring]JPA(Java Persistence API)
[Spring]JPA - Entity ↔ DB Table Mapping
[Spring]JPA - Entity ↔ Entity Mapping
[Spring]Spring Data JPA - 데이터 액세스 계층 구현
지난 글까진 순수 JPA를 이용해 엔티티와 테이블 간의 매핑을 구현했다.
이번 글에선 Spring Data JPA를 이용해 JPA를 조금 더 편하게 사용하는 방법에 대해 살핀다.
Spring Data JPA를 통한 데이터 액세스 계층 구현
Spring Data JPA
Spring Data JPA는 앞서 Spring Data JDBC에 대해 알아볼 때 한 번 등장했었다.
2022.08.26 - [개발/Spring] - [Spring]Spring Data JDBC, Spring Data JDBC 사용법
요약하면 Spring Data는 DB의 특성을 유지하며 데이터에 접근하기 위한 프로젝트로,
Spring Data JPA는 그 라이브러리, 즉 표준 스펙 중 하나이다.
주로 JPA를 위한 스프링 데이터 저장소(JpaRepository 인터페이스)를 지원하며, 이 기능을 가장 많이 사용한다.
또한 JPA를 살피며 아래와 같은 그림을 본 적이 있는데,
2022.08.31 - [개발/Spring] - [Spring]JPA(Java Persistence API)
여기서 Spring Data JPA는 JPA를 한 단계 더 추상화한 기술이며,
바꿔말하면 Hibernate가 JPA를 구현하듯이 JPA는 Spring Data JPA를 구현한다고 볼 수 있다.
쉽게 말하자면 Spring Data JPA는 JPA를 구현한 Hibernate의 API를 쉽게 사용할 수 있게 해주는 모듈인 것이다.
실제로 애플리케이션이 데이터베이스에 접근하는 순서는 아래와 같다.
이 글에서는 이전에 Spring Data JDBC를 적용했던 예제를 불러와 Spring Data JPA를 다시 적용한다.
2022.08.30 - [개발/Spring] - [Spring]Spring Data JDBC - Service, Repository 구현
시작하기 전에, build.gradle과 application.yml에 JPA 의존성을 추가하자.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
spring:
h2:
console:
enabled: true
path: /h2 // Context path 설정
datasource:
url: jdbc:h2:mem:test // JDBC URL 설정
jpa:
hibernate:
ddl-auto: create // 테이블 스키마 자동 생성
show-sql: true // 로그에 SQL 쿼리 출력
Entity Classes
Spring Data JDBC를 걷어내고 Spring Data JPA의 애너테이션을 추가한다.
Member Entity
package com.gnidinger.member.entity;
import com.gnidinger.order.entity.Order;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@NoArgsConstructor
@Getter
@Setter
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long memberId;
@Column(nullable = false, updatable = false, unique = true)
private String email;
@Column(length = 100, nullable = false)
private String name;
@Column(length = 13, nullable = false, unique = true)
private String phone;
@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<>();
public Member(String email) {
this.email = email;
}
public Member(String email, String name, String phone) {
this.email = email;
this.name = name;
this.phone = phone;
}
public void addOrder(Order order) {
orders.add(order);
}
public enum MemberStatus { // 회원 상태 구성
MEMBER_ACTIVE("활동중"),
MEMBER_SLEEP("휴면 상태"),
MEMBER_QUIT("탈퇴 상태");
@Getter
private String status;
MemberStatus(String status) {
this.status = status;
}
}
}
회원 상태 저장을 위한 enum 변수 memberstatus와 사용할 상태를 구성하는 MemberStatus enum이 추가되었다.
JPA 애너테이션을 통해 엔티티 매핑을 하는 코드는 이전 글에 설명했으므로 생략한다.
2022.08.31 - [개발/Spring] - [Spring]JPA - Entity ↔ DB Table Mapping
2022.09.01 - [개발/Spring] - [Spring]JPA - Entity ↔ Entity Mapping
Coffee Entity
package com.gnidinger.coffee.entity;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
import java.time.LocalDateTime;
@NoArgsConstructor
@Getter
@Setter
@Entity
public class Coffee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long coffeeId;
@Column(length = 100, nullable = false)
private String korName;
@Column(length = 100, nullable = false)
private String engName;
@Column(length = 5, nullable = false)
private int price;
@Column(length = 3, nullable = false, unique = true)
private String coffeeCode;
@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();
public enum CoffeeStatus { // 추가
COFFEE_FOR_SALE("판매중"),
COFFEE_SOLD_OUT("판매중지");
@Getter
private String status;
CoffeeStatus(String status) {
this.status = status;
}
}
}
Member와 마찬가지로 enum 변수와 CoffeeStatus enum이 추가되었다.
Order Entity
package com.gnidinger.order.entity;
import com.gnidinger.member.entity.Member;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
import java.time.LocalDateTime;
@NoArgsConstructor
@Getter
@Setter
@Entity(name = "ORDERS")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long orderId;
@Enumerated(EnumType.STRING)
private OrderStatus orderStatus = OrderStatus.ORDER_REQUEST;
@Column(nullable = false)
private LocalDateTime createdAt = LocalDateTime.now();
@Column(nullable = false, name = "LAST_MODIFIED_AT")
private LocalDateTime modifiedAt = LocalDateTime.now();
@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
public void addMember(Member member) {
this.member = member;
}
public enum OrderStatus {
ORDER_REQUEST(1, "주문 요청"),
ORDER_CONFIRM(2, "주문 확정"),
ORDER_COMPLETE(3, "주문 처리 완료"),
ORDER_CANCEL(4, "주문 취소");
@Getter
private int stepNumber;
@Getter
private String stepDescription;
OrderStatus(int stepNumber, String stepDescription) {
this.stepNumber = stepNumber;
this.stepDescription = stepDescription;
}
}
}
Repository Interfaces
글 초입에도 적었지만 Spring Data JPA에서 가장 많이 쓰이는기능은 JpaRepository 인터페이스이다.
JpaRepository는 위와 같은 인터페이스를 상속받아 CRUD, Paging, Sorting등의 기능과 더불어
Spring Data에 속한 QueryByExampleExecutor를 상속받아 검색 조건을 포함하는 Example 객체를 생성하고
이를 쿼리 메서드에 전달함으로써 원하는 값을 조회할 수 있게 된다.
이를 바탕으로 각 레포지토리 클래스를 수정해 보자.
MemberRepository
package com.gnidinger.member.repository;
import com.gnidinger.member.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface MemberRepository extends JpaRepository<Member, Long> { // Crud -> Jpa
Optional<Member> findByEmail(String email);
}
기존에 CrudRepository를 상속받던 부분을 JpaRepository로 교체했다.
CoffeeRepository
package com.gnidinger.coffee.repository;
import com.gnidinger.coffee.entity.Coffee;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.Optional;
public interface CoffeeRepository extends JpaRepository<Coffee, Long> { // Crud -> Jpa
Optional<Coffee> findByCoffeeCode(String coffeeCode);
@Query(value = "SELECT c FROM Coffee c WHERE c.coffeeId = :coffeeId")
// @Query(value = "FROM Coffee c WHERE c.coffeeId = :coffeeId") // ‘SELECT c' 생략 가능
// @Query(value = "SELECT * FROM COFFEE WHERE coffee_Id = :coffeeId", nativeQuery = true) // (1)
Optional<Coffee> findByCoffee(long coffeeId);
}
JpaRepository 상속과 함께 JPQL을 이용해 coffeeId에 대한 정보를 조회하는 것을 확인할 수 있다.
JPQL(Java Persistence Query Language)
- SQL을 추상화한, 특정 DB SQL에 의존하지 않는 객체지향 쿼리
- SQL은 DB 테이블(COFFEE)을, JPQL은 엔티티(Coffee)를 대상으로 쿼리를 보냄
- SQL과 문법이 유사하고, SELECT, FROM, WHERE 등을 지원
- JPA는 JPQL을 분석한 후 SQL을 생성해 DB 조회, 결과를 엔티티 객체로 매핑한 뒤 반환
주석에 쓰인 대로 'SELECT c'는 생략이 가능하며, SQL을 이용해 조회하려면 (1)과 같이 하면 된다.
OrderRepository
package com.gnidinger.order.repository;
import com.gnidinger.order.entity.Order;
import org.springframework.data.jpa.repository.JpaRepository;
public interface OrderRepository extends JpaRepository<Order, Long> {
}
Service Classes
MemberService
...
@Service
public class MemberService {
...
public Member updateMember(Member member) {
...
Optional.ofNullable(member.getMemberStatus())
.ifPresent(memberStatus -> findMember.setMemberStatus(memberStatus));
findMember.setModifiedAt(LocalDateTime.now());
return memberRepository.save(findMember);
}
...
}
회원의 상태 정보와 등록 시간을 조회하는 기능을 추가했다.
CoffeeService
...
public class CoffeeService {
...
public Coffee updateCoffee(Coffee coffee) {
...
// (1) 추가된 부분
Optional.ofNullable(coffee.getCoffeeStatus())
.ifPresent(coffeeStatus -> findCoffee.setCoffeeStatus(coffeeStatus));
return coffeeRepository.save(findCoffee);
}
...
}
커피 상태를 업데이트하는 코드를 작성했다.
OrderService
...
@Service
public class OrderService {
...
public Order updateOrder(Order order) { // // updateOrder() 메서드 추가
Order findOrder = findVerifiedOrder(order.getOrderId());
Optional.ofNullable(order.getOrderStatus())
.ifPresent(orderStatus -> findOrder.setOrderStatus(orderStatus));
findOrder.setModifiedAt(LocalDateTime.now());
return orderRepository.save(findOrder);
}
...
}
updateOrder() 메서드를 추가했다.
코드를 바꾸면서 Spring Data JDBC -> Spring Data JPA의 체감이 크진 않았다.
그만큼 Spring이 추구하는 PSA(일관된 서비스 추상화)가 구현되어 있고
개발자는 일관된 코드 구현 방식을 유지하며 기술의 변경이 필요할 때 최소한의 변경만을 하도록 만들어져 있다는 뜻이겠다.
이번 코드는 컨트롤러나 DTO 클래스도 손을 봤는데,
하나하나 올릴 수 없으니 누덕누덕 기워 만든 내 코드 원본을 올려본다.
'Java+Spring > Spring' 카테고리의 다른 글
[Spring]트랜잭션(Transaction) - @Transactional (0) | 2022.09.06 |
---|---|
[Spring]트랜잭션(Transaction) (0) | 2022.09.05 |
[Spring]ORM, Hibernate, JPA, Spring Data JPA (0) | 2022.09.02 |
[Spring]JPA - Entity ↔ Entity Mapping (2) | 2022.09.01 |
[Spring]JPA - Entity ↔ DB Table Mapping (2) | 2022.08.31 |
[Spring]JPA(Java Persistence API) (0) | 2022.08.31 |
- Total
- Today
- Yesterday
- 세계여행
- 면접 준비
- 세계일주
- spring
- 맛집
- 자바
- RX100M5
- 여행
- 백준
- 파이썬
- 리스트
- 동적계획법
- java
- 남미
- 유럽
- a6000
- 칼이사
- 세모
- 기술면접
- BOJ
- Algorithm
- 지지
- Python
- 스프링
- Backjoon
- 알고리즘
- 중남미
- 유럽여행
- 야경
- 스트림
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |