[Spring] JPA 와 스프링 데이터 JPA

김민범·2024년 10월 28일

Spring

목록 보기
8/29

5. JPA


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

build.gradle 파일에 JPA 관련 라이브러리 추가 ( h2 관련 라이브러리는 제일 처음 추가했음 )

dependencies {
		implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
		implementation 'org.springframework.boot:spring-boot-starter-web'
		//  implementation 'org.springframework.boot:spring-boot-starter-jdbc'
		**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'
		}
}

spring-boot-starter-data-jpa 는 내부에 jdbc 관련 라이브러리를 포함한다. 따라서 jdbc는 제거해도 된다.

스프링 부트에 JPA 설정 추가

resources/application.properties

spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none

주의! : 스프링부트 2.4부터는 spring.datasource.username=sa 를 꼭 추가해주어야 한다. 그렇지 않으면 오류가 발생한다.

  • show-sql : JPA가 생성하는 SQL을 출력한다.
  • ddl-auto : JPA는 테이블을 자동으로 생성하는 기능을 제공하는데 none 를 사용하면 해당 기능을 끈다.
    • create 를 사용하면 엔티티 정보를 바탕으로 테이블도 직접 생성해준다. → 해보자.
  • JPA는 인터페이스만 제공이 되는 거고 구현체로 Hibernate, Eclipse Link 등 구현 기술들이 존재.
  • 우리는 JPA 인터페이스의 Hibernate를 사용할것.

💡 JPA는 객체+ORM 이라는 기술의 집약체
ORM 이란?
1. 객체(object)
2. 관계형 데이터베이스(relational)
3. Mapping
어떻게 Mapping 하냐 ? → Annotation을 이용!

JPA 엔티티 매핑

참고 - https://ultrakain.gitbooks.io/jpa/content/chapter2/chapter2.4.html

package hello.hellospring.domain;

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

@Entity
public class Member {
		// @Id -> PK, 또한 현재 H2 db에서는 값(이름)을 넣으면 db가 자동으로 ID(PK)를 생성해준다.
    // 이를 Identity Strategy 라고 한다.
    // 이를 아래와 같이 매핑해주자.
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
		private Long id;
		// 만약 db에 있는 name 이라는 column이 name이 아니라 username이다? 그럼 매핑을 시켜준다
    // @Column(name = "username")
		private String name;

		public Long getId() {
			return id;
    }

		public void setId(Long id) {
				this.id = id;
    }
		
		// 만약 db table에서의 column이 username이다? 그럼 매핑을 시켜준다. 
		// 이렇게 하면 Member 엔티티의 "name"이라는 필드를 테이블의 "username" 컬럼과 매핑!
    @Column(name = "username")
		public String getName() {
				return name;
    }

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

JPA 회원 리포지토리

package hello.hellospring.repository;

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

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

public class JpaMemberRepository implements MemberRepository{

    // JPA는 EntityManager라는 걸로 모든게 동작을 한다.
    // build.gradle에서 JPA 라이브러리를 받으면, 스프링이 자동으로 EntityManager를 생성하여 db랑 다 연결하여 만들어준다.
    // 그래서 우리는 이 만들어진 것을 인젝션 받으면 된다.
    private final 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) // JPA에서 사용되는 메서드로, 쿼리의 파라미터 값을 설정하는 역할. 이 메서드를 사용하는 이유는 동적으로 쿼리에 파라미터 값을 전달하기 위해서입니다.
                .getResultList(); // 파라미터 값을 설정한 후에 쿼리를 실행하고, 그 결과를 리스트 형태로 반환
        return result.stream().findAny(); // findAny(), findFirst()
    }

    @Override
    public List<Member> findAll() {
        // JPQL이라는 객체지향 쿼리를 사용!!!! -> 테이블 대상이 아닌, 객체를 대상으로 하여 쿼리를 날리는 것!
        // JPQL 문을 보면, entitu를 대상으로 쿼리를 날리고있다. 또한 select 하는데 Member 엔티티 자체를 셀렉트 하고 있다. 즉, 객체를 조회한다.
        return em.createQuery("select m from Member m", Member.class)
                .getResultList();
    }
}

findAny() vs findFirst()
일반적인 경우 findFirst()findAny()는 동일하게 콜렉션의 앞부터 뒤로 검색.

그러나 Stream()을 병렬 처리하는 경우엔 findAny()는 동작이 달리질 수 있다. 앞에서 뒤로 검색하는 스레드와 뒤에서 앞으로 검색하는 스레드 등 여러 스레드가 동시에 컬렉션을 순회하며 가장 빨리 찾은 스레드의 값을 반환한다.

아래와 같이 데이터가 존재할 떄 findFirst는 언제나 Id가 1인 객체를 반환하나, 병렬 환경에서 findAny는 1번 혹은 3번 아이디를 가진 객체를 반환할 수 있다.

{id: 1, name: 홍길동}

{id: 2, name: 임꺽정}

{id: 3, name: 홍길동}

서비스 계층에 트랜잭션 추가

import org.springframework.transaction.annotation.Transactional

@Transactional
public class MemberService {}

JPA를 사용하도록 스프링 설정 변경

package hello.hellospring;

import hello.hellospring.repository.*;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.persistence.EntityManager;
import javax.sql.DataSource;

@Configuration
public class SpringConfig {

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

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

    @Bean
		public MemberRepository memberRepository() {
//      return new MemoryMemberRepository();
//      return new JdbcMemberRepository(dataSource);
//      return new JdbcTemplateMemberRepository(dataSource);
				return new JpaMemberRepository(em);
    }
}

참고 : JPA도 스프링 만큼 성숙한 기술이고, 학습해야 할 분량도 방대하다. 다음 강의와 책을 참고하자.
인프런 강의 링크 : 인프런 - 자바 ORM 표준 JPA 프로그래밍 - 기본편
JPA 책 링크: 자바 ORM 표준 JPA 프로그래밍 - YES24

6. 스프링 데이터 JPA


스프링 부트와 JPA만 사용해도 개발 생산성이 정말 많이 증가하고, 개발해야할 코드도 확연히 줄어듭니다. 여기에 스프링 데이터 JPA를 사용하면, 기존의 한계를 넘어 마치 마법처럼, 리포지토리에 구현 클래스 없이 인터페이스 만으로 개발을 완료할 수 있습니다. 그리고 반복 개발해온 기본 CRUD 기능도 스프링 데이터 JPA가 모두 제공합니다.

스프링 부트와 JPA라는 기반 위에, 스프링 데이터 JPA라는 환상적인 프레임워크를 더하면 개발이 정말 즐거워집니다. 지금까지 조금이라도 단순하고 반복이라 생각했던 개발 코드들이 확연하게 줄어듭니다. 따라서 개발자는 핵심 비즈니스 로직을 개발하는데, 집중할 수 있습니다.

실무에서 관계형 데이터베이스를 사용한다면 스프링 데이터 JPA는 이제 선택이 아니라 필수 입니다.

주의 : 스프링 데이터 JPA는 JPA를 편리하게 사용하도록 도와주는 기술입니다. 따라서 JPA를 먼저 학습한 후에 스프링 데이터 JPA를 학습해야 합니다.

  • 앞의 JPA 설정을 그대로 사용한다.

스프링 데이터 JPA 회원 리포지토리

package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {
		// 인터페이스가 인터페이스를 받을 때는 Extends를 사용

		// 메서드 이름 만으로 조회 기능 제공
		Optional<Member> findByName(String name);
}

스프링 데이터 JPA 회원 리포지토리를 사용하도록 스프링 설정 변경

package hello.hellospring;

import hello.hellospring.repository.*;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringConfig {

		private final MemberRepository memberRepository;

		public SpringConfig(MemberRepository memberRepository) {
				this.memberRepository = memberRepository;
    }

    @Bean
		public MemberService memberService() {
				return new MemberService(memberRepository);
    }
}
  • 스프링 데이터 JPA가 SpringDataJpaMemberRepository 를 스프링 빈으로 자동 등록해준다.
    • 즉, 스프링 데이터 JPA가 JpaRepository 를 받고있는 SpringDataJpaMemberRepository구현체를 자동으로 만들고 스프링 빈에 자동으로 등록해준다.
  • 스프링 컨테이너에서 MemberRepository를 찾는데, 지금 내가 등록한 게 뭐가… 있나?
    • 스프링 데이터 JPA가 SpringDataJpaMemberRepository 를 스프링 빈에 등록해놨다!!

스프링 데이터 JPA 제공 클래스

스프링 데이터 JPA 제공 기능

  • 인터페이스를 통한 기본적인 CRUD
  • findByName() , findByEmail() 처럼 메서드 이름 만으로 조회 기능 제공
  • 페이징 기능 자동 제공
  • 즉, 우리가 상상할 수 있는 공통할 수 있는 것은 다 공통화해서 제공해준다.
  • 단, findByName 같이 유저 이름, 이메일 이름, 주문서 번호 등으로 조회하고 싶은 것은 비지니스마다 다 다르기 때문에 공통화가 불가능!!
    • 그러면 어떻게 해야하냐? → 스프링 데이터 JPA한테 findBy + "조회단위" 를 해주면 된다. ex) findByName , findById , findByNameAndID 등등…
    • 그러면 내장된 규칙에 따라 쿼리가 짜진다. 만약 findByName 이라면 select m from member m where m.name 처럼 JPQL을 짜준다.
    • 즉, 메서드 이름 만으로 조회 기능 제공한다는 것~~!

참고: 실무에서는 JPA와 스프링 데이터 JPA를 기본으로 사용하고, 복잡한 동적 쿼리는 Querydsl이라는 라이브러리를 사용하면 된다. Querydsl을 사용하면 쿼리도 자바 코드로 안전하게 작성할 수 있고, 동적 쿼리도 편리하게 작성할 수 있다. 이 조합으로 해결하기 어려운 쿼리는 JPA가 제공하는 네이티브 쿼리를 사용하거나, 앞서 학습한 스프링 JdbcTemplate를 사용하면 된다.

자세한 내용은 다음 강의를 참고하자 : 인프런 - 실전! 스프링 데이터 JPA

0개의 댓글