본문 바로가기
Server/Spring

JPA를 통해서 DB와 연결하기

by print_soo 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("이미 존재하는 회원입니다.");
    }
}

 

회원 등록 성공