모르지 않다는 것은 아는것과 다르다.

Spring Jpa

Spring Data Jpa

채마스 2022. 2. 27. 00:53

Spring Data JPA란?

  • Spring Data JPA는 Spring에서 제공하는 모듈이다.
  • JPA, 하이버네이트를 몰라도 되어야 한다. -> 추상화의 이점
  • JPA를 한 단계 추상화시킨 Repository라는 인터페이스를 제공함으로써 이루어진다.
  • Spring Data JPA의 Repository의 구현에서 JPA를 사용하고 있다.
  • JPA 구현체의 사용을 감추고, 다양한 지원과 설정 방법을 제공한다.
  • JPA 기본 구현체로 Hibernate 사용한다.
  • Querydsl 지원를 지원한다.
  • 아래와 같은 것들을 사용할 필요가 없다.
    • EntityManager 를 직접 사용하지 않는다.
    • JPQL을 직접 사용하지 않는다.
    • persist(), merge(), close() 를 직접 사용하지 않는다.
    • 트랜잭션을 getTransaction(), commit(), rollback() 으로 관리하지 않는다.
  • CRUD 처리를 위한 공통 인터페이스 제공한다.
  • 공통 인터페이스 종류는 대략 아래와 같다.
  • count, delete, deleteAll, deleteAll, deleteById, existsById, findById, save.
  • 공통 메소드가 아닐 경우에도 스프링 데이터 JPA가 메소드 이름을 분석해서 JPQL을 실행할 수 있다.




JPA vs Data JPA

JPA만으로 Repository를 구현한다면?

@Repository
public class TeamJpaRepository {
    @PersistenceContext
    private EntityManager em;

    public Team save(Team team) {
        em.persist(team);
        return team;
    }
    public void delete(Team team) {
        em.remove(team);
    }

    public List<Team> findAll() {
        return em.createQuery("select t from Team t", Team.class)
                .getResultList();
    }
    public Optional<Team> findById(Long id) {
        Team team = em.find(Team.class, id);
        return Optional.ofNullable(team);
    }
    public long count() {
        return em.createQuery("select count(t) from Team t", Long.class)
                .getSingleResult();
    }
}
  • 위와 같이 JPA만으로 Repository를 구현할 수 있다.
  • 이 방식도 나쁘진 않다.
  • 하지만 Data JPA의 힘을 빌리면...




Data Jpa를 쓴다면

public interface TeamRepository extends JpaRepository<Team, Long> {
}
  • 위의 한 문장으로 끝...

  • Data JPA는 더 많은 기능을 제공한다.




JPA만 쓴다면?

@Repository
public class MemberJpaRepository {
    @PersistenceContext
    private EntityManager em;

    public List<Member> findByPage(int age, int offset, int limit){
        return em.createQuery("select m from Member m where m.age = :age order by m.username desc")
                .setParameter("age", age)
                .setFirstResult(offset)
                .setMaxResults(limit)
                .getResultList();
    }

    public long totalCount(int age){
        return em.createQuery("select count(m) from Member m where m.age = :age", Long.class)
                .setParameter("age", age)
                .getSingleResult();
    }

    public int bulkAgePlus(int age){
        int resultCount = em.createQuery(
                "update Member m set m.age = m.age + 1" +
                        " where m.age >= :age")
                .setParameter("age", age)
                .executeUpdate();
        return resultCount;
    }

}



Data JPA 사용 예시

//현재 실무에서 가장 많이 씀  --> 로딩시점에서 파싱이 이루어지므로 개발할때 안전함
@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); 
//값 자체를 받아올 때
@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();
//컬랙션 바인딩 받을때 (바인딩은 주로 이름기반!)
@Query("select m from Member m where m.username in :name")
List<Member> findByNames(@Param("name") Collection<String> names);
@Query(value = "select m from Member m",countQuery = "select count(m.username) from Member m") //count 쿼리를 따로 정의 해서 최적화할 수 있음
Page<Member> findByAge(int age, Pageable pageable);   //count 쿼리 사용
Slice<Member> findByAge(int age, Pageable pageable); //count 쿼리 사용 안함
List<Member> findByAge(int age, Pageable pageable); //count 쿼리 사용 안함
@Modifying(clearAutomatically = true)
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
int bulkAgePlus(@Param("age") int age);
//@Query(name = "Member.findByUsername") //우선순위가 얘가 더 높아서 굳이 안적어도 됨
List<Member> findByUsername(@Param("username") String username);
List<Member> findByUsernameAndAgeGreaterThan(String username, int age);



Fetch 예시

@Query("select m from Member m left join fetch m.team")
List<Member> findMemberFetchJoin();
@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);
@EntityGraph("Member.all")
@Query("select m from Member m")
List<Member> findMemberEntityGraph();
//읽기만 할때는 성능 최적화됨 --> 하지만 그렇게 최적화에 도움을 주진 않음
@QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value = "true"))
//for update를 날림 --> 아직 몰라도 되는 개념인듯
@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Member> findByUsername(String name);
// == projections == interface로 하는방법과 class로하는 방법이 있음 --> 객체말고 원하는 값만 뽑아 내고싶을 때
List<UsernameOnly> findProjectionsByUsername(@Param("username") String username);
List<UsernameOnlyDto> findProjectionsByUsername(@Param("username") String username);
//네이티브 쿼리
@Query(value = "SELECT m.member_id as id, m.username, t.name as teamName " + "FROM member m left join team t",countQuery ="SELECT count(*) from member",nativeQuery = true)
Page<MemberProjection> findByNativeProjection(Pageable pageable);



사용자 정의 (커스터 마이징)

  • 입맛에 맞게 커스터 마이징 할 수 있다.

MemberRepositoryCustom

//사용자 정의 인터페이스
public interface MemberRepositoryCustom {
    List<Member> findMemberCustom();
}



MemberRepositoryCustomImpl

@RequiredArgsConstructor
public class MemberRepositoryCustomImpl implements MemberRepositoryCustom{

    private final EntityManager em;

    @Override
    public List<Member> findMemberCustom() {
        return em.createQuery("select m from Member m")
                .getResultList();
    }
}



Controller

  • Controller는 다음과 같이 구성된다.
@RestController
@RequiredArgsConstructor
public class MemberController {

    private final MemberRepository memberRepository;

    @GetMapping("/members/{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();
    }

    @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.getId(), member.getUsername(), null));
        return map;
    }

    //@PostConstruct
    public void init(){
        for(int i = 0; i<100; i++){
            memberRepository.save(new Member("username"+1,1));
        }
    }
}

Reference

  • 김영한님의 Spring Data JPA
  • 김은호님의 Spting Data JPA

'Spring Jpa' 카테고리의 다른 글

조회 성능 최적화  (0) 2022.02.27
값타입  (0) 2022.02.27
OSIV  (0) 2022.02.27
JPA 란  (0) 2022.02.27
영속성 전이 (CASCADE)  (0) 2022.02.27