이 글은 김영한 님의 Infrean 강의를 학습한 내용을 정리하여 작성합니다.
스프링 JdbcTemplate
- 순수 Jdbc와 동일한 환경설정을 하면 된다.
- 스프링 JdbcTemplate과 MyBatis 같은 라이브러리는 JDBC API에서 본 반복 코드를 대부분 제거해준다. 하지만 SQL은 직접 작성해야 한다.
스프링 JdbcTemplate 회원 리포지토리
MemberRepository interface를 확장해 JdbcTemplateMemberRepository 클래스를 구현한다.
JdbcTemplate을 사용하기 위해서는 위 코드처럼 클래스 내부에 JdbcTemplate을 선언해준다.
이때, JdbcTemplate은 이전 다른 코드들처럼 스프링으로부터 주입(Injection) 받을 수 없다.
따라서, DataSource를 생성자의 매개변수로 받아 위 이미지처럼 JdbcTemplate을 생성해준다.
(이 방법이 스프링에서 권장하는 방법이다.)
참고
생성자가 하나만 존재하는 경우 @Autowired 어노테이션을 생략 가능하다.
<JdbcTemplateMemberRepository>
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class JdbcTemplateMemberRepository implements MemberRepository {
private final JdbcTemplate jdbcTemplate;
public JdbcTemplateMemberRepository(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public Member save(Member member) {
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id"); Map<String, Object> parameters = new HashMap<>();
parameters.put("name", member.getName());
Number key = jdbcInsert.executeAndReturnKey(new
MapSqlParameterSource(parameters));
member.setId(key.longValue());
return member;
}
@Override
public Optional<Member> findById(Long id) {
List<Member> result = jdbcTemplate.query("select * from member where id = ?", memberRowMapper(), id);
return result.stream().findAny();
}
@Override
public List<Member> findAll() {
return jdbcTemplate.query("select * from member", memberRowMapper());
}
@Override
public Optional<Member> findByName(String name) {
List<Member> result = jdbcTemplate.query("select * from member where name = ?", memberRowMapper(), name);
return result.stream().findAny();
}
private RowMapper<Member> memberRowMapper() {
return (rs, rowNum) -> {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return member;
};
}
}
현재 위 코드를 이해하지 못해도 괜찮다. 나중 강의를 통해 모두 공부할 내용이다.
현재는 어떤 흐름으로 프로그램이 동작하는지만 알아도 충분하다.
만약, 위 코드에 대해 자세히 알고 싶다면 JdbcTemplate 문서를 참고하자.
JdbcTemplate을 사용하도록 스프링 설정 변경
테스트 실행
정상적으로 동작하는 것을 확인할 수 있다.
JPA
순수 Jdbc 대신 JdbcTemplate을 사용해 반복되는 코드가 현저히 줄어들었다.
하지만 JdbcTemplate에도 부족한 부분이 존재했다.
SQL은 개발자가 직접 작성해야 한다는 것이다.
- JPA는 기존의 반복 코드는 물론이고, 기본적인 SQL도 JPA가 직접 만들어서 실행해준다.
- MemoryMemberRepository 예제에서 객체를 메모리에 넣었듯이 객체를 JPA에 넣으면 JPA가 DB와 연동해 알아서 처리해준다.
- JPA를 사용하면, SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환을 할 수 있다.
- JPA를 사용하면 개발 생산성을 크게 높일 수 있다.
build.gradle 파일에 JPA, h2 데이터베이스 관련 라이브러리 추가
- spring-boot-starter-data-jpa는 내부에 jdbc 관련 라이브러리를 포함한다. 따라서 jdbc는 제거해도 된다.
라이브러리를 모두 받았다면 External Libraries에 다음과 같이 hibernate 라이브러리가 존재해야 한다.
JPA는 인터페이스다.
JPA를 사용하기 위해서는 JPA를 구현한 Hiberate, EclipseLink, ... 프레임워크 중 하나를 사용해야 하는데 우리는 그 중 Hibernate를 주로 사용한다.
스프링 부트에 JPA 설정 추가
- show-sql : JPA가 생성하는 SQL을 출력한다.
- ddl-auto : JPA는 테이블을 자동으로 생성하는 기능을 제공하는데 none을 사용하면 해당 기능을 끈다.
- create를 사용하면 엔티티 정보를 바탕으로 테이블도 직접 생성해준다. -> 해보자!!
JPA 엔티티 매핑
JPA는 자바 ORM(Object-Relational Mappin, 객체 관계 매핑) 기술에 대한 API 표준 명세를 의미한다.
즉, JPA는 ORM을 사용하기 위한 인터페이스를 모아둔 것으로 객체는 객체대로, 관계형 DB는 관계형 DB대로 설계한다.
이때 ORM은 객체와 DB의 테이블이 매핑을 이루는 것을 말한다.
매핑 방법은 간단하다.
다음과 같이 적절한 어노테이션들을 사용해주면 된다.
- @Entity
- 실제 DB의 테이블과 매칭될 Class임을 명시한다.
- 즉, 테이블과 링크될 클래스임을 나타낸다.
<Member>
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 @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;
}
}
- @Id
- 해당 테이블의 PK(Primary Key, 기본 키) 필드를 나타낸다.
기본 키(Primary Key) 매핑 방법 중 자동 생성이 존재한다.
이전 h2 데이터베이스에서 insert하면 자동으로 id가 생성되는 것을 기억할 것이다.
이러한 방식을 IDENTITY 전략이라 한다.
- @GeneratedValue
- PK의 생성 규칙을 나타낸다.
참고
만약 DB의 컬럼명이 "username"이라면 다음 어노테이션을 통해 name 필드가 username과 매핑된다.
- @Column
테이블의 컬럼을 나타내며, 굳이 선언하지 않더라도 해당 클래스의 필드는 모두 컬럼이 된다.
JPA 회원 리포지토리
JPA는 EntityManager를 통해 모든 동작을 수행한다.
이전에 가져온 라이브러리를 통해 스프링 부트가 자동으로 EntityManager를 미리 생성해 둔다.
그래서 우리는 생성자 주입 방식을 통해 EntityManager를 그저 주입만 받으면 된다.
<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;
}
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();
}
}
서비스 계층에 트랜잭션 추가
- org.springframework.transaction.annotation.Transactional를 사용하자.
- 스프링은 해당 클래스의 메서드를 실행할 때 트랜잭션을 시작하고, 메서드가 정상 종료되면 트랜잭션을 커밋한다. 만약 런타임 예외가 발생하면 롤백한다.
- JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 실행해야 한다.
JPA를 사용하도록 스프링 설정 변경
<SpringConfig>
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;
@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 JpaMemberRepository(em);
}
}
테스트
스프링 데이터 JPA
스프링 부트와 JPA만 사용해도 개발 생산성이 매우 증가하고, 개발해야할 코드도 확연히 줄어든다.
여기에 스프링 데이터 JPA를 사용하면, 기존의 한계를 넘어 마치 마법처럼, 리포지토리에 구현 클래스 없이 인터페이스 만으로 개발을 완료할 수 있다.
그리고 반복 개발해온 기본 CRUD 기능도 스프링 데이터 JPA가 모두 제공한다.
스프링 부트와 JPA라는 기반 위에, 스프링 데이터 JPA라는 환상적인 프레임워크를 더하면 개발이 정말 즐거워질 것이다.
지금까지 조금이라도 단순하고 반복이라 생각했던 개발 코드들이 확연히 줄어든다.
따라서 개발자는 핵심 비지니스 로직을 개발하는데, 집중할 수 있다.
실무에서 관계형 데이터베이스를 사용한다면 스프링 데이터 JPA는 더이상 선택이 아닌 필수다!
주의: 스프링 데이터 JPA는 JPA를 편리하게 사용하도록 도와주는 기술이다. 따라서 JPA를 먼저 학습한 후 스프링 데이터 JPA를 학습하는 것이 좋다.
- 이전 JPA 설정을 그대로 사용한다.
스프링 데이터 JPA 회원 리포지토리
- 스프링 데이터 JPA가 JpaRepository를 상속한 SpringDataJpaMemberRepository를 스프링 빈으로 자동 등록해준다.
스프링 데이터 JPA 회원 리포지토리를 사용하도록 스프링 설정 변경
SpringDataJpaMemberRepository가 현재 스프링 빈으로 등록된 상태이므로 생성자 주입 방식을 통해 MemberRepository를 주입 받는다.
테스트
스프링 데이터 JPA 제공 클래스
스프링 데이터 JPA 제공 기능
- 인터페이스를 통한 기본적인 CRUD
- findByName(), findByEmail() 처럼 메서드 이름 만으로 조회 기능 제공
- 페이징 기능 자동 제공
참고: 실무에서는 JPA와 스프링 데이터 JPA를 기본으로 사용하고, 복잡한 동적 쿼리는 Querydsl이라는 라이브러리를 사용하면 된다. Querydsl을 사용하면 쿼리도 자바 코드로 안전하게 작성할 수 있고 ,동적 쿼리도 편리하게 작성할 수 있다. 이 조합으로 해결하기 어려운 쿼리는 JPA가 제공하는 네이티브 쿼리를 사용하거나, 앞서 학습한 스프링 JdbcTemplate를 사용하면 된다.
'스프링 > 스프링 입문' 카테고리의 다른 글
AOP (0) | 2022.07.24 |
---|---|
스프링 DB 접근 기술 #1 (0) | 2022.03.02 |
회원 관리 예제 - 웹 MVC 개발 (0) | 2022.03.01 |
스프링 빈과 의존관계 (0) | 2022.03.01 |
회원 관리 예제 - 백엔드 개발 (0) | 2022.02.28 |