엔티티로 만들어보자.
import lombok.*;
import javax.persistence.*;
@ToString
@NoArgsConstructor(access= AccessLevel.PROTECTED)/*프록시 객체를 위해 protect로 기본 생성자*/
@Getter @Setter
@Entity
public class Member {
@Id @GeneratedValue
@Column(name="member_id")
private Long id;
private String username;
private int age;
@ToString.Exclude//연관관계 필드는 toString을 안하는 것이 좋다.
@JoinColumn(name="team_id")
@ManyToOne(fetch=FetchType.LAZY)
private Team team;
public Member(String username, int age, Team team) {
this.username = username;
this.age = age;
if(team!=null) this.changeTeam(team);
}
//연관관계 메서드
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
public Member(String username) {
this.username = username;
}
}
import lombok.*;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@ToString
@NoArgsConstructor(access= AccessLevel.PROTECTED)/*프록시 객체를 위해 protect로 기본 생성자*/
@Getter @Setter
@Entity
public class Team {
@Id @GeneratedValue
@Column(name="team_id")
private Long id;
private String name;
@ToString.Exclude
@OneToMany(mappedBy="team", cascade=CascadeType.ALL)
private List<Member> members = new ArrayList<>();
public Team(String name) {
this.name = name;
}
}
Member, Team에 대해서 JPA를 사용한 기본적인 CRUD가 가능한 리퍼지토리를 만들어보자.
MemberJPARepository
import org.springframework.stereotype.Repository;
import study.datajpa.entity.Member;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
import java.util.Optional;
@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);
}
public void delete(Member member){
em.remove(member);
}
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 List<Member> findAll(){
return em.createQuery("select m from Member m", Member.class).getResultList();
}
}
TeamJPARepository
import org.springframework.stereotype.Repository;
import study.datajpa.entity.Team;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
import java.util.Optional;
@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) {
return Optional.ofNullable(em.find(Team.class, id));
}
public long count() {
return em.createQuery("select count(t) from Team t", Long.class).getSingleResult();
}
}
그런데 유심히 위의 CRUD메서드들을 보면 MemberRepository, TeamRepository 모두 Member타입이냐 Team타입이냐만 다르지 거의 같은 로직들을 가지고 있는 것을 볼 수 있다. 즉 많은 양의 코드들이 중복되고 있는 것이다.
하여 과거에 이를 제네릭을 도입하여 해결을 시도하는 등 수많은 도전이 있었다고 한다. 하지만 어떤 개발자가 인터페이스로 해결하였는데, 그것이 바로 Data JPA가 되었다.
MemberRepository, TeamRepository를 Data JPA를 사용하여 만들기 위해서는 아래와 같이 하면 된다.
import org.springframework.data.jpa.repository.JpaRepository;
import study.datajpa.entity.Member;
public interface MemberRepository extends JpaRepository<Member, Long> {
}
import org.springframework.data.jpa.repository.JpaRepository;
import study.datajpa.entity.Team;
public interface TeamRepository extends JpaRepository<Team, Long> {
}
각각의 Repository는 누군가가 만들어놓은 JpaRepository<T, ID>를 extends하기만 하면 된다.
그런데 어떻게 이러한 텅텅빈 Repository를 빈객체로 생성하여 의존성을 주입받는지가 의문점이다.
이는 아래 그림과 같이 스프링이 직접 만들어 주기 때문이다. 즉 개발자가 직접 Repository를 구현하고 만들 필요없이, 스프링이 타입을 보고 그에 대한 Repository객체를 만들어서 의존성 주입을 해주는 것이다.
JPARepository는 아래와 같이 다양한 메서드들을 제공한다.
주요 메서드:
• save(S) : 새로운 엔티티는 저장하고 이미 있는 엔티티는 병합한다.
• delete(T) : 엔티티 하나를 삭제한다. 내부에서 EntityManager.remove() 호출
• findById(ID) : 엔티티 하나를 조회한다. 내부에서 EntityManager.find() 호출
• getOne(ID) : 엔티티를 프록시로 조회한다. 내부에서 EntityManager.getReference() 호출
• findAll(...) : 모든 엔티티를 조회한다. (정렬( Sort )이나 페이징( Pageable ) 조건을 파라미터로 제공할 수 있다. )
그런데.. 만약 username으로 Member를 find하는 메서드를 제공하고 싶다면?? 이러한 메서드는 Data JPA 인터페이스가 제공하지 않으므로 이를 직접 구현해야할까?
그럴필요 없다. 아래처럼 추상 메서드를 정의해주면 알아서 만들어준다....
import org.springframework.data.jpa.repository.JpaRepository;
import study.datajpa.entity.Member;
public interface MemberRepository extends JpaRepository<Member, Long> {
Member findByUsername(String username);
}
도대체 이게 어떻게 된 일인지는 다음장에서 알아보자
김영한님의 인프런 강의와 PDF를 바탕으로 정리하였습니다.
'JPA > 스프링 DATA JPA' 카테고리의 다른 글
Data JPA 06 쿼리 메서드 - 반환타입, 페이징 (0) | 2022.01.06 |
---|---|
Data JPA 05 쿼리 메서드 - Query, 리포지토리 메서드에 쿼리 정의하기 (0) | 2022.01.05 |
Data JPA 04 쿼리 메서드 - NamedQuery (0) | 2022.01.05 |
Data JPA 03 쿼리 메소드 - 메서드 이름으로 쿼리 생성 (0) | 2022.01.05 |
Data JPA 01 - 스프링 데이터 jpa 맛보기 (0) | 2022.01.02 |