[Spring-입문] JPA 설정 및 테스트하기

DANI·2023년 11월 19일

Spring[김영한T]

목록 보기
10/31
post-thumbnail

❓ JPA를 사용하는 이유

  • JPA는 기존의 반복 코드는 물론이고, 기본적인 SQL도 JPA가 직접 만들어서 실행
  • JPA를 사용하면, SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환할 수 있다
  • JPA를 사용하면 개발 생산성을 높일 수 있다.


💾 bulid.gradle 파일에 JPA 관련 라이브러리 추가

✅ 제거 : implementation 'org.springframework.boot:spring-boot-starter-jdbc'
✅ 추가 :implementation 'org.springframework.boot:spring-boot-starter-data-jpa'


💾 application.properties에 설정 추가

spring.jpa.show-sql=true : JPA가 생성하는 SQL을 출력한다
spring.jpa.hibernate.ddl-auto=none : JPA는 테이블을 자동으로 생성하는 기능을 제공하는데 none을 사용하면 해당 기능을 끈다.

  • create를 사용하면 엔티티 정보를 바탕으로 테이블도 직접 생성해준다.


💻 Member 엔티티 수정

package com.hello.hellospring.domain;

import jakarta.persistence.*;


// jpa에서 관리하는 엔터티임
@Entity
public class Member {

    // Id는 pK
    // db가 id를 자동으로 생성하는 것 -> 아이덴티티 전략이라고 함
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    // db랑 연결되는 네임
    // @Column(name="uesrname")
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

  • @Entity : JPA에서 관리하는 엔터티이다.
  • @Id : PK
  • @GeneratedValue(strategy = GenerationType.IDENTITY) : db가 id를 자동으로 생성하는 것
    👉 아이덴티티 전략이라고 함
  • @Column : 칼럼의 속성


🚫 주의사항

@Id 어노테이션을 추가할 때 import 되는 부분이
import org.springframework.data.annotation.Id; 이 아닌
import jakarta.persistence.Id; 이어야 함!





JPA 설정과 엔티티와의 매핑을 마쳤다. 이제 JPA 회원 리포지터리를 생성해보자!

💻 JpaMemberRepository 파일 생성

package com.hello.hellospring.repository;

import com.hello.hellospring.domain.Member;
import jakarta.persistence.EntityManager;

import java.util.List;
import java.util.Optional;

public class JpaMemberRepository implements MemberRepository {


    // bulid 하면 자동으로 엔터티매니저라는 객체를 만들어줌
    private final EntityManager em;

    // JPA를 쓰려면 엔터티매니저를 주입해줘야 한다.
    public JpaMemberRepository(EntityManager em) {
        this.em = em;
    }

    @Override
    public Member save(Member member) {
        // 영구적으로 저장하다
        em.persist(member);
        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
        // find(조회할 클래스, 식별자)
        Member member = em.find(Member.class, id);
        return Optional.ofNullable(member);
    }

    @Override
    public Optional<Member> findByName(String name) {
        return em.createQuery("select m from Member m where m.name = :name", Member.class)
                .setParameter("name", name).getResultList().stream().findAny();
    }

    @Override
    public List<Member> findAll() {
        // ctrl + alt + n : inline
        // sql문에서 객체 자체를 셀렉트함
        return em.createQuery("select m from Member m", Member.class).getResultList();
    }
}


💻 MemberService에 @Transactional 추가

  • 스프링은 해당 클래스의 메서드를 실행할 때 트랙잭션을 시작하고, 메서드가 정상 종료되면 트랜잭션을 커밋한다. 만약 런타임예외가 있으면 롤백한다.
  • JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 실행해야한다.
package com.hello.hellospring.service;

import com.hello.hellospring.domain.Member;
import com.hello.hellospring.repository.MemberRepository;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

;

public class MemberService {
    private final MemberRepository memberRepository;


    public MemberService(MemberRepository memberRepository){

        this.memberRepository = memberRepository;
    }


   
    @Transactional
    // JPA는 트랜잭션이 있어야함
     /*
    회원 가입
     */
    public Long join(Member member){

        validateDuplicateMember(member); // 중복회원 검증
        memberRepository.save(member);
        return member.getId();
    }

    private void validateDuplicateMember(Member member) {
        memberRepository.findByName(member.getName()).ifPresent(member1 -> {

                throw new IllegalStateException("이미 존재하는 회원입니다.");

        });
    }

    /*
    전체 회원 조회
     */
    public List<Member> findMembers(){

        return memberRepository.findAll();
    }

    /*
    아이디 찾기
     */
    public Optional<Member> findOne(Long memberId){

        return memberRepository.findById(memberId);
    }
}



JPA를 사용하도록 config 파일을 수정해보자!

💾 SpringConfig 파일 수정

package com.hello.hellospring.service;

import com.hello.hellospring.repository.JpaMemberRepository;
import com.hello.hellospring.repository.MemberRepository;
import jakarta.persistence.EntityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class SpringConfig {

    private EntityManager em;

    public SpringConfig(EntityManager em) {
        this.em = em;
    }

    @Bean
    public MemberService memberService(){
        return new MemberService(memberRepository()); 
    }

   @Bean
   public MemberRepository memberRepository(){
        return new JpaMemberRepository(em);
   }
}



💾 MemberServiceIntergrationTest 실행


package com.hello.hellospring.service;

import com.hello.hellospring.domain.Member;
import com.hello.hellospring.repository.MemberRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;



import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

@SpringBootTest
// 자동으로 롤백됨
@Transactional
class MemberServiceIntergrationTest {

    @Autowired MemberService memberService;
    @Autowired
    MemberRepository memberRepository;


    @Test
    // @Commit 붙이면 db에 저장됨
    void join() {
        // given 주어진 것
        Member member = new Member();
        member.setName("홍길동");

        // when
        Long id = memberService.join(member);

        // then
        Member result = memberService.findOne(id).get();
        assertThat(member.getName()).isEqualTo(result.getName());
    }

    @Test
    void 중복_회원_예외() throws Exception{
        // given
        Member member1 = new Member();
        member1.setName("홍길동");

        Member member2 = new Member();
        member2.setName("홍길동");

        // when
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));

//        memberService.join(member1);
//        try{
//            memberService.join(member2);
//            fail();
//        } catch(IllegalStateException e){
//            assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
//        }
    }
}

🔵 실행 결과

0개의 댓글