본문 바로가기

개인 공부/인프런

[JavaSpring] JPA & SpringDataJpa

728x90

해당 글은 김영환 자바스프링 입문을 요약, 정리를 하며 개인적 견해가 들어간 글입니다.

JPA

앞서 Jdbc 템플릿을 사용해서 코드를 확 줄인걸 확인할 수 있었다.

하지만 아직 sql은 우리가 타이핑을 해야한다는 번거로움이 남아있다.

이때 JPA가 번거로운걸 해결해준다.

⇒ 개발 생상성을 높여준다.

JPA 세팅

우선 build.gradle파일에 JPA, h2 DB 관련 라이브러리를 추가해준다.

코드는 아래와 같다.

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'
}
}

그리고 리프레시를 눌러준다.

Untitled

이후 스프링 부트에 JPA 설정을 추가해준다.

코드는 아래와 같다.

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

JPA는 객체를 보고 테이블을 자동으로 생성해준다.

여기서 auto기능을 none으로 설정했는데 우리는 이미 테이블을 생성했기 때문에 자동으로 JPA에서 생성할 필요가 없기때문이다.

또한, 도메인에 있는 멤버 클래스도 아래와 같이 바꿔준다.

package study.studyspring.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Member {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    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;
    }
}

SQL은 IDENTITY전략이 있다.

  • IDENTITIY전략이란?
    • 보통의 경우 DB에서 테이블에 데이터를 삽입시 ID를 직접 입력해야한다.
    • 하지만 IDENTITY를 사용시 DB에서 ID를 자동으로 생성해서 삽입해준다.
  • DB에 자동으로 생성되는 ID를 의미한다.

이 IDENTITY를 사용하기 위해 @GeneratedValue어노테이션을 사용한다.

그리고, 리포지토리에 JpaMemberRepository를 만들어준다.

코드는 아래와 같다.

package study.studyspring.repository;

import study.studyspring.domain.Member;

import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;

    public class JpaMemberRepository implements MemberRepository{

        private final EntityManager em;
        public JpaMemberRepository(EntityManager em) {
            this.em = em;
        }
        public Member save(Member member) {
            em.persist(member);
            return member;
        }
        public Optional<Member> findById(Long id) {
            Member member = em.find(Member.class, id);
            return Optional.ofNullable(member);
        }
        public List<Member> findAll() {
            return em.createQuery("select m from Member m", Member.class)
                    .getResultList();
        }
        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();
    }
}

여기서 EnityManageer는 엔티티 객체와 DB간의 상호작용을 담당한다.

즉, EnityManager를 통해 엔티티의 생성, 수정, 삭제, 검색등을 처리하며, 이를 통해 DB에서도 생성, 수정, 삭제, 검색을 할 수 있다.

JPA는 데이터를 변경할때는 @Transactional내에서 이뤄져야만 한다.

때문에, MemberService에서 @Transactional을 넣어준다.

잘 돌아가는지 확인하기위해 통합테스트를 통해 확인하면 정상적으로 돌아가는게 확인된다.

여기서 Hibernate가 아래에 보이는데 이게 DB 테이블과, 자바 객체간 매핑을 자동으로 처리해서 DB쿼리 작성을 간소화 시켜주는 역할을 한다.

즉, 쉽게 말하면 SQL에 쿼리 구문이 실행된다는 뜻이다.

Untitled

확인을 하려면 테스트 코드 위에 @Commit을 입력하면 H2 콘솔에서 SQL이 작동하는것을 확인이 가능하다.

스프링 데이터 JPA

  • 스프링 부트와 JPA만 사용해도 개발 생산성이 정말 많이 증가하고, 개발해야할 코드도 확연히 줄어든다는것을 확인했다.
  • 추가적으로 스프링 데이터 JPA를 사용하면, 리포지토리에 구현 클래스 없이 인터페이스 만으로 개발을 완료할 수 있다.
  • 그리고 반복 개발해온 기본 CRUD(Create, Read, Update, Delete) 기능도 스프링 데이터 JPA가 모두 제공한다.
  • 이를통해 개발자는 핵심 비즈니스 로직을 개발하는데, 집중이 가능하다.

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

설정은 앞에사용한 JPA설정을 그대로 사용하면 된다.

우선 SpringDataJpaMemberRepository이름을 가진 인터페이스를 Repository에 만들어준다.

코드는 아래와 같다.

package study.studyspring.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import study.studyspring.domain.Member;

import java.util.Optional;

public interface SpringDataJpaMemberRepository extends JpaRepository<Member,
        Long>, MemberRepository {
    Optional<Member> findByName(String name);

}
  • JPA는 JpaRepository로 받아야한다.
  • 인터페이스는 implements가 아닌 extends로 받아야 한다.

이러면 구현을 할 필요가 없이 완성이 된다.

우선 인터페이스를 Bean에 연결해주기 위해서 SpringConfig로 이동한다.

코드를 아래와 같이 바꿔준다.

package study.studyspring.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import study.studyspring.repository.*;

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

@Configuration
public class SpringConfig {
    private final MemberRepository memberRepository;

//    @Autowired 생성자가 1개면 생략 가능
    public SpringConfig(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository);
    }
//    public MemberRepository memberRepository() {
// return new MemoryMemberRepository();
// return new JdbcMemberRepository(dataSource);
// return new JdbcTemplateMemberRepository(dataSource);
//        return new JpaMemberRepository(em);
//    }
}
  • 스프링 데이터 JPA가 SpringDataJpaMemberRepository 인터페이스를 스프링 빈으로 자동 등록해준다.

이후 통합테스트 코드를 실행시키면 정상 작동하는게 확인된다.

스프링 데이터 JPA 제공 기능

  • 인터페이스를 통한 기본적인 CRUD
  • findByName() , findByEmail() 처럼 메서드 이름 만으로 조회 기능 제공
  • 페이징 기능 자동 제공

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

인터페이스 파헤치기

SpringDataJpaMemberRepository 에 extends로 JpaRepository상속받게 된다.

JpaRepository를 들어가보면 findAll이나 findAllById같은 것들이 있는것이 보인다.

거기에 extends로 PagingAndSortingRepository를 상속받는것을 알 수 있는데 타고 또 들어가면 또 extends로 상속받는게 있다.

이렇게 타고타고 들어가다보면 CRUD를 비롯한 우리가 구현하지 않은 것들이 이미 인터페이스에 존재한다는것을 알 수 있다.

하지만, findByName는 공통 인터페이스기 때문에 구현을 해야한다.

여기서 findByName은 SQL에 “select m from Member m wher m.name = ?” 형식으로 보내게 된다.

Name뿐만 아니라 id도 같이 받고 싶으면 findByNameAndId와 같이 짜면 id도 같이 보내게 된다.

이처럼 인터페이스를 이용하면 매우 편하게 구현할 수 있다.

728x90

'개인 공부 > 인프런' 카테고리의 다른 글

[Spring] 스프링이 만들어진 이유  (0) 2023.04.06
[JavaSpring] AOP  (0) 2023.04.05
[JavaSpring] Spring Integration Test & Spring JdbcTemplate  (0) 2023.04.03
[JavaSpring] Jdbc  (0) 2023.03.31
[JavaSpring] Spring DB  (0) 2023.03.30