Programming-[Backend]/JPA

[spring data JPA] 1. 초기 설정 및 Data Jpa 기본

컴퓨터 탐험가 찰리 2021. 11. 15. 07:52
728x90
반응형

 

1. 초기 세팅

 

이전 강의와 거의 동일하게 프로젝트를 만들고 환경 설정을 해준다.

 

application.yml 파일은 다음과 같다. 환경 설정에서 띄어쓰기, 오타 등의 실수는 치명적이다. 정확히 써야한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
spring:
  datasource:
   url: jdbc:h2:tcp://localhost/~/datajpa
    username: sa
    password:
    driver-class-name: org.h2.Driver
 
  jpa:
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
#        show-sql: true
        format_sql: true
 
logging.level:
  org.hibernate.SQL: debug
#  org.hibernate.type: trace
 
cs

 

 

 

참고사항 : @NoArgsConstructor(access = AccessLevel.PROTECTED), @ToString

 

이전 JPA 강의들에서 배운 내용이지만, 복습 해본다.

 

JPA는 기본 생성자를 활용하여 프록시 등을 처리하기 때문에, 기본 생성자의 접근 제어자는 protected로 만들어줘야 한다. 기본 생성자를 만드는 것을 대체하는 것이 @NoArgsConstructor이며, protected로 설정하기 위해서 access 옵션을 AccessLevel.PROTECTED로 주면 된다.

 

@ToString은 연관관계를 갖는 객체 정보가 들어가지 않도록, of 속성을 주어 다른 객체와 상관없는 멤버 필드값만 들어가도록 설정해준다.

 

1
2
3
4
5
6
7
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {"id""username""age"})
public class Member {
cs

 

 


 

2. 도메인 객체 만들기, 문제점

 

도메인 객체들을 생성하고 기본 CRUD 메서드를 만든다. 기본편때부터 해오던 거라 별 특별할게 없다. 영속성 컨텍스트를 이용하므로 update는 작성하지 않고, 리스트로 결과를 불러오는 findAll이나 count, where문이 들어가야하는 함수들은 JPQL문을 사용해야한다는 것을 복습 차원에서 실습해본다. Member와 Team객체를 만든다.

 

 

MemberJpaRepository.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@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);
  }
 
 
}
cs

 

 

문제점

중요한 것은 이런 식으로 JPA Repository 메서드를 작성하다보면 반복적인 작업을 해야하는 문제가 생긴다는 것이다. Team에 대한 Repository를 짜더라도, 타입만 바뀔 뿐 거의 동일한 메서드를 작성하게 됨을 알 수 있다.

 

Spring Data JPA 에서는 이런 문제를 해결하기 위해서 공통 인터페이스를 만들고, 사용자가 지정하는 이름에 따라 쿼리를 자동으로 생성해주는 놀라운 기능을 제공해준다. 

 

 


 

3. 환경설정 및 인터페이스 만들기

 

기본적으로 main 메서드가 있는 Application 클래스에 @EnableJpaRepositories(basePackages = ... ) 설정을 해줘야 한다. 그러나 스프링 부트를 사용하는 경우 이 설정을 스프링 부트가 자동으로 해준다.

 

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableJpaRepositories(basePackages = "study.datajpa.repository")
public class DataJpaApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(DataJpaApplication.class, args);
    }
 
}
cs

 

JpaRepository를 상속받는 Repository 인터페이스를 만들고 테스트 해본다. 사용자가 인터페이스만 만들면, Spring Data JPA에서 이에 해당하는 프록시 객체를 만들어서 자동으로 구현체(실제 메서드들)와 연결해준다. JpaRepository의 타입은 조회할 엔티티, 해당 엔티티의 식별자의 타입을 순서대로 넣어준다.

 

JpaRepository를 상속받으면 @Repository를 생략해도 자동으로 스캔 대상에 포함시켜준다.

 

<interface> MemberRepository

1
2
3
public interface MemberRepository extends JpaRepository<Member, Long> {
 
}
cs

 

 

 


 

4. 쿼리 메서드 기능

 

 

쿼리 메서드 기능은 사용자가 작성하는 이름을 참조하여 where 절 등 조건이 들어간 쿼리문을 자동으로 생성해주는 기능이다. 예를 들어 아래와 같은 메서드를 작성한다면, JPA가 알아서 username과 일치하는 member 엔티티를 찾아온다.

 

member findByUsername(String username)

 

쿼리 메서드가 제공하는 기능은 다음 3가지이다.

 

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

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

3. @Query 어노테이션을 이용해서 인터페이스에 쿼리 직접 정의

 

 

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

보통 메소드 이름으로 쿼리 생성을 많이 한다. findAllByUsernameAndAgeGreateThan(username, age) 정도로, 2개 정도의 파라미터가 필요한 간단한 쿼리의 경우 Data JPA의 것을 많이 활용한다. 복잡하거나 join이 필요한 경우 등에는 다른 방법을 적용해야 한다.

 

 

2. @NamedQuery

 

NamedQuery는 Entity 위에 JPQL문을 작성하고, 실제 필요한 곳에서 em.createQuery(...)로 호출하여 사용하는 방식이다. 그런데, 사실 이 방식은 JPQL문을 바로 작성하는 것보다 더 번거로운 방식이라 잘 사용을 안한다. 

 

1
2
3
4
5
6
7
8
9
10
11
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {"id""username""age"})
@NamedQuery(
        name="Member.findByUsername",
        query = "select m from Member m where m.username = :username"
)
public class Member {
cs

 

3. @Query : 인터페이스에 직접 쿼리 정의

 

인터페이스에 바로 쿼리문을 작성해서 메서드를 사용할 수 있다. 파라미터는 @Param 어노테이션을 이용한다. 이렇게 하면 @NamedQuery의 장점인 쿼리문을 애플리케이션 로딩 시점에 파싱하여 에러를 잡아내는 장점도 함께 적용된다. 즉 @Query 내부의 쿼리문에서 오타가 발생하면 잘못된 쿼리문이라고 알려준다.

 

1
2
3
4
5
6
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);
  
}
cs

 

 


 

5. @Query 사용해보기

 

@Query를 가장 많이 사용하므로, 여러가지 상황에서 사용법을 좀 더 구체적으로 알아보자.

 

 

1. 단일 필드 조회

단일 필드 조회는 다음과 같이 그냥 엔티티를 프로퍼티 접근하는 식으로 하면 된다.

 

<<interface>> MemberRepository

1
2
@Query("select m.username from Member m")
  List<String> findUsernameList();
cs

 

 

2. DTO로 조회

 

여러 엔티티의 필드값을 조회하고 싶을 때는, DTO 객체를 만들어서 조회한다. 이때, 해당 DTO의 패키지 위치를 정확히 입력해주고, new 키워드를 작성해주어야 한다.

 

<<interface>> MemberRepository

1
2
@Query("select new study.datajpa.dto.MemberDto(m.id, m.username, t.name) from Member m join m.team t")
  List<MemberDto> findMemberDto();
cs

 

MemberDto.class

조인되는 team의 이름값은 필드명을 teamName으로 지정하고, 생성자에서 넣어주면 된다. 그러면 쿼리문에서 작성된 t.name을 teamName값으로 인식한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Getter
@Setter
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;
  }
}
cs

 

 

 


 

참조

 

1. 인프런_실전! 스프링 부트와 JPA 활용1_김영한 님 강의

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-JPA-%EC%8B%A4%EC%A0%84/

728x90
반응형