스프링 데이터 JPA 리포지토리
공통 인터페이스 분석
공통 인터페이스 설정
@Configuration @EnableJpaRepositories(basePackages = "jpabook.jpashop.repository") public class AppConfig {} >스프링부트 사용시 @SpringBootApplication 위치를 지정(해당 패키지와 하위 패키지 인식) 만약 위치가 달라지면 @EnableJpaRepositories 필요 @Repository 애노테이션 생략가능
스프링 데이터 JPA 기반 리포지토리
public interface MemberRepository extends JpaRepository<Member, Long> {} >JpaRepository 인터페이스 : 공통CRUD 제공 제네릭타입 <엔티티타입,식별자타입> 설정
- 제네릭 타입
- T: 엔티티 - ID: 엔티티의 식별자 타입 - S: 엔티티와 그 자식 타입
- 주요메서드
- save(S) : 새로운 엔티티는 저장하고 이미 있는 엔티티는 병합 - delete(T) : 엔티티 하나를 삭제한다. 내부에서 EntityManager.remove() 호출 - findById(ID) : 엔티티 하나를 조회한다. 내부에서 EntityManager.find() 호출 - getOne(ID) : 엔티티 프록시로 조회한다. 내부에서 EntityManager.getReference() 호출 - findAll(..) : 모든 엔티티를 조회한다. 정렬이나 페이징 조건을 파라미터로 제공
- 제네릭 타입
쿼리메서드 기능
스프링 데이터 JPA가 제공하는 쿼리 메서드 기능
- 조회: find...By, read...By, query...By, get...By ex)findNameBy 처러 ...에 식별하기 위한 내용 - COUNT : count...By / 반환 타입 long - EXISTS : exists...By / 반환타입 boolean - 삭제 : delete...By, remove...By / 반환타입 long - DISTINCT : findDistinct, findMemberDistinctBy - LIMIT : findFirst3, findFirst, findTop, findTop3 >> 이 기능은 엔티티 필드명이 변경되면 인터페이스에 정의된 메서드 명도 함께 변경해야한다.
NamedQuery
엔티티에 @NamedQuery 에노테이션으로 쿼리 지정
@Entity @NamedQuery( name = "Member.findByUsername", query = "select m from Member m where m.username = :username") public class Member{ ... }
JPA를 직접 사용해서 NamedQuery 호출
public class MemberRepository{ public List<Member> findByUsername(String username) { ... //createNamedQuery에 엔티티에서 지정한 NamedQuery 명을 사용 List<Member> result = em.createNamedQuery("Member.findByUsername",Member.class) .setParameter("username",username) .getResultList(); } }
스프링 데이터 JPA로 NamedQuery사용
@Query(name = "Member.findByUsername") List<Member> findByUsername(@Param("username") String username);
스프링 데이터 JPA로 Named 쿼리 호출
public interface MemberRepository extends JpaRepository<Member, Long> { List<Member> findByUsername(@Param("username") String username); } >스프링 데이터 JPA는 선언한 "도메인클래스.메서드이름"으로 Named쿼리를 찾아서 실행 Named쿼리가 없으면 메서드 이름으로 쿼리 생성전략 사용
Query, 리포지토리 메서드에 쿼리 정의하기
메서드에 JPQL 쿼리 작성
public interface MemberRepository extends JpaRepository<Member,Long> { @Query("select m from Member m where m.username= :username and m.age = :age") List<Member> findUser(@Param("username") String username, @Param("age") int age); } >실행할 메서드에 정적쿼리를 직접작성하므로 이름 없는 Named쿼리이다. JPA Named쿼리처럼 애플리케이션 실행지점 문법오류 확인 가능
@Query, 값, DTO 조회하기
- 단순히 값 하나를 조회 @Query("select m.username from Member m") List<String> findUsernameList(); - DTO 직접 조회 @Query("select new study.datajpa.dto.MemberDto(m.id,m.username, t.name) from Member m join m.team t") List<MemberDto> findMemberDto(); >DTO 직접 조회시 'new'명령어를 사용해야한다.그리고 다음과 같이 생성자가 맞는 DTO가 필요
파라미터 바인딩
select m from Member m where m.username = ?0 //위치기반 select m from Member m where m.username = :name //이름 기반 public interface MemberRepository extends JpaRepository<Member,Long>{ @Query("select m from Member m where m.username = :name") Member findMembers(@Param("name") Strin username); } >코드 가독성과 유지보수를 위해 이름기반 파라미터 바인딩 사용
반환타입
스프링 데이터JPA는 유연한 반환타입 지원조회 결과가 많거나 없을시 컬렉션 : 결과없음 > 빈 컬렉션 반환 단건조회 : 결과없음 > null 반환 / 결과가 2건이상시 > 예외발생
스프링데이터 JPA 페이징과 정렬
페이징과 정렬 파라미터
org.springframework.data.domain.Sort : 정렬기능
org.springframework.data.domain.Pageable : 페이징기능(내부에 Sort포함)특별한 반환 타입
org.springframework.data.domain.PAge : 추가 count쿼리 결과를 포함하는 페이징
org.springframework.data.domain.Slice : 추가 count쿼리 없이 다음페이지만 확인 가능(내부적으로 limit +1 조회)
List : 추가count 쿼리없이 결과만 반환Page<Member> findByUsername(String name,Pageable pageable); //count 쿼리 사용 Slice<Member> findByUsername(String name,Pageable pageable); //count 쿼리 사용 안함 List<Member> findByUsername(String name,Pageable pageable); //count 쿼리 사용 안함 List<Member> findByUsername(String name,Sort sort); //count 쿼리 사용
Page 사용 예제
public void page() { memberRepository.save(new Member("member1",10)); ...~ memberRepository.save(new Member("member10",10)); PageRequest pageRequest = PageRequest.of(0,3, Sort.by(Sort.Direction.DESC,"username")); Page<Meber> page = meberRepository.findByAge(10,pageRequest); List<Member> content = page.getContent(); //조회된 데이터 }
Page 인터페이스
public interface Page<T> extends Streamable<T> { int getTotalPages(); long getTotalElenets(); <U> Page<U> map(Function<? super T, ? extends U> converter); }
Slice 인터페이스
public interface Slice<T> extends Streamable<T> { int getNumber(); //현재 페이지 int getSize(); //페이지 크기 int getNumberOfElements(); //현재 페이지에 나올 데이터 수 List<T> getContent(); //조회된 데이터 boolean hasContent(); //조회된 데이터 존재 여부 Sort getSort(); //정렬 정보 boolean isFirst(); //현재 페이지가 첫 페이지 인지 여부 boolean isLast(); //현재 페이지가 마지막 페이지 인지 여부 boolean hasNext(); //다음 페이지 여부 boolean hasPrevious(); //이전 페이지 여부 Pageable getPageable(); //페이지 요청 정보 Pageable nextPageable(); //다음 페이지 객체 Pageable previousPageable();//이전 페이지 객체 <U> Slice<U> map(Function<? super T, ? extends U> converter); //변환기 }
>count쿼리를 @Query에서 분리 가능 @Query(value = "select m from Member m ", countQuery = "select count(m.username) from Member m") >페이지를 유지하면서 엔티티를 DTO로 변환 Page<Member> page = memberRepository.findByAge(10, pageRequest); Page<MemberDto> dtoPage = page.map(m -> new MemberDto());
벌크성 수정 쿼리
@Modifying @Query("update Member m set m.age = m.age + 1 where m.age >= :age") int bulkAgePlus(@Param("age") int age); >벌크성 수정,삭제 쿼리는 @Modifying 에노테이션 사용 벌크성 쿼리 실행하고 나서 영속성 컨텍스트 초기화 : @Modifying(clearAutmatically = true) 기본값은 false 벌크 연산은 영속성 컨텍스트를 무시하고 실행하기 때문에 영속성 컨텍스트에 있는 엔티티와 DB에 엔티티의 상태가 달라 질수 있다. 영속성 컨텍스트에 엔티티가 없는 상태에서 벌크연산을 실행. 부득이하게 영속성 컨텍스트에 엔티티가 있다면 벌크연산 직후 영속성 컨텍스트 초기화.
@EntityGraph
연관된 엔티티들을 SQL 한번에 조회하는 방법
사실상 페치조인의 간편 버전
LEFT OUTER JOIN 사용//공통 메서드 오버라이드 @Override @EntityGraph(attributePaths = {"team"}) List<Member> findAll(); //JPQL + 엔티티 그래프 @EntityGraph(attributePaths = {"team"}) @Query("select m from Member m") List<Member> findMemberEntityGraph(); //메서드 이름으로 쿼리에서 특히 편리하다. @EntityGraph(attributePaths = {"team"}) List<Member> findByUsername(String username)
NamedEntityGraph 사용방법
@NamedEntityGraph(name = "Member.all", attributeNodes = @NamedAttributeNode("team")) @Entity public class Member {} @EntityGraph("Member.all") @Query("select m from Member m") List<Member> findMemberEntityGraph();
Auditing
설정 방법
@EnableJpaAuditing : 스프링부트 설정 클래스에 적용
@EntityListeners(AuditingEntityListener.class) : 엔티티에 설정
사용 에노테이션
@CreatedDate
@LastModifiedDate
@CreatedBy
@LastModifiedBy스프링데이터 Auditing 적용
@EntityListeners(AuditingEntityListener.class) @MappedSuperclass public class BaseEntity { @CreatedDate @Column(updatable = false) private LocalDateTime createdDate; @LastModifiedDate private LocalDateTime lastModifiedDate; @CreatedBy @Column(updatable = false) private String createdBy; @LastModifiedBy private String lastModifiedBy; }
Base타입을 분리하고 원하는 타입을 선택해서 상속방법
public class BaseTimeEntity { @CreatedDate @Column(updatable = false) private LocalDateTime createdDate; @LastModifiedDate private LocalDateTime lastModifiedDate; } public class BaseEntity extends BaseTimeEntity { @CreatedBy @Column(updatable = false) private String createdBy; @LastModifiedBy private String lastModifiedBy; }
'Spring' 카테고리의 다른 글
Spring Security 내부 흐름 (0) | 2024.10.22 |
---|---|
Querydsl 기본 사용법 (2) | 2024.09.30 |
Spring JPA (2) (0) | 2024.09.30 |
Spring JPA (1) (0) | 2024.09.30 |
Spring DB 데이터 접근 기술 (0) | 2024.09.30 |