
JPA는 기존의 반복 코드는 물론이고, 기본적인 SQL도 JPA가 직접 만들어서 실행해준다.
JPA를 사용하면, SQL과 데이터 중싱의 설계에서 객체 중심의 설계로 패러다임을 전환할 수 있다.
JPA를 사용하면 개발 생산성을 크게 높일 수 있다.
spring-boot-starter-data-jpa는 내부의 jdbc 관련 라이브러리를 포함하므로 jdbc는 제거해도 된다.
build.gradle
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'
}
show-sql: JPA가 생성하는 SQL을 출력한다.
ddl-auto: JPA는 테이블을 자동으로 생성하는 기능을 제공하는데 none을 사용하면 해당 기능을 끈다. (create을 사용하면 엔티티 정보를 바탕으로 테이블도 직접 생성해준다.)
resources/application.properties
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
@Entity 애노테이션으로 객체(Member)와 DB를 연동합니다.
@ID 애노테이션으로 pk설정을 하고, @GeneratedValue(strategy = GenerationType.IDENTITY)로 id값을 자동으로 생성하여 pk값으로 지정해 줍니다.
주석처리한 @Column 애노테이션을 사용하면 name 멤버 변수를 DB의 'username'과 mapping할 수 있습니다
package hello.hellospring.domain;
import javax.persistence.*;
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long 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;
}
}
src\main\java\hello\hellospring\repository\JpaMemberRepository.java
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
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();
}
}
src\main\java\hello\hellospring\service\MemberService.java 에 트랜잭션 추가
@Transactional
org.springframework.transaction.annotation.Transactional을 사용
스프링은 해당 클래스의 매서드를 실행할 때 트랜잭션을 시작하고, 메서드가 정상 종료되면 트랜잭션을 커밋한다. 만약 런타임 예외가 발생하면 롤백한다.
JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 실행해야 한다.
src\main\java\hello\hellospring\SpringConfig.java
@Bean
public MemberRepository memberRepository() {
// return new MemoryMemberRepository();
// return new JdbcMemberRepository(dataSource);
// return new JdbcTemplateMemberRepository(dataSource);
return new JpaMemberRepository(em);
}
스프링 데이터 JPA를 사용하면 리포지토리에 구현 클래스 없이 인터페이스 만으로 개발을 완료할 수 있습니다. 그리고 반복 개발해온 기본 CRUD 기능도 스프링 데이터 JPA가 모두 제공합니다.
SpringDataJpaMemberRepository 인터페이스는 스프링 데이터 JPA에서 제공하는 JpaRepository를 받습니다. 이때 저장되는 Member 클래스와 pk의 type인 Long을 명시해주고, JpaRepository와 같이 MemberRepository 또한 받습니다.
src\main\java\hello\hellospring\repository\SpringDataJpaMemberRepository.java 를 인터페이스로 생성
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 {
@Override
Optional<Member> findByName(String name);
}
스프링 데이터 JPA가
SpringDataJpaMemberRepository를 스프링 빈으로 자동 등록해준다.
src\main\java\hello\hellospring\SpringConfig.java
package hello.hellospring;
import hello.hellospring.domain.Member;
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 DataSource dataSource;
//private final EntityManager em;
private final MemberRepository memberRepository;
public SpringConfig(MemberRepository memberRepository){
this.memberRepository = memberRepository;
}
@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);
// }
}

우리가 인터페이스를 생성할 때 내려받은 JpaRepository는 PagingAndSortingRepository인터페이스를 내려받고, 이는 CrudRepository, Repository를 내려받습니다. 해당 인터페이스들에는 save(), findOne(), findAll(), findById()과 같이 우리가 이전에 직접 구현했던 기능들 뿐 아니라, 이 외에도 기본적인 CRUD를 수행하거나 단순 조회를 수행하는 기능과 count(), delete()와 같은 기능 또한 구현되어있습니다.
또 findByName() 이나 findByEmail()과 같이 공통으로 뽑을 수 없는 메서드들은 따로 인터페이스 안에 위와 같이 'Optional findByName(String name);' 처럼 명시하기만 하면 스프링 데이터 JPA에서 자동으로 'select m from Member m where m.name =?'과 같은 JPQL 쿼리를 생성하여 이를 사용할 수 있습니다.
(예를 들어 findByEmail()이라면 'Optional findByEmail(String email);' 과 같은 코드를 추가하기만 하면 됩니다)
스프링 데이터 JPA 제공 기능
인터페이스를 통한 기본적인 CRUD
findByName(),findByEmail()처럼 메서드 이름 만으로 조회 기능 제공
페이징 기능 자동 제공
⭐참고⭐: 실무에서는 JPA와 스프링 데이터 JPA를 기본으로 사용하고, 복잡한 동적 쿼리는 Querydsl이라는 라이브러리를 사용하면 된다. Querydsl을 사용하면 쿼리도 자바 코드로 안전하게 작성할 수 있고, 동적 쿼리도 편리하게 작성할 수 있다. 이 조합으로 해결하기 어려운 쿼리는 JPA가 제공하는 네이티브 쿼리를 사용하거나, 앞서 학습한 스프링 JdbcTemplate를 사용하면 된다.