자바 ORM 표준 JPA 프로그래밍 - 기본편 챕터 12 정리

정종일·2023년 6월 27일
0

Spring

목록 보기
16/18

1. 스프링 데이터 JPA


데이터 접근계층을 개발할 때 구현 클래스 없이 인터페이스만 작성해도 개발을 완료할 수 있다

1) 스프링 데이터 프로젝트


스프링 데이터 JPA는 스프링 데이터 프로젝트의 하위 프로젝트 중 하나이며 다양한 데이터 저장소에 대한 접근을 추상화해서 개발자 편의를 제공하고 지루하게 반복하는 데이터 접근 코드도 줄여줌

2. 스프링 데이터 JPA 설정


// gradle
org.springframework.boot:spring-boot-starter-data-jpa
// JavaConfig 설정
@Configuration
@EnableJpaRepositories(basePackages = "jpabook.jpashop.repository")
public class AppConfig {}

어플리케이션을 실행할 때 basePackage에 있는 리포지토리 인터페이스들을 찾아서 해당 인터페이스를 구현한 클래스를 동적으로 생성한 다음 스프링 빈으로 등록

3. 공통 인터페이스 기능


간단한 CRUD 기능을 공통으로 처리하는 JpaRepository 인터페이스를 제공

public interface MemberRepository extends JpaRepository<Member, Long> {}
  • save(S) : 새로운 엔티티는 저장, 이미 존재하는 엔티티는 수정
  • delete(T) : 엔티티 삭제
  • findOne(ID) : 엔티티 하나 조회
  • getOne(ID) : 엔티티를 프록시로 조회
  • findAll(...) : 모든 엔티티를 조회. 정렬이나 페이징 조건을 파라미터로 제공 가능

4. 쿼리 메소드 기능


메소드만 선언하면 해당 메소드의 이름으로 적절한 JPQL 쿼리를 생성해서 실행한다.

  • 메소드 이름으로 쿼리 생성
  • 메소드 이름으로 JPA NamedQuery 호출
  • @Query을 사용해서 레포지토리 인터페이스에 쿼리 직접 정의

1) 메소드 이름으로 쿼리 생성


public interface MemberRepository extends JpaRepository<Member, Long> {
		List<Member> findByEmailAndName(String email, String name);
}
// 실행 SQL
SELECT m FROM Member m where m.email = ?1 and m.name = ?2

참고 자료: http://docs.spring.io/spring-data/jpa/docs/1.8.0.RELEASE/reference/

2) JPA NamedQuery


쿼리에 이름을 부여해서 사용하는 방법

// 정의
@Entity
@NamedQuery(
		name = "Member.findByUsername",
		query = "select m from Member m where m.username = :username")
public class Member {...}
// 사용
public class MemberRepository {
		public List<Member> findByUsername (String username) {
				...
				List<Member resultList =
						em.createNamedQuery("Member.findByUsername", Member.class)
								.setParameter("username", "종욱")
								.getResultList();
		}
}

// 정의 된 네임드 쿼리 사용
public interface MemberRepository extends JpaRepository<Member, Long> {
		List<Member> findByUsername(@Param("username") String username);
}

3) @Query, 레포지토리 메소드에 쿼리 정의


public interface MemberRepository extends JpaRepository<Member, Long> {
		@Query("select m from Member m where m.username = ?1")
		Member findByUsername(String username);
}

// 네이티브 쿼리의 경우 아래와 같으며 파라미터 바인딩이 0부터 시작

		@Query("SELECT * FROM MEMBER WHERE USERNAME = ?0")
		

4) 파라미터 바인딩


위치기반, 이름기반 파라미터 바인당 모두 지원

  • ?1 → 위치기반
  • :name → 이름 기반

가독성과 유지보수를 위해 이름기반 파라미터 바인딩 사용 권장

5) 벌크성 수정 쿼리


int bulkPriceUp(String stockAmount) {
		...
		String qlString = 
				"update Product p set p.price = p.price * 1.1 where
						p.stockAmount < :stockAmount";

		int resultCount = em.createQuery(qlString)
												.setParameter("stockAmount", stockAmount)
												.executeUpdate();

}
@Modifying
@Query("update Product p set p.price = p.price * 1.1 where p.stockAmount < :stockAmount")
int bulkPriceUp(@Param("stockAmount") String stockAmount);

벌크성 쿼리 실행 후 영속성 컨텍스트를 초기화 하고싶다면

@Modifying(clearAutomatically = true) 를 해주면 된다.

6) 반환 타입


단건조회는 예외를 무시하고 null 을 반환한다.

7) 페이징과 정렬


org.springframework.data.domain.Sort : 정렬

org.springframework.data.domain.Pageable : 페이징

// count 쿼리를 추가로 호출
Page<Member> findByName(String name, Pageable pageable);

// count 쿼리 미호출
List<Member> findByName(String name, Pageable pageable);

List<Member> findByName(String name, Sort sort);

8) 힌트


@QueryHints(value = { @QueryHint(name = "org.hibernate.readOnly", value = "true")},
		forCounting = true)
Page<Member> findByName(String name, Pageable pageable);

JPA에 제공하는 힌트

  • forCounting : 반환타입으로 Page 인터페이스를 적용하면 추가로 호출하는 count 쿼리에도 쿼리 힌트를 적용할지 설정하는 옵션

9) Lock


@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Member> findByName(String name);

5. 명세


JpaSpecificationExcutor 의 메소드들은 Specification 을 파라미터로 받아서 검색 조건으로 사용

public List<Order> findOrders(String name) {
		List<Order> result = orderRepository.findAll(
				where(memberName(name)).and(isOrderStatus())		
		);
		return result;
}

Specifications 는 명세들을 조립할 수 있도록 도와주는 클래스

where(), or(), and(), not() 메소드를 제공

6. 사용자 정의 레포지토리 구현


public interface MemberRepositoryExtension {
		public List<Member> findMemberExtension();
}
public class MemberRepositoryImpl implements MemberRepositoryExtension {
		@Override
		public List<Member> findMemberExtension() {
				...
		}
}
public interface MemberRepository extends JpaRepository<Member, Long>
																			,MemberRepositoryExtension {};

위와 같이 레포지토리 인터페이스에서 사용자 정의 인터페이스를 상속받으면 된다

7. Web 확장


1) 설정


org.springframework.boot:spring-boot-starter-web
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
public class WebAppConfig {
		...
}

설정을 완료하면 도메인 클래스 컨버터와 페이징과 정렬을 위한 HandlerMethodArgumentResolver 가 스프링 빈으로 등록된다

2) 도메인 클래스 컨버터 기능


도메인 클래스 컨버터는 HTTP 파라미터로 넘어온 엔티티의 아이디로 엔티티 객체를 찾아서 바인딩해준다.

@RequestMapping("member/memberUpdateForm") // 아래와 같이 'id'로 멤버를 바로 찾아줌
public String memberUpdateForm(@RequestParam("id") **Member member**, Model model) {
		...
}

컨버터가 중간에 동작해서 아이디를 회원 엔티티 객체로 변환해서 넘겨준다.

와.. 개꿀

3) 페이징과 정렬 기능


  • page : 현재 페이지, 0부터 시작
  • size : 한 페이지에 노출할 데이터 수
  • sort : 정렬 조건 정의
‼️ 페이지를 1부터 시작하고 싶다면 `PageableHandlerMethodArgumentResolver` 를 스프링 빈으로 직접 등록하고 `setOneIndexedParameters`를 true로 설정하면 됨
  • 접두사
    // 사용해야 할 페이징 정보가 둘 이상
    public String list (
    		@Qualifier("member") Pageable memberPageable,
    		@Qualifier("order") Pageable orderPageable
    )
  • 기본 값
    // Pageable 의 기본값 변경
    @RequestMapping(value = "/members_page", method = RequestMethod.GET)
    public String list(@PageableDefault(size = 12, sort = "name", 
    		direction = Sort.Direction.DESC) Pageable pageable) {
    		...
    }

8. 스프링 데이터 JPA가 사용하는 구현체


  • @Repository : JPA 예외를 스프링이 추상화한 예외로 변환
  • @Transactional : JPA의 모든 변경은 트랜잭션 안에서 이루어져야한다 (등록, 수정, 삭제)
  • @Transactional(readOnly = true) : 데이터를 조회하는 메소드에는 readOnly = true 를 사용하면 플러시를 생략해서 약간의 성능 향상을 얻을 수 있다
  • save() 메소드 : 저장과 병합

9. 생략


10. 스프링 데이터 JPA와 QueryDSL 통합


2가지 방법으로 QueryDSL을 지원

org.springframework.data.querydsl.QueryDslPredicateExcutor

org.springframework.data.querydsl.QueryDslRepositorySupport

1) QueryDslPredicateExecutor


public interface ItemRepository extends JpaRepository<Item, Long>, 
		QueryDslPredicateExecutor<Item> { }

QueryDslPredicateExecutor는 편리하게 QueryDSL을 사용할 수 있지만 기능에 한계가 존재

join, fetch등을 사용할 수 없다 (JPQL에서 이야기하는 묵시적 조인은 사용가능)

이를 사용하려면 JPAQuery 혹은 QueryDslRepositorySupport 를 사용해야 한다.

2) QueryDslRepositorySupport


package org.springframework.data.jpa.repository.support;

@Repository
public abstract class QueryDslRepositorySupport {
		
		// 엔티티 매니저 반환
		protected EntityManager getEntityManager() {
				return entityManager;
		}

		// from 절 반환
		protected JPQLQuery from(EntityPath<?> ... paths) {
				return querydsl.createQuery(paths);
		}

		// QueryDSL delete 절 바환
		protected DeleteClause<JPADeleteClause> delete(EntityPath<?> path) {
				return new JPADeleteClause(entityManager, path);
		}

		// QueryDSL update 절 반환
		protected UpdateClause<JPAUpdateClause> update(EntityPath<?> path) {
				return new JPAUpdateClause(entityManager, path);
		}

		// 스프링 데이터 JPA가 제공하는 Querydsl을 편하게 사용하도록 돕는 헬퍼 객체 반환
		protected Querydsl getQuerydsl() {
				return this.querydsl;
		}
}
profile
제어할 수 없는 것에 의지하지 말자

0개의 댓글