본문 바로가기
Server/Spring

JPA를 통해서 DB와 연결하기

by kkkkk1023 2024. 8. 26.

JPA란?

JPA (Java Persistence API)는 자바에서 데이터베이스와 상호작용하기 위한 표준 기술이다.
JPA를 사용하면 개발자가 직접 SQL을 작성하지 않고도 데이터 베이스의 데이터를 관리할 수 있다. 

 

쉽게 말하면 JPA는 자바 프로그램이 데이터베이스와 쉽게 이야기할 수 있게 해주는 도구이다. 예를 들어 게임에서 아이템을 창고에 넣거나 꺼내올 때, JPA는 이 작업을 쉽고 빠르게 할 수 있도록 도와준다. 우리가 직접 창고가 있는 곳까지 가서 넣거나 빼지 않아도 JPA라는 도구가 알아서 일을 처리해준다. 


1. build.gradle 파일에 JPA, h2데이터 베이스 관련 라이브러리 추가하기

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 
	runtimeOnly 'com.h2database:h2' 
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
}

 


2. 스프링 부트에 JPA 설정 추가하기

파일 경로: resources/application.properties`

spring.datasource.url=jdbc:h2:tcp://localhost/~/test 
-> 스프링이 H2 데이터베이스에 연결할 주소를 설정하는 코드
spring.datasource.driver-class-name=org.h2.Driver 
-> 데이터베이스와 연결하기 위한 통로를 설정하는 코드, 위 코드는 h2라는 데이터베이스를 사용한다고 알려주고있다.
spring.datasource.username=sa
-> 데이터베이스에 로그인할 때 사용할 이름을 설정하는 코드
spring.jpa.show-sql=true
-> 스프링이 데이터베이스와 대화하는 내용을 화면에 보여준다는 의미의 코드, 데이터 베이스에 어떤 요청을 보내고 있는지 확인할 수 있도록 하는 것이다.
spring.jpa.hibernate.ddl-auto=none
-> 데이터베이스의 구조를 자동으로 바꾸지 않겠다는 의미의 코드, 스프링이 데이터베이스의 테이블이나 내용을 자동으로 변경하지 않도록 설정한 것이다.

 

위 설정들은 스프링이 데이터베이스와 어떻게 연결되는지 설정하는 내용이다. 즉, 스프링이 데이터 베이스와 대활 때 필요한 비밀번호나 어떤 방식으로 대화할지 알려주는 것이다. 


3. JPA 엔티티 매핑하기(자바클래스와 데이터베이스 테이블을 서로 연결해주는 것)

[Member.java]

package section4.section4_spring.domain;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity //이건 JPA가 관리하는 엔티티다
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    //JPA에게 PK(고유 번호)는 내가 지정하지 않고 데이터 베이스가 자동으로 정해줄거야! 라고 말하는 것과 같은 의미이다.
    private Long id;
    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;
    }
}

 

4. JPA 회원 Repository 만들기

Repository 패키지 내에 JpaMemberRepository를 만들어보자.

package section4.section4_spring.repository;

import jakarta.persistence.EntityManager;
import section4.section4_spring.domain.Member;

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

public class JpaMemberRepository implements MemberRepository {

    private EntityManager em;

    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) {
        Member member = em.find(Member.class, id);
        return Optional.ofNullable(member);
    }

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

    @Override
    public List<Member> findAll() {
        return em.createQuery("select m from Member m", Member.class).getResultList();
    }
}
  • EntityManger
    • WHAT: 데이터 배이스와 연결하여 데이트를 저장하거나 조회하는데 도움을 주는 도구
    • WORK: 데이터 베이스에 있는 정보를 읽거나 새로 추가하거나 업데이트하는 작업을 처리한다.
  • em
    • WHAT: em은 EntityManager를 가르키는 변수
    • WHERE: 이 변수는 클래스 생성자에서 초기화된다. 
    • HOW: em을 사용해서 실제 데이터 베이스 작업을 수행한다.

public Member save(Member member) {
    em.persist(member);
    return member;
}
  • em.persist(member)
    • em을 사용해서 데이터 베이스에 멤버 정보를 저장하는 명령어이다.

 


public Optional<Member> findById(Long id) {
    Member member = em.find(Member.class, id);
    return Optional.ofNullable(member);
}
  • em.find(Member.class, id)
    • em을 사용하여 데이터 베이스에서 주어진 ID에 해당하는 멤버를 찾아오는 명령어
    • Member.class란 'Member' 클래스가 담긴 객체이다. 데이터 베이스에서 'Member' 객체를 찾거나 저장할 때 이 클래스 정보를 참고한다.

 


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

 

  • crateQueary
    • 데이터 베이스에 멤버의 이름으로 멤버 정보를 찾기위한 쿼리를 만드는 역할
  • select m from Member m where m.name = :name
    • 데이터 베이스에서 Member 테이블에서 이름이 name인 멤버를 찾아줘! 라는 의미
  • .setParameter("name", name)
    • 쿼리에서 name 이라는 부분에 실제 학생의 이름을 넣어줘야한다.
    • 이 명령어는 쿼리의 :name 부분을 실제로 찾고자 하는 이름으로 교체한다.
  • getResultList()
    • 만들어진 쿼리를 실제로 데이터 베이스에 보내서 실행한다. 
    • 데이터 베이슨느 쿼리 요청에 따라 결과를 리스트 형태로 반환한다.

public List<Member> findAll() {
    return em.createQuery("select m from Member m", Member.class).getResultList();
}

 

  • em.createQuery("select m from Member m", Member.class)
    • 데이터 베이스에 보낼 쿼리를 만드는 단계
    • Member라는 데이블에서 m이라는 변수로 모든 학생을 Member 객체 형태로 반환해달라는 쿼리

5. Service 계층에 트랜잭션 추가하기

package section4.section4_spring.service;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import section4.section4_spring.domain.Member;
import section4.section4_spring.repository.MemberRepository;
import section4.section4_spring.repository.MemoryMemberRepository;

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

@Transactional //트랜잭션 추가 - 비지니스 로직의 복잡한 작업을 안전하게 처리하고 중간에 오류가 발생해도 데이터 베이스를 안정적으로 유지 가능하다.
public class MemberService {
    ...

}

 

 

6. config 파일에서 Repsitory 반환 값 변경하기

package section4.section4_spring;

import jakarta.persistence.EntityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import section4.section4_spring.repository.JpaMemberRepository;
import section4.section4_spring.repository.MemberRepository;
import section4.section4_spring.repository.MemoryMemberRepository;
import section4.section4_spring.service.MemberService;

@Configuration
public class SpringConfig {

    private EntityManager em;

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

    @Bean //빈을 등록할거야!
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }

    @Bean //빈을 등록할거야!
    public MemberRepository memberRepository() {
//        return new MemoryMemberRepository();
        return new JpaMemberRepository(em); 
    }


    //위 처럼 진행하면 스프링 컨테이너에 서비스와 레포지토리가 등록이 되고 서비스에 레포지토리가 주입된다.

}

 

7. 통합 테스트 해보기

package section4.section4_spring.service;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Commit;
import org.springframework.transaction.annotation.Transactional;
import section4.section4_spring.domain.Member;
import section4.section4_spring.repository.MemberRepository;
import section4.section4_spring.repository.MemoryMemberRepository;

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

@SpringBootTest //통합 테스트를 위한 어노테이션 (해당 어노테이션을 붙이면 실제 DB와 연결한다.)
@Transactional
class MemberServiceIntegrationTest {

    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;

    @Test
    @Commit //해당 어노테이션을 사용하지 않으면 테스트 끝과 동시에 DB에 저장된 내용은 롤백되어 없어진다.(실제 DB에 올리고 싶다면)
    public void 회원가입() throws Exception {
        //Given
        Member member = new Member();
        member.setName("park");
        //When
        Long saveId = memberService.join(member);
        //Then
        Member findMember = memberRepository.findById(saveId).get();
        assertEquals(member.getName(), findMember.getName());
    }

    @Test
    public void 중복_회원_예외() throws Exception {
        //Given
        Member member1 = new Member();
        member1.setName("spring");

        Member member2 = new Member();
        member2.setName("spring");
        //When
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class,
                () -> memberService.join(member2));//예외가 발생해야 한다.
        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
    }
}

 

회원 등록 성공