[실전! 스프링 데이터 JPA] WEEK 14

enxnong·2023년 12월 9일
0

김영환님의 강의 실전! 스프링 데이터 JPA 보면서 공부한 내용입니다.

🏊‍ 섹션 1

프로젝트 생성

h2데이터베이스 설치


✅ h2 데이터 연결에서 오류났을 때 jdbc:h2:~/datajpa을 입력하면 데이터베이스 파일을 로컬에서 만들면서 나오게된다. 이후 JDBC URL을 다시 jdbc:h2:tcp://localhost/~/datajpa로 연결하면 정상 연결 된다.

스프링 데이터 JPA와 DB 설정, 동작확인

// 인터페이스
public interface MemberRepository extends JpaRepository<Member, Long> {
}

// 테스트
@SpringBootTest
@Transactional
@Rollback(value = false)
class MemberRepositoryTest {

    @Autowired MemberRepository memberRepository;

    @Test
    public void testMember(){
        // given
        Member member = new Member("memberA");
        Member savedMember = memberRepository.save(member);

        // when
        Member findMember = memberRepository.findById(member.getId()).get();

        // then
        assertThat(findMember.getId()).isEqualTo(findMember.getId());
        assertThat(findMember.getUsername()).isEqualTo(findMember.getUsername());
        assertThat(findMember).isEqualTo(findMember);
    }
}

🏊‍ 섹션 2

예제 도메인 모델과 동작확인


// Member
@Entity
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of={"id", "username", "age"}) // 객체를 찍을 때 바로 출력이 됨
// 연관관계 필드{team}은 무한루프가 생성될 수 있기 때문에 자제하는 것이 좋음
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 = username;
    }

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

// 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")
    private List<Member> members = new ArrayList<>();

    public Team(String name) {
        this.name = name;
    }
}

// Test
@SpringBootTest
@Transactional
@Rollback(value = false)
class MemberTest {

    @PersistenceContext
    private EntityManager em;

    @Test
    public void testEntity(){

        Team teamA = new Team("teamA");
        Team teamB = new Team("teamB");

        em.persist(teamA);
        em.persist(teamB);

        Member member1 = new Member("member1", 10, teamA);
        Member member2 = new Member("member2", 20, teamB);
        Member member3 = new Member("member3", 30, teamA);
        Member member4 = new Member("member4", 40, teamB);

        em.persist(member1);
        em.persist(member2);
        em.persist(member3);
        em.persist(member4);

        // 초기화
        em.flush(); // 강제로 db에 insert 쿼리를 날림
        em.clear(); // db와 영속성컨텍스트에 있는 캐쉬를 날려버림

        // 확인
        List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();

        for (Member member : members) {
            System.out.println("member = " + member);
            System.out.println("-> member.team = " + member.getTeam());
        }
    }
}

🏊‍ 섹션 3

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

📝 기본 CRUD

  • 저장
  • 변경 → 변경감지 사용
  • 삭제
  • 전체 조회
  • 단건 조회
  • 카운트

💡 jpa에서 수정은 변경감기 기능을 사용하면 된다.
트랜잭션 안에서 엔티티를 조회한 다음에 데이터를 변경하면, 트랜잭션 종료 시점에 변경 감지 기능이 작동해서 변경된 엔티티를 감지하고 update sql을 실행한다.

// MemberRepository
// team도 이하 동일
@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 Member find(Long id) {
        return em.find(Member.class, id);
    }

    // 전체 조회
    public List<Member> findAll() {
        // 전체 및 필터링 조회는 jpa에서 제공하는 jpql이라는 기술을 사용한다
        // 해당 기술에서 Member는 엔티티를 의미한다. 즉, 테이블을 대상으로 하는 것이 아닌 객체를 대상으로 하는 것이다
        // 모양은 sql과 똑같다
        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); // member가 null일수도 아닐수도 있다
    }

    public long count() {
        return em.createQuery("select count(m) from Member m", Long.class).getSingleResult();
    }
}

공통 인터페이스 설정

📝 설정

  • 스프링 부트 사용시 시 @SpringBootApplication 위치를 지정(해당 패키지와 하위 패키지 인식)
  • 만약 위치가 달라지면 @EnableJpaRepositories 필요

공통 인터페이스 적용

public interface TeamRepository extends JpaRepository<Team, Long> {
    // JpaRepository와 타입(team)만 잘 적으면 Spring Data JPA가 보고 구현체를 알아서 꽂아버림
}

public interface MemberRepository extends JpaRepository<Member, Long> {
}@Repository 애노테이션 생략 가능
- 컴포넌트 스캔을 스프링 데이터 JPA가 자동으로 처리
- JPA 예외를 스프링 예외로 변환하는 과정도 자동으로 처리

✅ 구현체가 아닌 공통 인터페이스가 있음

공통 인터페이스 분석

📝 분석
- JpaRepository 인터페이스: 공통 CRUD 제공
- 제네릭은 <엔티티 타입, 식별자 타입> 설정

✅ T findOne(ID) → Optional findById(ID) 변경
✅ boolean exists(ID) → boolean existsById(ID) 변경

📝 제네릭 타입

  • T : 엔티티
  • ID : 엔티티의 식별자 타입
  • S : 엔티티와 그 자식 타입

📝 주요 메서드

  • save(S) : 새로운 엔티티는 저장하고 이미 있는 엔티티는 병합한다.
  • delete(T) : 엔티티 하나를 삭제한다.
    내부에서 EntityManager.remove() 호출
  • findById(ID) : 엔티티 하나를 조회한다.
    내부에서 EntityManager.find() 호출
  • getOne(ID) : 엔티티를 프록시로 조회한다.
    내부에서 EntityManager.getReference() 호출
  • findAll(…) : 모든 엔티티를 조회한다.
    정렬(Sort)이나 페이징(Pageable) 조건을 파라미터로 제공할 수 있다

💡 JpaRepository 는 대부분의 공통 메서드를 제공한다.

public interface MemberRepository extends JpaRepository<Member, Long> {

    // Spring Data Jpa에서 제공하는 기능이 아닌 새로운 기능을 만들고자 할 때
    // MemberRepository에 있는 기능들을 다 구현해야되는 문제 발생
    // => 방법을 해결하는 "커스텀 기능"이 있음
	// List<Member> findByUsername(String username){}
    // 구현하지 않아도 동작함 => "쿼리 메소드 기능"으로 가능
    
}
profile
높은 곳을 향해서

0개의 댓글