카테고리 없음

스프링 데이터 JPA

LimCoz 2022. 11. 5. 21:09

12.1 스프링 데이터 JPA 소개

스프링 데이터 JPA

 > 프레임워크에서 JPA를 편리하게 사용할수 있도록 지원하는 프로젝트이다.

 > 데이터 접근 계층을 개발할 때 지루하게 반복되는 CRUD 문제를 공통 인터페이스로 제공

 > 인터페이스만 작성하면 JPA가 어플리케이션 실행 시점에 구현 객체를 생성해서 주입해줌

 > 직접 작성한 메소드, 스프링 데이터 jpa가 메소드 이름을 분석해서 jpql을 실행해준다.

 

12.1.1 스프링 데이터 프로젝트

- 스프링 데이터 프로젝트 : 다양한 데이터 저장소 접근을 추상화하하여 개발 편의를 제공하는 프로젝트

- 스프링 데이터 JPA는 스프링 데이터 프로젝트의 하위 프로젝트중 하나이다.

 

 

12.2 스프링 데이터 JPA 설정

1) mavan 라이브러리 추가

  > spring-data-commons 에 의존함으로 함께 다운로드 받아진다.

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>1.9.6.RELEASE</version>
</dependency>

2) base-package 의 하위 패키지를 검색하도록 설정.

 - base-package 에서 JPA 인터페이스를 구현한 클래스를 동적으로 생성후, 스프릉 빈으로 등록해준다.

<jpa:repositories base-package="jpabook.jpashop.repository" />

 

12.3 공통 인터페이스 기능 제공

스프링 데이터 프로젝트
public interface Repository<T, ID> {}
public interface CrudRepository<T, ID> extends Repository<T, ID> {}
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {}

스프링 데이터 JPA 프로젝트
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>{}

 

 

#JpaRepositry 인터페이스 상속시, 사용할수 있는 메소드 (SimpleJpaRepository 가 구현해줌)

 - save : 새로운 엔티티 저장, 이미 있으면 수정

- delete : 엔티티 삭제, EntityManager.remove() 호출

- findOne : 엔티티 하나 조회, EntityManager.find() 

- getOne : 엔티티 프록시 조회, EntityManager.getReference() - 영

- findAll : 모든 엔티티 조회, 정렬/페이징 기능 제공

 

12.4 쿼리 메소드 기능 제공

스프링 데이터 JPA가 제공하는 쿼리 메소드 기능  3가지

 

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

 > JPA에서 제공하는 쿼리 생성 기능이 있다.(예약어 같이 조건절을 줄수 있음)

   ex) findByAgesLessThan -------------------- where x.age < ?

public interface MemberRepository extends JpaRepository<Member, Long> {

    List<Member> findByName(String name);
}


/* 결과 */
select
    member0_.MEMBER_ID as MEMBER_I1_4_,
    member0_.city as city2_4_,
    member0_.street as street3_4_,
    member0_.zipcode as zipcode4_4_,
    member0_.name as name5_4_ 
from
    Member member0_ 
where
    member0_.name=?

 

2. 메소드 이름으로 JPA NamedQuery 호출

@NamedQuery(
        name = "Member.findByUsername",
        query = "select m from Member m where m.name = :name"
)
public class Member {}
public interface MemberRepository extends JpaRepository<Member, Long> {

    List<Member> findByName(String name);
    List<Member> findByUsername(@Param("name") String name);
}

- JPA는 선언한 '도메인 클래스.+메소드 이름' 으로 naemd쿼리를 찾는다.

 /* named HQL query Member.findByUsername */ select
        member0_.MEMBER_ID as MEMBER_I1_4_,
        member0_.city as city2_4_,
        member0_.street as street3_4_,
        member0_.zipcode as zipcode4_4_,
        member0_.name as name5_4_ 
    from
        Member member0_ 
    where
        member0_.name=?

 

3. @Query 어노테이션 인터페이스 쿼리 직접 정의

public interface MemberRepository extends JpaRepository<Member, Long> {

    List<Member> findByName(String name);
    List<Member> findByUsername(@Param("name") String name);

	//jpql 파라미터 바인딩 1부터 시작(위치기반 바인딩)
    @Query(value = "select m from Member m where m.name = ?1")
    Member findByUserNameJpql(String name);

	//jpql 파라미터 바인딩 native 쿼리, 0부터 시작(위치기반 바인딩)
    @Query(value = "SELECT * FROM MEMBER HWERE USERNAME = ?0", nativeQuery = true)
    Member findByUserNameNative(String name);
    
    //jpql 파라미터 바인딩
    @Query(value = "select m from Member m where m.name = :name")
    Member fineByUserNameParamBinding(String name);
}

 

4. 파라미터 바인딩

스프링 데이터 jpa는 위치기반 파라미터 바인딩, 이름 기반 바인딩 모두 지원한다.(3번 참조)

@Query(value = "select m from Member m where m.name = :name")
Member fineByUserNameParamBinding(String name);

 

5. 벌크성 수정 쿼리

- @Modifying : 벌크성 수정, 삭제 쿼리 사용하는 어노테이션

@Modifying
//@Modifying(clearAutomatically = true) 영속성 컨텍스트 초기화 default 는 false;
@Query(value = "update Member m set m.name= :name") //member 로 사용하면 native로 인식함
int bulkUpdate(@Param("name") String name);

 

6. 반환 타입

스프링 데이터 JPA는 유연한 반환 타입 지원

- 컬렉션 
> 결과 O : 컬렉션에 담아옴
> 결과 X : 빈 컬렉션 반환(empty)
List<Member> findByName(String name);

- 단건
> 결과 O : 단건 반환
> 결과 X : null 반환
> 결과 2개이상 : 오류 발생(non unique exception) 
Member findByEmail(String email);

 

7. 페이징/정렬

스프링 데이터 JPA는 페이징과 정렬 기능을 사용할수 있도록 sort, Pageable 이라는 파라미터를 제공

//repo 정의
public interface MemberRepository extends JpaRepository<Member, Long> {
    Page<Member> findByNameStartingWith(String name, Pageable pageable);
}
public List<Member> selectPageMemberList(){
  /**
    페이징 조건 명시
     > 10건
     > 이름 내림차순
     > 이름이 '김'으로 시작하는 회원
    **/
    PageRequest pageRequest = new PageRequest(0,10,new Sort(Sort.Direction.DESC,"name"));
    Page<Member> result = memberRepository.findByNameStartingWith("김", pageRequest);
    List<Member> members = result.getContent(); //조회된 데이터
    int totalPages = result.getTotalPages(); //전체페이지 수
    boolean hasNextPage = result.hasNext(); //다음 페이지 존재 여부
    return members;
}

8. 힌트

- @QueryHints 어노테이션을 이용하여, JPA쿼리 힌트를 사용한다.

 >  실제 SQL힌트가 아닌 JPA구현체에 제공하는 힌트다.

public interface MemberRepository extends JpaRepository<Member, Long> {
/**
forCounting = true : 추가로 호출하는 페이징도 쿼리힌트를 적용할지 설정
readonly : 읽기전용으로, 영속성컨텍스트에 스냅샷을 저장하지 않는다.
**/
    @QueryHints(value = {
            @QueryHint(name = "org.hibernate.readOnly",value = "true")
    }, forCounting = true)
    Page<Member> findByName(String name, Pageable pageable);
}

9. lock

- JPA에서 제공하는 락

- @Lock(LockModeType.PESSIMISTIC_WRITE)

 > SELECT FOR ~ UPDATE
 > 일반적인 옵션. 데이터베이스에 쓰기 락
 > 다른 트랜잭션에서 읽기도 쓰기도 못함. (배타적 잠금)

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

 

12.5 명세

- JPA는 JpaSpecificationExecutor 상속받아 다양한 검색조건을 조립해서 새로운 검색조건을 쉽게 만들 수 있다.

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

 > JPA Criteria로 이개념을 사용할 수 있도록 지원

public interface OrderRepository extends JpaRepository<Order, Long>, JpaSpecificationExecutor<Order>, CustomOrderRepository {

}
//service, toSpecification 메소드 호출!
public List<Order> findOrders(OrderSearch orderSearch) {
   return orderRepository.findAll(orderSearch.toSpecification()); // Specification 사용
}
//OrderSearch.java
public Specifications<Order> toSpecification() {
    return where(memberNameLike(memberName))
            .and(orderStatusEq(orderStatus));
}
public class OrderSpec {
	
    public static Specification<Order> memberNameLike(final String memberName) {
        return new Specification<Order>() {
            public Predicate toPredicate(Root<Order> root, CriteriaQuery<?> query, CriteriaBuilder builder) {

                if (StringUtils.isEmpty(memberName)) return null;

                Join<Order, Member> m = root.join("member", JoinType.INNER); //회원과 조인
                return builder.like(m.<String>get("name"), "%" + memberName + "%");
            }
        };
    }

    public static Specification<Order> orderStatusEq(final OrderStatus orderStatus) {
        return new Specification<Order>() {
            public Predicate toPredicate(Root<Order> root, CriteriaQuery<?> query, CriteriaBuilder builder) {

                if (orderStatus == null) return null;

                return builder.equal(root.get("status"), orderStatus);
            }
        };
    }
}

 

12.6 사용자 정의 리포지토리 구현

- JpaRepository 인터페이스에서 제공하는 않는 경우 사용자가 직접 레포지토리를 구현할수 있는 방법을 제공한다.

public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom {
}
public interface MemberRepositoryCustom {
    public List<Member> findMemberCustom();
}
public class MemberRepositoryImpl implements MemberRepositoryCustom {

    @Override
    public List<Member> findMemberCustom() {
    	//querydsl 등 구현 하여 return
        return new ArrayList<Member>();
    }
}

12.7 WEB 확장

- 스프링 데이터 프로젝트는 스프링 MVC에서 사용할수 있는 기능을 제공한다.

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

<bean class="org.springframework.data.web.config.SpringDataWebConfiguration"></bean>
@RequestMapping(value = "member/memberUpdateForm",method = RequestMethod.POST)
@ResponseBody
public String memberUpdateForm(@RequestParam("id") Long id, Model model){
    Member member = memberRepository.findOne(id);
    return member.toString();
}

 

@RequestMapping(value = "member/memberUpdateForm",method = RequestMethod.POST)
@ResponseBody
public String memberUpdateForm(@RequestParam("id") Member member,Model model){
    return member.toString();
}

- 넘어온 파라미터 id를 바로 Member 에 넣어줌

 

2)  페이징/정렬 기능

 - 요청파라미터

  > page (현재 페이지) , size (한 페이지 건수), sort (정렬조건)

  > default 로 page 0, size : 20 이다.

 

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

- 스프링 데이터 JPA 공통 인터페이스는 SimpleJpaRepository 클래스가 구현함.

@Repository
@Transactional(
    readOnly = true
)
public class SimpleJpaRepository<T, ID extends Serializable> implements JpaRepository<T, ID>, JpaSpecificationExecutor<T> {
	....
    
    @Transactional
    public <S extends T> S save(S entity) {
        if (this.entityInformation.isNew(entity)) {
            this.em.persist(entity);
            return entity;
        } else {
            return this.em.merge(entity);
        }
    }
}

 

12.10 스프링 데이터 JPA와 QueryDSL 통합

- QueryDsl 사용 2가지 방법, 두가지 인터페이스 상속

 1) QueryDslPredicateExecutor : 기능에 한계가 있다.(join, fetch 사용 불가)

 2) QueryDslRepositorySupport : QueryDsL의 모든기능을 사용가능

 

- 레파지토리

public interface OrderRepository extends JpaRepository<Order, Long>, CustomOrderRepository {
}

- 사용자 커스텀 레파지토리

public interface CustomOrderRepository {
    public List<Order> search(OrderSearch orderSearch);
}

- 사용자  레파지토리 구현 상세

public class OrderRepositoryImpl extends QueryDslRepositorySupport implements CustomOrderRepository {

    public OrderRepositoryImpl() {
        super(Order.class);
    }

    @Override
    public List<Order> search(OrderSearch orderSearch) {
        QOrder order = QOrder.order;
        QMember member = QMember.member;

        JPQLQuery query = from(order);

        if (StringUtils.hasText(orderSearch.getMemberName())) {
            query.leftJoin(order.member, member)
                    .where(member.name.contains(orderSearch.getMemberName()));
        }

        if (orderSearch.getOrderStatus() != null) {
            query.where(order.status.eq(orderSearch.getOrderStatus()));
        }

        return query.list(order);
    }
}