Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 코드로 시작하는 자바 첫걸음
- 자바의 정석 기초편 ch4
- 자바의 정석 기초편 ch2
- 자바의 정석 기초편 ch5
- @Aspect
- 자바의 정석 기초편 ch12
- jpa - 객체지향 쿼리 언어
- 스프링 mvc2 - 로그인 처리
- 타임리프 - 기본기능
- 스프링 mvc2 - 타임리프
- 스프링 mvc1 - 스프링 mvc
- 자바의 정석 기초편 ch3
- 스프링 고급 - 스프링 aop
- 스프링 mvc1 - 서블릿
- 2024 정보처리기사 수제비 실기
- 자바의 정석 기초편 ch6
- 게시글 목록 api
- jpa 활용2 - api 개발 고급
- 자바의 정석 기초편 ch11
- 스프링 db2 - 데이터 접근 기술
- 스프링 입문(무료)
- 자바의 정석 기초편 ch7
- 스프링 mvc2 - 검증
- 자바의 정석 기초편 ch13
- 자바의 정석 기초편 ch14
- 자바의 정석 기초편 ch8
- 스프링 db1 - 스프링과 문제 해결
- 자바의 정석 기초편 ch1
- 2024 정보처리기사 시나공 필기
- 자바의 정석 기초편 ch9
Archives
- Today
- Total
나구리의 개발공부기록
데이터 접근기술 - 스프링 데이터 JPA, 스프링 데이터 JPA 소개(등장 이유/ 기능), 스프링 데이터 JPA 주요 기능, 스프링 데이터 JPA 적용 본문
인프런 - 스프링 완전정복 코스 로드맵/스프링 DB 2편 - 데이터 접근 활용
데이터 접근기술 - 스프링 데이터 JPA, 스프링 데이터 JPA 소개(등장 이유/ 기능), 스프링 데이터 JPA 주요 기능, 스프링 데이터 JPA 적용
소소한나구리 2024. 9. 22. 15:17 출처 : 인프런 - 스프링 DB 2편 데이터 접근 핵심 원리 (유료) / 김영한님
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2
1. 스프링 데이터 JPA 소개1 - 등장 이유
1) Spring, JPA 등장 이후 Spring Data의 등장
- EJB 엔터티빈이 사장이 되고 하이버네이트와 같은 오픈소스를 기반으로 표준이 만들어지고 다듬어지게 됨
- 과거에는 관계형 데이터베이스로만 모든 것을 저장하던 때가 있었으나 이제는 몽고 DB, 레디스, 카우치베이스, Neo4j, 하둡 등등 여러가지 데이터베이스 기술들이 나오면서 데이터를 어디에 저장할 것인지에 대한 고민을 하게 됨
- 각 기술들은 여러가지가 존재하지만 사실 하는 행위는 어딘가에 저장을 하고 조회를 하는등 기본적으로 CRUD를 기반으로하는 비슷한 행위를 개발자가 각 DB기술에 맞게 만들고 있었음
- 이러한 행위를 더 큰 레벨로 추상화해서 기본적인 CRUD에 대한 인터페이스를 만들고 편리하게 사용할 수 있게 제공해보자라는 관점에서 SpringData가 등장하게 됨
- 인터페이스를 제공하고 각각의 구현 기술마다의 특징을 확장에서 제공되는 것이 Spring Data JPA, Spring Data Mongo, Spring Data Redis 등등 나오게 됨
2) 단순한 통합 그 이상
- 단순 CURD 만 하는 정도가 아닌 쿼리에 대한 부분도 제공
- 공통 인터페이스 제공(CRUD저장소 등)
- 페이징 처리 제공
- 메서드 이름으로 쿼리 생성
- 스프링 MVC 에서 id 값만 넘겨도 도메인 클래스로 바인딩
- 이러한 기능들을 특정 저장소와 관계없이 추상화(인터페이스)해서 공통으로 제공하고 각 JPA, 레디스, 몽고 등 각 기술에 맞게 그 인터페이스를 구현해서 사용하면 됨
3) DB기술은 모르고 Spring Data, Spring Data JPA만 알면 되는가?
- 자바를 모르고 스프링을 사용하는 것과 같은 정도로 구현 기술에 대해서 잘 알고 써야 함
- 쓰는 DB 기술이 JPA라면 JPA, Spring Data, Spring Data JPA를 잘 아는 사람이, 쓰는 기술이 Mongo라면 Mongo Spring Data는 물론 Spring Data Mongo 기술을 잘 사람이 반복적인 작업을 줄이는 등으로 개발 생산성을 향상시키려고 쓰는 것이지, 실제 사용할 DB의 기술을 정확히 모르고 Spring Data와 Spring Data 구현기술만 알고 사용하는 것은 어려움
2. 스프링 데이터 JPA 소개2 - 기능
1) 기술의 변화
- 복잡하고 반복적이 코드가 많았던 순수 JDBC 코드를 작성
- 스프링이 JdbcTemplate을 제공하고 MyBatis와 같은 기술을 사용하면서 코드를 편리하게 작성하게 됨
- Spring과 JPA라는 기술을 조합하면서 EntityManager를 주입 받아서 코드를 더욱 편리하게 작성하게 됨
- Spring Data JPA가 등장하면서 훨씬 더 편리하게 코드를 작성하게 됨 (Repository인터페이스에 코드가 아무것도 없이 jpa인터페이스만 상속받음)
2) SpringDataJpa
(1) JpaRepository 인터페이스와 구현체
- Repository 인터페이스에 Spring Data가 제공하는 JpaRepository라는 인터페이스를 상속받으면 아무런 코드 작성 없이 CRUD는 물론 우리가 상상할 수 있는 기능들을 기본으로 제공해줌
- 인터페이스만으로도 동작하는 것처럼 보이는 이유는 동적 프록시 기술이 인터페이스에 대한 구현체를 자동으로 만듦
(2) 쿼리 생성 기능
- 기본 제공 기능 외에 추가적인 기능을 생성하기 위해 메서드를 생성하면 생성한 메서드 이름을 가지고 앞서 배운 JPQL 쿼리를 자동으로 생성함
- JPA에 대해 좀 더 알고 있는 경우 @Query 애노테이션을 메서드에 작성하면 쿼리 자체를 직접 작성할 수도 있고 @Modifying으로 수정 쿼리도 직접 정의가 가능함
4) SpringDataJPA 경험
- 자동화 할 수 있는 부분은 컴퓨터에게 맡기고 개발자는 개발자의 일을 한다.
- 기본적으로 프로젝트 규모를 떠나서 대부분의 프레임워크 조합은 스프링 코어나 스프링 부트를 메인으로 깔고 웹 기술은 스프링 MVC를 사용하고 데이터 접근 기술을 JPA - 하이버네이트, SpringDataJPA, 동적 쿼리를 위한 QueryDSL로 구성됨
- 기술이 여러개 조합되면 보통 지저분해지는데, 데이터 접근 기술을 기술을 이렇게 묶어가면 오히려 코딩이 매우 줄어듦
5) 장점
- SQL을 직접 다루는 JdbcTemplate, MyBatis를 사용하는 것에 비해 코딩량이 매우 줄어들게 됨
- 엔터티를 사용하기 때문에 도메인 클래스를 중요하게 다루며, 비즈니스 로직을 이해하기가 쉬워지며 집중할 수 있음
- SQL을 직접 작성하게 되면 SQL이 뭐가 실행되는지 다 열어봐야하고 SQL에 비즈니스 로직이 녹아들게 되는 문제들이 생기는데, JPA는 도메인 클래스를 중시하게 되기 때문에 구현된 코드만 봐도 여러가지 문제가 해결 된 것이 보임
- 더 많은 테스트 케이스를 작성할 수 있고 SQL에 의존적이지 않기 때문에 자바의 컬렉션을 다루는 것처럼 단순해 지는 것이 많기 때문에 테스트 케이스 작성도 깔끔해지고 쉬워짐
- 가끔 JPA로 해결이 안되는 쿼리문들도 존재하는데 그럴때는 JdbcTemplate 붙혀서 SQL을 그대로 붙혀버리면 해결이 됨
6) SpringDataJPA 주의점
- SQL 공부 안해도 된다고 생각할 수도 있지만 절대 그렇지 않음
- 계속 반복적으로 강조하는데, 90%이상의 장애는 DB에서 발생하므로 DB 자체에 대한 기술을 알아야 문제를 해결할 수 있음
- ORM 기술을 처음 쓰고나서 문제가 발생하여 MyBatis나 JdbcTemplate로 돌아가겠다고 하는 경우도 존재하는데 버그가 아닌 JPA 기술 자체에 대한 이해가 부족해서 문제들이 발생하는 경우가 많기 때문에 실무에 ORM을 도입하고자 한다면 JPA를 먼저 공부하여 잘 알고 있어야 함
- 스프링데이터 JPA는 그냥 거들뿐이고 본질은 JPA(JPQL, 즉시로딩, 지원로딩, 영속성 컨텍스트 등등)를 무조건 잘 알아야 함
- JPA가 스프링 만큼 배울게 많고 배우기 쉬운 기술은 아니지만 한 번 배워놓고 나면 관계형 데이터베이스 자체가 급변하는 경우가 드물기 때문에 ORM 기술은 크게 업그레이드 할 게 없다고 봐도 무방함
3. 스프링 데이터 JPA 주요 기능
- 스프링 데이터 메뉴얼
- 스프링 데이터 JPA는 JPA를 편리하게 사용할 수 있도록 도와주는 라이브러리이며 많은 편리한 기능을 제공하지만 가장 대표적인 기능은 공통 인터페이스 기능, 쿼리 메서드 기능을 제공하는 것
1) 공통 인터페이스 기능
- JpaRepository 인터페이스를 통해서 기본적인 CRUD는 물론 페이징 기능 등 공통화 가능한 기능이 거의 모두 포함되어있음
- JpaRepository에 들어가서 구조를 보면 CRUD, 페이징,쿼리 관련 인터페이스들을 상속받아서 구성되어 있음
(1) JpaRepository 사용법
- JpaRepository 인터페이스를 사용하고자하는 Repository 인터페이스에 상속을 받고 제네릭에 관리할 <엔티티, 엔티티ID>를 입력해주면 JpaRepository가 제공하는 기본 CRUD 기능을 모두 사용할 수 있음
- JpaRepository 인터페이스만 상속 받으면 스프링 데이터 JPA가 프록시 기술을 사용해서 구현 클래스를 만들어주며 만든 구현 클래스의 인스턴스를 만들어서 스프링 빈으로 등록함
- 개발자는 구현 클래스 없이 인터페이스만 만들면 기본 CRUD 기능은 물론 다양한 기능을 사용할 수 있게 됨
public interface ItemRepository extends JpaRepository<Item, Long> {
}
2) 쿼리 메소드 기능
(1) 쿼리 메소드
- 인터페이스에 메서드만 적어두면, 메서드 이름을 분석해서 쿼리를 자동으로 만들어주고 실행해주는 기능을 제공함
- 순수 JPA를 직접 JPQL을 작성하고 파라미터도 직접 바인딩 해야 하지만 스프링 데이터 JPA는 메서드 이름을 분석해서 필요한 JPQL을 만들고 실행해주며 JPQL은 SQL 번역되어 실행됨
- 아무 이름이나 사용하는 것은 아니고 규칙을 따라야 가능함
// 순수 JPA 리포지토리의 메서드
public List<Member> findByUsernameAndAgeGreaterThan(String username, int age) {
return em.createQuery("select m from Member m where m.username = :username and m.age > :age")
.setParameter("username", username)
.setParameter("age", age)
.getResultList();
}
// 스프링 데이터 JPA - 메서드만 만들면 위의 코드를 자동으로 작성하고 실행해줌
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
}
(2) 스프링 데이터 JPA가 제공하는 쿼리 메소드 기능
- 메소드의 시작을 아래처럼 시작하고 By뒤에 And, Or 등의 조건들을 입력하면 됨
- 조회: find...By, read...By, query...By, get...By
- ex) findHelloBy처럼 ...에 식별하기 위한 내용이 들어가도 됨
- COUNT: count...By 반환타입 long
- EXISTS: exists...By 반환타입 boolean
- 삭제: delete...By, remove...By 반환타입 long
- DISTINCT: findDistinct, findMemberDistinctBy
- LIMIT: findFirst3, findFirst, findTop, findTop3 -> 리밋은 이렇게 잘 사용하진않고 페이징 관련 파라미터를 넣어서 사용함
- 스프링 데이터 JPA 공식 문서를 참고하면 더 다양한 쿼리 메소드 필터 조건을 확인할 수 있음
(3) JPQL 직접 사용하기
- 파라미터의 개수가 너무 많아 메소드이름이 길어지는 경우 @Query 애노테이션을 사용하여 JPQL을 직접 입력할 수 있으며 @Query가 붙은 메소드가 우선권을 가짐(기본 쿼리 메소드 기능의 규칙이 무시됨)
- JPQL뿐만 아니라 JPA의 네이티브 쿼리 기능도 지원하여 JPQL 대신에 SQL을 직접 작성할 수 있음
public interface SpringDataJpaItemRepository extends JpaRepository<Item, Long> {
//쿼리 메서드 기능
List<Item> findByItemNameLike(String itemName);
//쿼리 직접 실행
@Query("select i from Item i where i.itemName like :itemName and i.price <= :price")
List<Item> findItems(@Param("itemName") String itemName, @Param("price") Integer price);
}
** 중요
- 스프링 데이터 JPA는 JPA을 편리하게 사용하도록 도와주는 도구이므로 JPA 자체를 잘 이해하는 것이 가장 중요함 - 반복 강조
4. 스프링 데이터 JPA 적용1
- JPA를 설정하기 위해 build.gradle에 implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 를 추가하면 스프링 데이터 JPA도 추가됨
- 라이브러리에서 검색해보면 spring-data-jpa, spring-data-commons가 추가 되어있음
1) SpringDataJpaItemRepository
- 스프링 데이터 JPA가 제공하는 JpaRepository 인터페이스를 인터페이스 상속 받으면 기본적인 CRUD기능을 사용할 수 있지만 이름으로 검색하거나 가격으로 검색하는 기능은 공통으로 제공할 수 있는 기능이 아니기 때문에 쿼리 메서드 기능을 사용하거나 @Query를 사용해서 직접 쿼리를 실행 하면 됨
- 스프링 데이터 JPA는 동적쿼리에 약하므로 직접 4가지 상황을 스프링 데이터 JPA로 구현 -> 이후에 배우는 Querydsl로 동적 쿼리를 활용해서 깔끔하게 해결가능함
(1) findAll()
- 코드에는 보이지 않지만 JpaRepository 공통 인터페이스가 제공하며 모든 Item을 조회함
(2) findByItemNameLike()
- 이름 조건만 검색했을 때 사용하는 쿼리 메서드
- 실행되는 JPQL: select i from Item i where i.name like ?
(3) findByPriceLessThanEqual()
- 가격 조건만 검색했을 때 사용하는 쿼리 메서드
- 실행되는 JPQL: select i from Item i where i.price <= ?
(4) findByItemNameLikeAndPriceLessThanEqual()
- 이름과 가격 조건을 검색했을 때 사용하는 쿼리 메서드
- 실행되는 JPQL: select i form Item i where i.itemName like ? and i.price <= ?
- 파라미터는 순서대로 입력하면 됨
(5) findItems()
- 4번의 기능도 동일한 기능이지만 @Query 애노테이션을 사용하여 직접 쿼리를 사용해서 조회하는 메서드
- 쿼리 메서드 기능은 조건이 많으면 메서드 이름이 너무 길어지고, 조인 같은 복잡한 조건은 사용할 수 없다는 단점이 있어서 간단한 경우에는 쿼리 메서드를 사용하는것이 유용하지만, 복잡하면 직접 JPQL 쿼리를 작성하는 것이 좋음
- 파라미터를 @Param을 이용해서 명시적으로 바인딩해야하며 애노테이션의 값에 파라미터 이름을 주면 됨 (Srping-Data꺼를 사용해야함)
package hello.itemservice.repository.jpa;
public interface SpringDataJpaItemRepository extends JpaRepository<Item, Long> {
// 조건 없이 조회하는건 만들 필요없이 JpaRepository에 만들어져있는 메서드를 사용하면 됨
// 아이템 이름 조회
List<Item> findByItemNameLike(String itemName);
// 가격 조회
List<Item> findByPriceLessThanEqual(Integer price);
// 쿼리 메서드 -> 아래의 메서드와 같은 기능을 수행함
List<Item> findByItemNameLikeAndPriceLessThanEqual(String itemName, Integer price);
// 쿼리 직접 실행 - @Param(Spring-data꺼)를 꼭 넣어줘야함
@Query("select i from Item i where i.itemName like :itemName and i.price <= :price")
List<Item> findItems(@Param("itemName") String itemName, @Param("price")Integer price);
}
** 스프링 데이터 JPA도 Example이라는 기능으로 약간의 동적 쿼리를 지원하지만 실무에서 사용하기는 기능이 빈악하여 JPQL 동적쿼리는 Querydsl을 사용하는 것을 권장함
4. 스프링 데이터 JPA 적용2
1) JpaItemRepositoryV2
(1) save()
- 스프링 데이터 JPA가 제공하는 save()를 호출함
- CrudRepository 인터페이스가 가지고 있으며 구현하고있는 SimpleJpaRepository를 들어가서 보면 save()메서드에 결국 entityManager.persist()로 entity를 저장하고 있는 구조를 볼 수 있음
(2) update()
- 스프링 데이터 JPA가 제공하는 findById() 메서드를 사용해서 엔터티를 찾고 데이터를 수정
- 이후 트랙잭션이 커밋될 때 변경 내용이 데이터베이스에 반영됨(JPA가 제공하는 기능)
(3) findById()
- 스프링 데이터 JPA가 제공하는 findById()를 호출함
- CrudRepository 인터페이스가 가지고 있으며 구현하고있는 SimpleJpaRepository를 들어가서 보면 findById()메서드에 결국 entityManager.find()로 구현되어 있음
(4) findAll()
- 모든 데이터조회, 이름 조회, 가격 조회, 이름 + 가격 조회 총 4가지 조건에 따라 데이터를 분류해서 검색
- 모든 조건에 부합할 때는 쿼리메서드를 사용한 메서드나 직접 쿼리를 작성한 메서드 아무거나 적용해도 상관없지만, 조건이 2개만 되어도 이름이 너무 길어지는 단점이 존재하기에 상황에 따라 적절하게 선택하는 것이 필요함
- findAll()의 구현되어있는 모습을 보면 동적 쿼리가 아니라 상황에 따라 각각 스프링 데이터 JPA의 메서드를 호출해서 상당히 비효율 적인 코드인 것을 알 수 있는데, 앞서 설명했듯이 JPA는 동적 쿼리에 대한 지원이 매우 약하여 Querydls을 적용해서 동적 쿼리를 구현해야함
package hello.itemservice.repository.jpa;
@Repository
@Transactional
@RequiredArgsConstructor
public class JpaItemRepositoryV2 implements ItemRepository {
private final SpringDataJpaItemRepository itemRepository;
public Item save(Item item) {
return itemRepository.save(item);
}
@Override
public void update(Long itemId, ItemUpdateDto updateParam) {
Item findItem = itemRepository.findById(itemId).orElseThrow();
findItem.setItemName(updateParam.getItemName());
findItem.setPrice(updateParam.getPrice());
findItem.setQuantity(updateParam.getQuantity());
}
@Override
public Optional<Item> findById(Long id) {
return itemRepository.findById(id);
}
@Override
public List<Item> findAll(ItemSearchCond cond) {
String itemName = cond.getItemName();
Integer maxPrice = cond.getMaxPrice();
// 실무에서는 동적쿼리를 사용하고 이렇게 하지는 않음(조건이 2개정도일때는 함)
if (StringUtils.hasText(itemName) && maxPrice != null) {
// 동일한 기능을 하는 메서드 아무거나 호출
// return itemRepository.findByItemNameLikeAndPriceLessThanEqual("%" + itemName + "%", maxPrice); // 쿼리 메서드
return itemRepository.findItems("%" + itemName + "%", maxPrice); // 직접 쿼리를 입력한 메서드
} else if (StringUtils.hasText(itemName)) { // 이름정보만 존재하면 해당 메서드 반환
return itemRepository.findByItemNameLike("%" + itemName + "%");
} else if (maxPrice != null) { // 가격정보만 존재하면 해당 메서드 반환
return itemRepository.findByPriceLessThanEqual(maxPrice);
} else { // 위 조건이 모두 아니면 findAll()로 전체 조회
return itemRepository.findAll();
}
}
}
2)의존관계 구조
- ItemService는 ItemReposeitory에 의존하기 때문에 ItemService에서 SpringDataJpaItemRepsitory를 그대로 사용하려면 ItemService의 코드를 변경해야 함
- 지금 목적은 ItemService 코드의 변경없이 ItemService가 ItemRepository에 대한 의존을 유지하면서 DI를 통해 구현 기술을 변경하려고 하다보니 새로운 JpaItemRepostiroV2를 만들어서 ItemRepository와 SpringDataJpaItemRepository 사이를 맞추기 위한 어댑터 처럼 사용하도록 함
- JpaItemRepositoryV2는 ItemRepository를 구현하고 SpringDataJpaItemRepository를 사용함
- 런타임의 객체 의존관계는 itemService -> jpaItemRepository -> springDataJpaItemRepository(프록시 객체)로 동작함
- JpaItemRepositoryV2가 중간에서 어댑터 역할을 해준 덕분에 ItemService가 사용하는 ItemRepository 인터페이스를 그대로 유지하면서 클라이언트인 ItemService의 코드를 변경하지 않아도 됨
3) 설정 및 실행
(1) SpringDataJpaConfig 생성 및 ItemServiceApplication @Import 변경
- SpringDataJpaConfig를 생성 -> SpringDataJpaItemRepository을 주입하고 JpaItemRepositoryV2를 생성
- ItemServiceApplication의 Import를 @Import(SpringDataJpaConfig.class)로 변경
package hello.itemservice.config;
@Configuration
@RequiredArgsConstructor
public class SpringDataJpaConfig {
private final SpringDataJpaItemRepository itemRepository;
@Bean
public ItemService itemService() {
return new ItemServiceV1(itemRepository());
}
@Bean
public ItemRepository itemRepository() {
return new JpaItemRepositoryV2(itemRepository);
}
}
(2) 테스트 및 애플리케이션 실행
- 테스트는 DB실행 없이 해도 정상 동작 함
- H2 DB를 띄우고 애플리케이션 실행해보면 모든 기능이 정상 동작함
** 하이버네이트 5.6.6 ~ 5.6.7 버그
- 만약 강의에서 제공된 템플릿을 사용했을 때 스프링부트를 버전업 하지않고 2.6.5버전 하이버네이트 5.6.6 ~ 5.6.7 버전을 사용했다면 Like 문장을 사용할 때java.lang.IllegalArgumentException: Parameter value [\] did not match expected type [java.lang.String (n/a)] 예외가 발생됨
- 스프링 부트의 최신 버전을 사용해서 하이버네이트 등의 버전을 업그레이드하면 문제가 해결되어있음(초반 프로젝트 생성 강의를 참고)
(3) 예외 변환
- 스프링 데이터 JPA도 스프링 예외 추상화를 지원하여 스프링 데이터 JPA가 만들어주는 프록시에서 이미 예외변환을 처리하기 때문에 @Repository와 관계없이 예외가 변환됨
** 참고
- 스프링 데이터 JPA는 실무에서 기본으로 선택할 만큼 정말 수많은 편리한 기능을 제공하며 어렵게 사용해야하는 페이징을 위한 기능도 제공하고, 단순히 편리함을 넘어서 똑같은 코드로 중복 개발하는 부분을 개선해줌
- 스프링 데이터 JPA에 대한 자세한 내용은 실전! 스프링 데이터 JPA 강의에서 다룸