1. JPA
- JPA를 사용하면 기존의 반복 코드를 줄일 수 있으며 기본적인 SQL도 JPA가 직접 만들어서 DB에 접근할 수 있다는 장점이 있다.
- JPA를 사용하면, SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환을 할 수 있다.
- JPA를 사용하면 개발 생산성을 크게 높일 수 있다.
build.gradle
// implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
build.gradle에 추가한다.
해당 라이브러리에서 jdbc와 관련된 라이브러리는 주석처리를 해준다.
라이브러리를 추가하고 gradle refresh를 해준다.
resources/application.properties
파일에 다음 코드를 추가하여
- 스프링 부트가 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
'show-sql' 설정 : JPA가 SQL을 생성할 때 출력
'ddl-auto' 설정 : JPA가 테이블을 자동으로 생성(객체를 보고 테이블을 생성)하는 기능을 제공할 때 이를 'none'으로 사용하지 않음을 표시한다.
(이번 실습에서는 이미 생성된 Member 테이블을 사용한다.)
'none' 대신 'create'로 해당 기능을 사용할 수도 있다.
이때는 JPA가 엔터티 정보를 바탕으로 테이블을 직접 생성한다.
JPA
- 인터페이스, hibernate와 같은 구현체를 사용한다.
- ORM기술으로 이는 Object Relational Mapping을 의미하며, 객체와 관계형 데이터를 mapping하는 기술이다.
domain/Member
package hello.hellospring.domain;
import javax.persistence.*;
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // 시스템이 저장하는 id
// @Column(name="username")
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;
}
}
객체(Member)와 DB를 연동하기 위해 @Entity 애노테이션을 추가하였다.
(1) pk를 설정하기 위해 id에 @id 애노테이션으로 pk 설정
(2) @GeneratedValue(strategy = GenerationType.IDENTITY)
를 사용하면 기존에 우리가 설정하였던 것처럼 id 값을 자동으로 생성하여 pk값으로 지정해준다.
(3) @Column
애노테이션을 사용하면 name 멤버 변수를 DB의 'username'과 mapping 할 수 도 있다.
이처럼 각 애노테이션을 사용하여 DB와 mapping을 시도하고 이를 바탕으로 자동으로 SQL문을 작성한다.
이제 JPA를 사용하여 회원 레포지토리를 작성해본다.
repository/JpaMemberRepository
파일 생성
package hello.hellospring.repository;
import hello.hellospring.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;
}
@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();
}
}
JPA는 작동시 EntityManager로 동작한다.
이는 스프링 부트가 JPA 라이브러리를 받으면 생성하는 것으로, 이전 강의에서 dataSource를 주입받은 것처럼 생성된 manager를 주입받으면 된다.
(1) save() 함수
- em.persist()로 간단하게 구현할 수 있다.
- 인자로 회원 객체를 넘겨주기만 하면 JPA가 insert query를 자동으로 작성해서 db에 넣고 id를 생성, 이를 받아 pk로 세팅한다.
- id를 부여받아 DB에 저장된 회원 객체를 반환하기만 하면 된다.
(2) findById() 함수
- em.find() 함수로 구현할 수 있다.
- 인자로 조회할 type, 조회 시 식별할 pk 값이 필요하며, 반환된 회원 객체를 Optional로 감싸 반환한다.
- 사용시 자동으로 select query를 작성, 실행한다.
(3) findByName() 함수
- findById()와 같이 pk기반으로 검색하는 함수가 아니기 때문에 다른 방법을 사용한다.
(4) em.createQuery() 메소드
- 메소드를 사용해 안에 JPQL을 작성하여 검색한다.
- 기존에 작성했던 SQL처럼 테이블 대상으로 회원의 id, name을 검색하는 것이 아니라, 회원 객체(엔터티)를 대상으로 쿼리를 보내 해당하는 객체 자체를 검색한다.
- 기존에는 id, name을 select 하여 새롭게 회원 객체를 생성해 이를 세팅하고 세팅된 객체를 반환하였다고 한다면, 이제는 검색하여 반환된 회원 객체 자체를 반환할 수 있다.
(5) findAll() 함수
- em.createQuery()를 사용하여 검색된 모든 결과를 리스트 형태로 받아 바로 반환한다.
위와 같이 JPA를 사용하여 리포지토리를 구현하니 새로 회원 객체를 생성하고 검색 결과를 이에 세팅하여 반환하는 것과 같은 중복된 과정이 삭제되고, SQL을 직접 작성하는 과정 또한 축소되었다.
추가적으로 JPA의 모든 데이터 변경은 트랜잭션 안에서 수행되어야 한다.
Service/MemberService
에 추가한다.
import org.springframework.transaction.annotation.Transactional;
@Transactional
public class MemberService {}
회원 서비스 안에 join()함수는 데이터를 추가하는 기능이므로 데이터 변경시 트랜잭션이 필요하다.
이를, @Transational 애노테이션을 추가하여 해결할 수 있다.
이때, 스프링은 해당 클래스 메소드를 실행할 때
- 트랜잭션을 시작하고, 메소드가 정상 종료되면 트랜잭션을 커밋
- 런타임 예외가 발생한다면 롤백을 수행
이제 SpringConfig 파일에서 JpaMemberRepository를 사용하도록 설정한다.
SpringConfig
package hello.hellospring;
import hello.hellospring.repository.*;
import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.persistence.EntityManager;
@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 JdbcMemberRepository(dataSource);
// return new JdbcTemplateMemberRepository(dataSource);
return new JpaMemberRepository(em);
}
}
기존에 JdbcMemberRepository를 사용하던 코드를 주석처리하고 JpaMemberRepository를 사용하도록 해당 리포지토리를 생성, 반환한다.
이때, 스프링에 생성되는 EntityManager를 주입한다.
이제 JPA를 사용한 리포지토리가 정상적으로 동작하는지 통합테스트를 통해 확인한다.
'6. 스프링 DB 접근 기술'에서 작성한 통합테스트로 DB와 연동된 리포지토리의 통합 테스트가 가능하다.
h2 DB를 연결하고 MemberServiceIntegrationTest 파일을 실행하여 테스트 결과를 확인한다.
다음과 같이 테스트가 정상적으로 동작하는 것을 확인할 수 있다.
또한 앞에서 진행한 설정으로 로그를 통해 JPA가 생성한 SQL 또한 확인할 수 있다. (Hibernate)
직접 작성하지 않은 SQL문이 생성, 실행된 것을 확인할 수 있다.
@Commit
애노테이션을 추가 후 테스트를 수행하면 h2 DB에 데이터가 저장되는 것 또한 확인할 수 있다.
2. 스프링 데이터 JPA
스프링 DB 접근 기술의 마지막 단계 : 스프링 데이터 JPA
스프링 부트와 JPA만을 사용해도 개발해야할 코드가 줄어들고, 개발 생산성 또한 많이 증가한다.
스프링 데이터 JPA를 사용하면 레포지토리를 구현 클래스 없이 인터페이스만을 작성하여 개발을 완료할 수 있다.
이제까지 반복해서 개발해온 CRUD 또한 스프링 데이터 JPA가 기본으로 제공한다.
따라서, 개발자는 단순하고 반복적인 코드를 작성하지 않아도 되며 핵심 비즈니스 로직을 구현하는데에만 집중할 수 있다.
주의해야할 사항 : 스프링 데이터 JPA는 JPA를 편리하게 사용하도록 도움을 주는 기술이므로, JPA에 대해 먼저 학습한 후 이를 학습해야 한다.
스프링 데이터 JPA를 사용하기 위해서는 앞에서 설정했던 JPA 설정을 그대로 사용한다.
repository/SpringDataJpaMemberRepository
인터페이스 파일 생성
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 {
// JPQL select m from Member m where m.name = ?
@Override
Optional<Member> findByName(String name);
}
SpringDataJpaMemberRepository
인터페이스는 스프링 데이터 JPA에서 제공하는 JpaRepository를 받는다.
이때 저장되는 Member 클래스와 pk의 type인 Long을 명시해주고, JpaRepository와 같이 MemberRepository 또한 받는다.
findByName() 메서드 : 인터페이스를 생성 해준다. (인터페이스를 생성하면 구현체를 만들 필요가 없다.)
이제, 스프링 데이터 JPA 레포티조티를 사용하기 위해 스프링 설정을 변경한다.
SpringConfig
파일 변경
package hello.hellospring;
import hello.hellospring.repository.*;
import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
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);
}
}
레포지토리의 구현체를 만들지 않았으므로 생성할 필요가 없다.
스프링 부트가 실행할 때 스프링 컨테이너에서 MemberRepository를 가져와 회원 서비스에 이를 주입하는 것이 전부이다.
테스트를 실행할 시
테스트가 정상적으로 잘 동작하는 것을 확인할 수 있다. 로그로 출력되는 SQL문 또한 JPA 레포지토리를 사용할 때의 테스트 결과와 동일하다.
이렇게 단순히 인터페이스만을 생성하여 동작이 가능한 것은 인터페이스를 생성할 때 JpaRepository를 extends 했기 때문이다.
해당 인터페이스를 내려받으면 스프링 데이터 JPA에서 자동으로 인터페이스의 구현체를 만들고, 스프링 빈으로 자동으로 등록한다.
현재, 자동으로 동록된 회원 레포지토리 스프링 빈을 SpringConfig에서 회원 서비스에 주입, 이를 사용하였다.
스프링 데이터 JPA가 제공하는 클래스를 확인해보면, 이와 같다.
우리가 인터페이스를 생성할 때 내려받은 JpaRepository는 PagingAndSortingRepository 인터페이스를 내려받고, 이는 CrudRepository, Repository를 내려받는다.
해당 인터페이스들에는 save(), findOne(), findAll(), findById()와 같이 우리가 이전에 직접 구현했던 기능들 뿐 아니라, 이 외에도 기본적인 CRUD를 수행하거나 단순 조회를 수행하는 기능과 count(), delete()와 같은 기능 또한 구현되어있다.
또한, findByName()이나 findByEmail()과 같이 공통으로 뽑을 수 없는 메서드들은 따로 인터페이스 안에 위와 같이 Optional<Member> findByName(String name);
처럼 명시하기만 하면 스프링 데이터 JPA에서 자동으로 'select m from Member m where m.name = ?'와 같은 JPQL 쿼리를 생성하여 이를 사용할 수 있다.
스프링 데이터 JPA는 위와 같이 인터페이스를 통한 기본적인 CRUD 기능 뿐 아니라, findByName()과 같이 메서드 이름만으로 조회를 가능하게 하고, 페이징 기능 또한 제공하며 구현 클래스를 작성하지 않고 인터페이스만을 작성하여 개발이 끝나도록 한다.
실무에서는 JPA와 스프링 데이터 JPA를 기본으로 사용하고 복잡한 동적 쿼리는 Querydsl과 같은 라이브러리를 사용한다. 이것만으로 해결하기 어려운 쿼리는 JPA가 제공하는 네이티브 쿼리 또는 앞서 학습한 JdbcTemplate을 활용할 수 있다.
(그렇기 때문에 스프링 데이터 JPA 뿐만 아니라 앞서 학습한 기술이나 SQL문 작성과 같은 전반적인 DB기술을 다룰 수 있어야 한다.)
'공부 및 활동 > 스프링 강의 정리' 카테고리의 다른 글
[스프링 입문] 7. AOP (0) | 2021.10.23 |
---|---|
[스프링 입문] 6. 스프링 DB 접근 기술 1 (0) | 2021.09.27 |
[스프링 입문] 5. 회원 관리 예제 - 웹 MVC 개발 (0) | 2021.09.26 |
[스프링 입문] 4. 스프링 빈과 의존관계 (0) | 2021.09.26 |
(회원 관리 예제) 회원 서비스 테스트 (0) | 2021.09.26 |
댓글