인프런 워밍업 클럽 0기 - 백엔드 코스 (과제7)
문제 1. JPA 적용하기
이전의 프로젝트 상에서 JdbcTemplate
을 사용해 DB에 접근했었습니다. JdbcTemplate
과 같은 경우 쿼리문을 string으로 작성하기 때문에 오류를 실제 동작 상황에서 파악할 수 있는 안 좋은 단점이 있었습니다. 이를 보완하기 위해 좀 더 객체와 테이블을 바로 매핑해서 코드로써 DB에 접근할 수 있는 JPA를 활용해보도록 하겠습니다. 그 중에서 JPA를 Spring에서 좀 더 간편하게 사용할 수 있는 Spring Data Jpa를 활용하고 Hibernate를 구현체로 사용해보도록 하겠습니다.
Entity 생성
@Entity
public class Fruit {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private LocalDate warehousingDate;
private long price;
private boolean isSold;
}
Jpa에서 테이블과 매핑할 Entity를 생성해줍니다.
Repository 생성
public interface FruitJpaRepository extends JpaRepository<Fruit,Long> {
}
Jpa를 활용할 Repository를 사용하는 방법은 JpaRepository<Entity_CLASS,ID_TYPE>
를 상속받은 인터페이스를 선언해주는 것입니다. 이렇게 되면 JpaRepository에서 제공하는 간단한 메서드를 사용할 수 있고, 간단한 쿼리메서드를 작성해서 자신만의 쿼리를 작성할 수도 있습니다.
저와 같은 경우 이전의 합계 계산 쿼리를 직접 작성해봤었습니다. 이전의 GROUP_BY
를 활용한 코드를 이번에는 JPQL로 작성해보도록 하겠습니다.
@Query(value = "SELECT new com.group.libraryapp.dto.fruit.FruitStatProjection(SUM(f.price),f.isSold) FROM Fruit f GROUP BY f.isSold")
List<FruitStatProjection> findByIsSold();
JPQL 또한 실제 Entity를 통해 쿼리를 작성할 수 있습니다. Fruit
엔티티의 isSold
필드를 통해 그루핑하고 각 그룹의 price 값의 총합을 구하는 쿼리를 작성해보았습니다.
해당 Repository를 활용한 Service는 이전과 거의 동일한 형태이므로 작성하지 않도록 하겠습니다.
문제 2. count query
DB에 해당 이름을 가진 과일 개수를 세는 쿼리를 작성해보도록 하겠습니다.
Jpa는 count로 시작하는 쿼리 메서드를 COUNT
쿼리가 나가도록 정해두었습니다. 그리고 이름을 통해 필터링할 것이기 때문에 countByName(String name)
으로 쿼리메서드를 지정하면 Spring Data Jpa가 이를 통해 실제 구현 코드를 스스로 작성해줍니다.
public interface FruitJpaRepository extends JpaRepository<Fruit,Long> {
...
Long countByName(String name);
}
위와 같이 작성한다면 바로 사용할 수 있습니다.
이를 사용하는 서비스와 컨트롤러 메서드는 별다른 점이 없으므로 따로 작성하지는 않겠습니다.
문제 3. option query
path:
/api/v1/fruit/list
query:
option
,price
response
{
"name": String,
"warehousingDate: LocalDate,
"price": long
}
option
이 GTE일 경우 Greater Than Equal 연산을 수행하고 LTE일 경우 Less Than Equal 연산을 수행합니다.
우선 option
과 price
를 담아줄 DTO를 선언해주었습니다.
public class FruitResponse {
private String name;
private LocalDate warehousingDate;
private long price;
}
response가 FruitResponse
의 list 형식으로 반환될 수 있도록 했습니다.
@RequestParam과 같은 경우 option, price 두 인자를 따로 따로 받아야 함으로, @ModelAttribute을 활용해 두 쿼리파라미터를 한 번에 받아오도록 하겠습니다.
FruitSearch
public class FruitSearch {
private String option;
private long price;
}
FruitController
@GetMapping("/api/v1/fruit/list")
public List<FruitResponse> getFruitList(@ModelAttribute FruitSearch fruitSearch){
return fruitServiceV2.getFruitList(fruitSearch);
}
JpaRepository
public interface FruitJpaRepository extends JpaRepository<Fruit,Long> {
...
List<Fruit> findByPriceGreaterThanEqual(long price);
List<Fruit> findByPriceLessThanEqual(long price);
}
쿼리 메서드를 두 개 선언해준 뒤 옵션에 따라 각각 실행될 수 있도록 해주었습니다.
Service
public List<FruitResponse> getFruitList(FruitSearch fruitSearch) {
List<Fruit> result = fruitSearch.isGTE() ?
fruitJpaRepository.findByPriceGreaterThanEqual(fruitSearch.getPrice()) :
fruitJpaRepository.findByPriceLessThanEqual(fruitSearch.getPrice());
return result.stream().map(FruitResponse::new).collect(Collectors.toList());
}
응답 예시
[
{
"name": "사과",
"warehousingDate": "2024-02-25",
"price": 8000
},
{
"name": "사과",
"warehousingDate": "2024-02-25",
"price": 3000
},
{
"name": "배",
"warehousingDate": "2024-02-25",
"price": 4000
},
{
"name": "귤",
"warehousingDate": "2024-02-25",
"price": 1000
},
{
"name": "수박",
"warehousingDate": "2024-02-25",
"price": 10000
}
]
댓글을 작성해보세요.
fruitSearch.isGTE()
이렇게 코드 작성하니까 가독성이 좋네요 배우고 갑니다