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("이미 존재하는 회원입니다.");
}
}
'Server > Spring' 카테고리의 다른 글
스프링 데이터 JPA (0) | 2024.08.27 |
---|---|
회원 관리 - 전체 구조 (0) | 2024.08.27 |
H2 데이터 베이스 설치 및 테이블 생성 (0) | 2024.08.26 |
회원 관리 - 프론트) 3. 모든 회원 조회하기 (0) | 2024.08.25 |
회원 관리 - 프론트) 2. 회원 등록하기 (0) | 2024.08.25 |