본문 바로가기

웹 프로그래밍/JPA

스프링 데이터 JPA

메소드 이름만으로 쿼리 생성 기능(강력함): 간단한 쿼리들 해결가능

 

findByLastnameAndFisrtname -> where x.lastname = ?1 and x.fisrtname = ?2

findByFirstname(Is) -> where x.fisrtname = ?1

findByStartDateBetween -> where x.startDate between ?1 and ?2

findByAgeGreaterThanEqual -> where x.age >= ?1

findByFirstnameLike -> where x.firstname like ?1

findByFirstnameContaining -> where x.firstname like ?1 ('%member%')

findByAgeOrderByLastnameDesc -> where x.age = ?1 order by x.lastname desc

findByAgeIn(Collection<Age> ages) -> where x.age in ?1

findByActiveTrue() -> where x.active = true 

 

너무 이름이 길어지는 단점이 있음


@Query, 리포지토리 메소드에 쿼리 바로 정의하기(실무)

 

@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);

 

애플리케이션 로딩시점에 문법오류 잡아줌


DTO로 조회하기

@Query("select new study.datajpa.dto.MemberDto(m.id, m.username, t.name) from Member m join m.team t")

List<MemberDto> findMemberDto();


스프링 데이터 JPA에서 페이징 하기

Page<Member> findByAge(int age, Pageable pageable);

int age = 10;
PageRequest pageRequest = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "username"));

Page<Member> page = memberRepository.findByAge(age, pageRequest);

 

Slice<Member> : N+1 만큼, 즉 한개더 가지고옴(e.g. 더보기)

 

@Query(value = "select m from Member m left join m.team t",
            countQuery = "select count(m) from Member m") -> 최적화를 위한 카운터 쿼리 분리(카운터는 조인할 필요 없음)


벌크성 수정 쿼리: 전 직원 연봉 10% 인상

@Modifying
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
int bulkAgePlus(@Param("age") int age); -> @Modifying 필수

 

벌크연산은 영속성 컨텍스트를 무시함으로 주의해서 써야함(즉 DB는 업데이트 되나 영속성 컨텍스트는 그대로)

-> 해결책으로 em.flush(), em.clear()로 영속성 컨텍스트 날려야함 또는 스프링 데이터 JPA 에서

@Modifying(clearAutomatically = true)로 변경


fetch join: 연관관계가 있는 객체 즉, 객체그래프(Member.team)를 한꺼번에 조회하는 기능

(일반 조인과는 달리 select 절에 데이터를 다 넣어줌)

select m from Member m left join fetch m.team

 

엔티티 그래프: 스프링 데이터에서 fetch join 기능을 간편히 제공

@EntityGraph(attributePaths = {"team"})

 

@EntityGraph(attributePaths = {"team"})
List<Member> findEntityGraphByUsername(@Param("username") String username);


쿼리 힌트

변경 감지(dirty checking)은 편리하지만 비용은 드는 기능 -> 그냥 조회만 하고싶을때 최적화

@QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value = "true"))


사용자 정의 리포지토리(실무)

커스텀 기능을 구현하려면 전부 Override해야함(불가)

-> 커스텀 기능 인터페이스와 그 구현체를 만들어서 구현하면 스프링이 그 구현체의 메서드를 실행시켜줌

 

(주의) 네이밍 규칙: MemberRepositoryImpl -> 그래야 인식

 

복잡한 동적쿼리를 짜야할때 Querydls과 함께쓰면 강력한 위력을 발휘

항상 사용자 정의 리포지토리가 필요한것은 아님

핵심 비지니스 로직 리포지토리와과 API(화면, DTO) 리포지토리를 분리하는것이 중요

(e.g. MemberQueryRepository에 복잡한 쿼리 분리 )


Auditing: 등록일, 수정일, 등록자, 수정자 추적(왠만해서 공통 필수)

e.g. JpaBaseEntity를 만들고 상속해서 어디든 사용

@PrePersist
public void perPersist(){
    LocalDateTime now = LocalDateTime.now();
    createDate = now;
    updateDate = now;
}

@PreUpdate
public void preUpdate(){
    updateDate = LocalDateTime.now();
}

 

스프링 데이터 JPA로 간평히 Auditing

어플리케이션에

@EnableJpaAuditing

어노테이션 반드시 해줘야함

e.g. BaseEntity를 만들고 상속해서 어디든 사용

@EntityListeners(AuditingEntityListener.class)
@Getter
@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;
}

도메인 클래스 컨버터

@GetMapping("/members1/{id}")
public String findMember(@PathVariable("id") Long id){
    Member member = memberRepository.findById(id).get();
    return member.getUsername();
}

@GetMapping("/members2/{id}")
public String findMember2(@PathVariable("id") Member member){
    return member.getUsername();
}

원래는 위처럼 PK로 매핑해줘야하나

스프링 데이터 JPA는 그 과정을 대신해서 member.id에 넣어줌(주의: 조회용으로만 써야함)


페이징과 정렬

@GetMapping("/members")
public Page<MemberDto> list(@PageableDefault(size=5, sort="username") Pageable pageable){
    Page<Member> page = memberRepository.findAll(pageable);
    Page<MemberDto> map = page.map(member -> new MemberDto(member));
    return map;
}

 

스프링이 구현체(PageRequest)를 인젝션 해줘서 가능

요청 파라미터(Postman)

http://localhost:8080/members?page=0&size=3&sort=id,desc

주의: DTO를 써야함


새로운 엔티티를 구별하는 방법: Id가 객체일때는 null, 자바타입일때는 0 이면 새로운 엔티티로 인식하여 persist

항상 데이터에 대한 변경은 트랙잭션 안에서 변경감지를 써야하고 저장은 persist를 써야merge는 비권장

merge를 안쓰고 싶은데 Id를 특별한 값으로 쓰고 싶다? 아래처럼 isNew 재정의

@Entity
@EntityListeners(AuditingEntityListener.class)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Item implements Persistable<String> {

    @Id
    private String id;

    @CreatedDate
    private LocalDateTime createdDate;

    public Item(String id){
        this.id = id;
    }

    @Override
    public String getId() {
        return id;
    }

    @Override
    public boolean isNew() {
        return createdDate == null;
    }
}

네이티브 쿼리: SQL을 직접 쓰고 싶을때 사용

@Query(value = "select * from member where username = ?", nativeQuery = true)
Member findByNativeQuery(String username);

'웹 프로그래밍 > JPA' 카테고리의 다른 글

Querydsl 중급  (0) 2023.03.22
Querydls 기본  (3) 2023.03.21
JPA 활용(2) 팁 정리  (0) 2023.03.16
JPA 활용(1) 팁 정리  (0) 2023.03.08
객체 지향 쿼리 언어_중급  (0) 2023.03.08