출처 : 실전! 스프링 데이터 JPA
@Setter
를 넣기 보다는package study.datajpa.entity;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
@Getter
@Setter
public class Member {
@Id
@GeneratedValue
private Long id;
private String username;
protected Member() {
}
public Member(String username) {
this.username = username;
}
}
@Setter
를 없애고, 필요에 따라 package study.datajpa.entity;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
@Getter
public class Member {
@Id
@GeneratedValue
private Long id;
private String username;
protected Member() {
}
public Member(String username) {
this.username = username;
}
public void changeUsername(String username) {
this.username = username;
}
}
→ 이런식으로 changeUsername(String username)
메소드를 주는 게 더 나은 방법이라고 봄
※ 참고
protected Member() {
}
가 있는 이유
→ JPA 표준 스펙에 엔티티는 기본적으로 파라미터 없는 디폴트 생성자가 있어야 한다.
→ protected까지 열어놔야 함.
(∵ JPA가 프록시 이런 기술들을 쓰는데, JPA 구현체들이 프록시를 강제로 만들어야 할 때...)
org.springframework.dao.InvalidDataAccessApiUsageException: No EntityManager with actual transaction available for current thread - cannot reliably process 'persist' call; nested exception is javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'persist' call
→ JPA의 모든 데이터 변경은 @Transactional
안에서 이루어져야 한다.
@Transactional
가 있으면, 끝날 때 다 Rollback을 시킴@Rollback(value = false)
추가insert
into
member
(username, id)
values
(?, ?)
→ 결과가 나온다.
package study.datajpa.repository;
import org.springframework.stereotype.Repository;
import study.datajpa.entity.Member;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@Repository
public class MemberJpaRepository {
@PersistenceContext
private EntityManager em;
public Member save(Member member) {
em.persist(member);
return member;
}
public Member find(Long id) {
return em.find(Member.class, id);
}
}
package study.datajpa.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import study.datajpa.entity.Member;
public interface MemberRepository extends JpaRepository<Member, Long> {
}
→ 테스트 코드 실행 시, 같은 결과가 나온다.
@Entity
@Getter
@Setter
public class Member {
@Id
@GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
private int age;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
protected Member() {
}
public Member(String username) {
this.username = username;
}
}
→ protected Member() {}
대신, @NoArgsConstructor(access = AccessLevel.PROTECTED)
넣기
package study.datajpa.entity;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
@Entity
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {
@Id
@GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
private int age;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
public Member(String username) {
this.username = username;
}
}
※ 초기화
em.flush();
JPA em.persist를 하면 바로 DB에 insert 쿼리를 날리는 게 아님.
JPA 영속성 컨텍스트에 member와 team을 다 모아 놓음.
그리고 flush를 하면 강제로 DB에 insert 쿼리를 다 날리게 됨.
em.clear();
DB에 쿼리를 다 날리고 JPA 영속성 컨텍스트에 있는 캐시를 다 날린다.
그래서 깔끔하게 다 확인됨.
※ 참고
JPA에서 수정은 변경감지 기능을 사용하면 된다.
트랜잭션 안에서 엔티티를 조회한 다음에 데이터를 변경하면,
트랜잭션 종료 시점에 변경감지 기능이 작동해서
변경된 엔티티를 감지하고 UPDATE SQL을 실행한다.
save(S)
: 새로운 엔티티는 저장하고 이미 있는 엔티티는 병합한다.delete(T)
: 엔티티 하나를 삭제한다. 내부에서 EntityManager.remove()
호출findById(ID)
: 엔티티 하나를 조회한다. 내부에서 EntityManager.find()
호출getOne(ID)
: 엔티티를 프록시로 조회한다. 내부에서 EntityManager.getReference()
호출findAll(…)
: 모든 엔티티를 조회한다. 정렬( Sort
)이나 페이징( Pageable
) 조건을 파라미터로 제공할 수 있다.→ 내가 상상할 수 있는 공통 기능들은 다 제공한다!
→ 도메인에 특화된 기능들은 어떻게 해야할까? 공통으로 만드는 게 불가한... : 커스텀 기능
(ex. List<Member> findByUsername(String username);
)
→→ 구현하지 않아도 동작한다!! : 쿼리 메소드 기능
스프링 데이터 JPA는 메소드 이름을 분석해서 JPQL을 생성하고 실행
이름과 나이를 기준으로 회원을 조회하려면?
// 이름과 나이를 기준으로 회원을 조회하려면?
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();
}
List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
→ 동일하게 동작한다!
where
member0_.username=?
and member0_.age>?
JPA의 NamedQuery를 호출할 수 있음
※ 이 기능은 거의 실무에서 쓸 일이 없음
스프링 데이터 JPA를 사용하면 실무에서 Named Query를 직접 등록해서 사용하는 일은 드물다.
대신 @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);
JPA Named 쿼리처럼 애플리케이션 실행 시점에 문법 오류를 발견할 수 있음(매우 큰 장점!)
동적쿼리는 Querydsl 써라
단순히 값 하나를 조회 (여태까지는 엔티티 타입만 조회한 것임)
// 사용자의 이름 리스트만 다 가져오고 싶을 때
@Query("select m.username from Member m")
List<String> findUsernameList();
Collection
타입으로 in절 지원
@Query("select m from Member m where m.username in :names")
List<Member> findByNames(@Param("names") List<String> names);
→ 결과
where
member0_.username in (
? , ?
)
스프링 데이터 JPA는 유연한 반환 타입 지원