ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JPA 스프링 데이터 JPA (Spring Data JPA) - 1
    DBMS/JPA 2024. 8. 13. 22:50
    728x90
    반응형

    스프링 데이터 JPA (Spring Data JPA)

     

    지금까지는 jpa를 직접 이용해서 코드를 구성해왔다.

    오늘은 스프링 데이터 JPA를 통해서 조금 더 쉽게 객체지향적 프로그래밍을 알아가보겠다.

     

     

     

    1. 예제 도메인 모델

    1 - 1. Member 엔티티

    @Entity
    @Getter @Setter
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    @ToString(of = {"id", "username", "age"})
    public class Member {
        @Id
        @GeneratedValue
        @Column(name = "member_id")
        private Long id;
        private String username;
        private int age;
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "team_id")
        private Team team;
        public Member(String username) {
            this(username, 0);
        }
        public Member(String username, int age) {
            this(username, age, null);
        }
        public Member(String username, int age, Team team) {
            this.username = username;
            this.age = age;
            if (team != null) {
                changeTeam(team);
            }
        }
        public void changeTeam(Team team) {
            this.team = team;
            team.getMembers().add(this);
        }
    }

    @Setter

    실무에서 가급적 Setter는 사용하지 않기

     

    @NoArgsConstructor (AccessLevel.PROTECTED)

    기본 생성자 막고 싶은데, JPA 스팩상 PROTECTED로 열어두어야 함

     

    @ToString

    가급적 내부 필드만(연관관계 없는 필드만)

     

    changeTeam() 으로 양방향 연관관계 한번에 처리(연관관계 편의 메소드)

     

     

    1 - 2. Team 엔티티

    @Entity
    @Getter @Setter
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    @ToString(of = {"id", "name"})
    public class Team {
        @Id @GeneratedValue
        @Column(name = "team_id")
        private Long id;
        private String name;
        @OneToMany(mappedBy = "team")
        List<Member> members = new ArrayList<>();
        public Team(String name) {
            this.name = name;
        }
    }

    Member와 Team은 양방향 연관관계, Member.team 이 연관관계의 주인,

    Team.members 는 연관관계의 주인이 아님,

    따라서 Member.team 이 데이터베이스 외래키 값을 변경, 반대편은 읽기만 가능

     

     

     

    2. 순수 JPA 기반 리포지토리 만들기

    기본 CRUD

    저장

    변경 : 변경감지 사용

    삭제

    전체 조회

    단건 조회

    카운트

     

    2 - 1. MemberRepository

    @Repository
    public class MemberJpaRepository {
        @PersistenceContext
        private EntityManager em;
        public Member save(Member member) {
            em.persist(member);
            return member;
        }
        public void delete(Member member) {
            em.remove(member);
        }
        public List<Member> findAll() {
            return em.createQuery("select m from Member m", Member.class)
                    .getResultList();
        }
        public Optional<Member> findById(Long id) {
            Member member = em.find(Member.class, id);
            return Optional.ofNullable(member);
        }
        public long count() {
            return em.createQuery("select count(m) from Member m", Long.class)
                    .getSingleResult();
        }
        public Member find(Long id) {
            return em.find(Member.class, id);
        }
    }

     

    2 - 2. TeamRepository

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

     

    2 - 3. 공통 인터페이스 설정

    JavaConfig 설정- 스프링 부트 사용시 생략 가능

    @Configuration
    @EnableJpaRepositories(basePackages = "jpabook.jpashop.repository")
    public class AppConfig {}

    스프링 부트 사용시 @SpringBootApplication 위치를 지정(해당 패키지와 하위 패키지 인식)

    만약 위치가 달라지면 @EnableJpaRepositories 필요

     

     

    2 - 4. 공통 인터페이스 적용

    순수 JPA로 구현한 MemberJpaRepository 대신에 스프링 데이터 JPA가 제공하는 공통 인터페이스 사용

     

    스프링 데이터 JPA 기반 MemberRepository

    public interface MemberRepository extends JpaRepository<Member, Long> {
    }

     

    TeamRepository 생성

    public interface TeamRepository extends JpaRepository<Team, Long> {
    }

    Generic

    T: 엔티티 타입

    ID: 식별자 타입(PK)

     

     

     

    3. 쿼리 메서드

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

     

    순수 JPA 리포지토리

    public List<Member> findByUsernameAndAgeGreaterThan(String username, int age) {
        return em.createQuery("select m from Member m where m.username = :username 
                and m.age > :age")
                .setParameter("username", username)
                .setParameter("age", age)
                .getResultList();
    }

     

    스프링 데이터 JPA

    public interface MemberRepository extends JpaRepository<Member, Long> {
        List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
    }

    스프링 데이터 JPA는 메소드 이름을 분석해서 JPQL을 생성하고 실행

     

    쿼리 메소드 필터 조건

    스프링 데이터 JPA 공식 문서 참고

    (https://docs.spring.io/spring-data/jpa/docs/current/reference/ html/#jpa.query-methods.query-creation)

     

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

    조회: find…By ,read…By ,query…By get…By, 예:) findHelloBy 처럼 ...에 식별하기 위한 내용(설명)이 들어가도 된다.

     

    COUNT: count…By 반환타입 long

    EXISTS: exists…By 반환타입 boolean

    삭제: delete…By, remove…By 반환타입 long

    DISTINCT: findDistinct, findMemberDistinctBy

    LIMIT: findFirst3, findFirst, findTop, findTop3

     

    참고

    이 기능은 엔티티의 필드명이 변경되면 인터페이스에 정의한 메서드 이름도 꼭 함께 변경해야 한다.

    그렇지 않으면 애플리케이션을 시작하는 시점에 오류가 발생한다.

    이렇게 애플리케이션 로딩 시점에 오류를 인지할 수 있는 것이 스프링 데이터 JPA의 매우 큰 장점이다.

     

     

     

    4. NamedQuery

    @NamedQuery 어노테이션으로 Named 쿼리 정의

    @Entity
    @NamedQuery(
            name="Member.findByUsername",
            query="select m from Member m where m.username = :username")
    public class Member {
     ...
    }

     

    JPA를 직접 사용해서 Named 쿼리 호출

    public class MemberRepository {
        public List<Member> findByUsername(String username) {
             ...
            List<Member> resultList =
                    em.createNamedQuery("Member.findByUsername", Member.class)
                            .setParameter("username", username)
                            .getResultList();
        }
    }

     

    스프링 데이터 JPA로 NamedQuery 사용

    @Query(name = "Member.findByUsername")
    List<Member> findByUsername(@Param("username") String username);

    @Query 를 생략하고 메서드 이름만으로 Named 쿼리를 호출할 수 있다.

     

    스프링 데이터 JPA로 Named 쿼리 호출

    public interface MemberRepository
            extends JpaRepository<Member, Long> { //** 여기 선언한 Member 도메인 클래스
        List<Member> findByUsername(@Param("username") String username);
    }

    스프링 데이터 JPA는 선언한 "도메인 클래스 + .(점) + 메서드 이름"으로 Named 쿼리를 찾아서 실행

    만약 실행할 Named 쿼리가 없으면 메서드 이름으로 쿼리 생성 전략을 사용한다.

    필요하면 Named 전략을 변경할 수 있지만 권장하지 않는다.

     

    참고

    스프링 데이터 JPA를 사용하면 실무에서 Named Query를 직접 등록해서 사용하는 일은 드물다.

    대신 @Query 를 사용해서 리파지토리 메소드에 쿼리를 직접 정의한다.

     

     

     

    5. @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);
    }

    @org.springframework.data.jpa.repository.Query 어노테이션을 사용

    실행할 메서드에 정적 쿼리를 직접 작성하므로 이름 없는 Named 쿼리라 할 수 있음

    JPA Named 쿼리처럼 애플리케이션 실행 시점에 문법 오류를 발견할 수 있음(매우 큰 장점!)

     

    참고

    실무에서는 메소드 이름으로 쿼리 생성 기능은 파라미터가 증가하면 메서드 이름이 매우 지저분해진다.

    따라서 @Query 기능을 자주 사용하게 된다.

     

     

     

    6. @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로 직접 조회 하려면 JPA의 new 명령어를 사용해야 한다.

    그리고 다음과 같이 생성자가 맞는 DTO가 필요 하다. (JPA와 사용방식이 동일하다.)

     

    @Data
    public class MemberDto {
        private Long id;
        private String username;
        private String teamName;
        public MemberDto(Long id, String username, String teamName) {
            this.id = id;
            this.username = username;
            this.teamName = teamName;
        }
    }

     

     

     

    7. 파라미터 바인딩

    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") String username);
    }

    코드 가독성과 유지보수를 위해 이름 기반 파라미터 바인딩을 사용하자 (위치기반은 순서 실수가 바꾸면…)

     

    컬렉션 파라미터 바인딩

    Collection 타입으로 in절 지원

    @Query("select m from Member m where m.username in :names")
    List<Member> findByNames(@Param("names") List<String> names);

     

     

    반환 타입

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

    List<Member> findByUsername(String name); //컬렉션
    Member findByUsername(String name); //단건
    Optional<Member> findByUsername(String name); //단건 Optional

     

    조회 결과가 많거나 없으면?

    컬렉션

    결과 없음: 빈 컬렉션 반환

     

    단건 조회

        결과 없음: null 반환

        결과가 2건 이상: javax.persistence.NonUniqueResultException 예외 발생

     

    참고

    단건으로 지정한 메서드를 호출하면 스프링 데이터 JPA는 내부에서 JPQL의 Query.getSingleResult() 메서드를 호출한다.

    이 메서드를 호출했을 때 조회 결과가 없으면 javax.persistence.NoResultException 예외가 발생하는데

    개발자 입장에서 다루기가 상당히 불편하다.

    스프링 데이터 JPA는 단건을 조회할 때 이 예외가 발생하면 예외를 무시하고 대신에 null 을 반환한다.

     

     

    일단 값을 다루는 기본적인 기능들은 전부 다룬 것 같다.

    페이징부터 더 자세한 부분은 다음 장에서 다루겠다.

    728x90
    반응형

    'DBMS > JPA' 카테고리의 다른 글

    JPA 스프링 데이터 JPA (Spring Data JPA) - 3  (0) 2024.08.14
    JPA 스프링 데이터 JPA (Spring Data JPA) - 2  (0) 2024.08.13
    JPA 객체지향 쿼리 언어(JPQL)  (0) 2024.08.10
    JPA 값타입  (0) 2024.08.09
    JPA 프록시와 연관관계 관리  (0) 2024.08.09
Designed by Tistory.