실무에서 JPA를 사용하는데 개념적인 부분을 집고 넘어가면 좋을 것 같아서 강의를 보게 되었고
인프런 강의를 보고 내용 정리를 해보았다.
프로젝트 어노테이션
@NoArgsConstructor(AccessLevel.PROTECTED)
엔티티에서 레벨을 protected 까지로 한다
Protected 로 설정하는 이유는 무분별한 객체 생성에 대해 한번 더 체크할 수 있게 한다.
그러나 @Builder와 같이 사용하고 싶다면 AllArgsConstructor 와 같이 사용하거나
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
안에 @Builder를 계속적으로 선언해서 사용해야한다.
@Builder 를 사용하면 의미있는 객체만 생성할 수 있게 된다.
@ToString 은 객체가 출력될때 바로 값이 출력되는 것이다.
@ToString 에서 연관관계에 있는 값도 출력하면 데이터가 너무 많이 출력될 수 있으니 주의한다.
가급적이면 연관관계 객체는 사용하지 않는것이 좋다.
인터페이스
@Repository 어노테이션을 달지 않고
인터페이스에 repository 이름을 달아서 작성해도
스프링에서 알아서 프록시 객체를 만들어서 넣어준다. (인터페이스만 잡으면 프록시가 구현 클래스를 만들어서 injection 함 )
쿼리 메소드
메소드 이름으로 쿼리 생성
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByUsernameAndCompany(String username, String company);
}
엔티티의 필드명이 변경되면 인터페이스에 정의한 메서드 이름도 꼭 함께 변경해야 한다.
엔티티 필드명과 인터페이스의 메서드 이름이 동일하지 않으면 애플리케이션을 시작하는 시점에 오류가 발생한다.
스프링 데이터 JPA 공식 문서 참고
NamedQuery
@Entity
@NamedQuery(
name="Member.findByUsername",
query="select m from Member m where m.username = :username")
public class Member {
...
}
@Query(name = "Member.findByUsername")
List<Member> findByUsername(@Param("username") String username);
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByUsername(@Param("username") String username);
}
@Query 를 생략하고 메서드 이름만으로 Named 쿼리를 호출할 수 있다
** 실무에서 Named Query를 직접 등록해서 사용하는 일은 드물다. 대신 @Query 를 사용해서 Repository 메소드에 쿼리를 직접 정의해서 사용한다.
@Query, 리포지토리 메소드에 쿼리 정의하기
import org.springframework.data.jpa.repository.Query;
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 기능을 자주 사용하게 된다.
@Query, 값, DTO 조회하기
** 실무에서 많이 사용
사용자 이름 리스트를 가져오고 싶다 라고 했을 때 아래와 같이 사용하면 됨
@Query("select m.username from Member m")
List<String> findUsernameList();
Dto 로 조회하고자 할 때
Dto 에 조회하고자 하는 대상 작성
import lombok.Data;
@Data
@AllArgsConstructor
public class MemberDto {
private Long id;
private String username;
private String teamName;
// @AllArgsConstructor 에 대한 내용
// public MemberDto(Long id, String username, String teamName) {
// this.id = id;
// this.username = username;
// this.teamName = teamName;
// }
}
@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 를 사용해서 생성자 객체를 사용해야 한다.
파라미터 바인딩
위치기반과 이름 기반으로 파라미터 바인딩이 가능하다.
코드 가독성과 유지보수를 위해 이름 기반 파라미터 바인딩을 사용하는 것이 좋다.
//이름 기반
@Query("select m from Member m where m.username = :name")
//위치 기반
@Query("select m from Member m where m.username = ?0")
컬렉션 파라미터 바인딩
Collection타입으로 in절을 지원해준다.
@Query("select m from Member m where m.username in :names")
List<Member> findByNames(@Param("names") List<String> names);
반환 타입
컬렉션
List 는 절대 null 이 아닐 수 있게 보장 받는다.
그래서 null 체크를 따로 하지 않아도 된다.
@Test
public void returnData(){
List<Member> result = memberRepository.findByUsername("qwerqwer");
if(result != null){ // Null 인지 아닌지 체크하는 이 코드를 쓰는것은 비효율적임
}
}
단건 조회
Optional 사용
데이터를 조회할 때 데이터가 있을수도 있고 없을수도 있다면 Optional 을 사용하는것이 맞다.
Member findByUsername(String name); //단건
Optional<Member> findByUsername(String name); //단건 Optional
결과가 없으면 null 이 반환되고
결과가 2건 이상이면 NonUniqueResultException 예외가 발생한다.
Repository의 쿼리 리턴타입은 스프링 공식 문서에서 더 확인할 수 있다.
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repository-query-return-types