[Spring] 스프링 입문 –

이 글은 김영한님이 작성하셨습니다.
Spring 소개 – 코드를 통해 Spring Boot, Web MVC 및 DB 액세스 기술 학습 강의를 듣고 작성한 글입니다.

오타나 부정확한 내용이 있으면 언제든지 알려주세요!
감사합니다.

Spring DB 접근 기술

H2 데이터베이스 설치

주의!

h2 데이터베이스의 경우 아래 링크에서 버전 1.4.200을 다운로드하여 설치해야 합니다.

https://www.h2database.com/html/download-archive.html

  • h2 데이터베이스 버전은 Spring Boot 버전에 해당합니다.
  • 인증 주기: chmod 755 h2.sh
  • 실행: ./h2.sh
  • 데이터베이스 파일을 만드는 방법
    • jdbc:h2:~/test (처음에만)
    • ~/test.mv.db 파일 생성 확인
    • 이후 jdbc:h2:tcp://localhost/~/test소켓을 통해 연결하십시오. (파일에 직접 접근하면 파일 충돌이 발생할 수 있기 때문에)


[Spring] 스프링 입문 - 1
집에 test.mv.db가 존재하는 것을 볼 수 있습니다.

테이블 생성

테이블 관리를 위해 프로젝트 마스터에게. sql/ddl.sql 파일 생성

drop table if exists member CASCADE;
create table member
(
    id   bigint generated by default as identity,
    name varchar(255),
    primary key (id)
);
  • H2 데이터베이스에 액세스 member 테이블 만들기
  • ID: 자바에서 long 입력하지만 DB에 bigint 남자야.
  • generated by default as identity: 값을 설정하지 않고 삽입하면 DB가 자동으로 ID값을 채워준다.


[Spring] 스프링 입문 - 2


[Spring] 스프링 입문 - 3
자동으로 ID 값이 채워진 DB를 볼 수 있습니다.

순수한 JDBC

아이디어

build.gradle 파일에 jdbc, h2 데이터베이스 관련 라이브러리 추가

implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'
  • DB 작업을 위한 Java jdbc 드라이버는 필수입니다.

Spring Boot 데이터베이스 연결 구성 추가

resources/application.properties

spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa

SpringBoot 2.4부터 spring.datasource.username=sa추가해야 합니다.

Jdbc 저장소 구현

JDBC API에서 직접 코딩하는 것은 15년 전이었습니다.
그래서 옛날 개발자들이 그렇게 진화했다고 생각하고 그냥 참고해서 넘어가자.

상호 작용 MemberRepository구현을 생성했기 때문에 JdbcMemberRepository해야 할 것

package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.springframework.jdbc.datasource.DataSourceUtils;

import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class JdbcMemberRepository implements MemberRepository {
    private final DataSource dataSource;

    public JdbcMemberRepository(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public Member save(Member member) {
        String sql = "insert into member(name) values(?)";

        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try { 
            conn = getConnection();
            pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

            pstmt.setString(1, member.getName()); // values(?)에, ?와 매칭이된다.
pstmt.executeUpdate(); // DB에 실제 쿼리가 들어간다.
rs = pstmt.getGeneratedKeys(); // Statement.RETURN_GENERATED_KEYS와 매칭된다.
if (rs.next()) { // 값이 있으면 member.setId(rs.getLong(1)); // 값을 꺼내고 세팅해준다.
} else { throw new SQLException("id 조회 실패"); } return member; } catch (Exception e) { throw new IllegalStateException(e); } finally { close(conn, pstmt, rs); } } @Override public Optional<Member> findById(Long id) { String sql = "select * from member where id = ?"; Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try { conn = getConnection(); pstmt = conn.prepareStatement(sql); pstmt.setLong(1, id); rs = pstmt.executeQuery(); // 조회는 executeQuery()이다.
if (rs.next()) { // 값이 있으면 Member member = new Member(); member.setId(rs.getLong("id")); member.setName(rs.getString("name")); return Optional.of(member); // Member 객체 생성 후 반환해준다.
} else { return Optional.empty(); } } catch (Exception e) { throw new IllegalStateException(e); } finally { close(conn, pstmt, rs); } } @Override public List<Member> findAll() { String sql = "select * from member"; // 전부 다 조회한다.
Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try { conn = getConnection(); pstmt = conn.prepareStatement(sql); rs = pstmt.executeQuery(); List<Member> members = new ArrayList<>(); // 전부 다 조회하기 때문에 List while (rs.next()) { // Loop를 돌면서 모든 회원들을 리스트에 담는다.
Member member = new Member(); member.setId(rs.getLong("id")); member.setName(rs.getString("name")); members.add(member); } return members; } catch (Exception e) { throw new IllegalStateException(e); } finally { close(conn, pstmt, rs); } } @Override public Optional<Member> findByName(String name) { String sql = "select * from member where name = ?"; Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try { conn = getConnection(); pstmt = conn.prepareStatement(sql); pstmt.setString(1, name); rs = pstmt.executeQuery(); if (rs.next()) { Member member = new Member(); member.setId(rs.getLong("id")); member.setName(rs.getString("name")); return Optional.of(member); } return Optional.empty(); } catch (Exception e) { throw new IllegalStateException(e); } finally { close(conn, pstmt, rs); } } private Connection getConnection() { return DataSourceUtils.getConnection(dataSource); } private void close(Connection conn, PreparedStatement pstmt, ResultSet rs) { // 역순으로 close 해준다.
try { if (rs !
= null) { rs.close(); } } catch (SQLException e) { e.printStackTrace(); } try { if (pstmt !
= null) { pstmt.close(); } } catch (SQLException e) { e.printStackTrace(); } try { if (conn !
= null) { close(conn); } } catch (SQLException e) { e.printStackTrace(); } } private void close(Connection conn) throws SQLException { DataSourceUtils.releaseConnection(conn, dataSource); } }

Jdbc 구성원 저장소

package hello.hellospring;

import hello.hellospring.repository.JdbcMemberRepository;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
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.sql.DataSource;

@Configuration
public class SpringConfig {

    // JdbcMemberRepository에 필요한 DataSource
    // 스프링이 자동으로 dataSource를 만들어준다.
private DataSource dataSource; @Autowired public SpringConfig(DataSource dataSource) { this.dataSource = dataSource; } @Bean // 스프링 빈을 직접 등록한다.
public MemberService memberService() { return new MemberService(memberRepository()); // memberRepository() 메소드 호출 } @Bean // 스프링 빈을 직접 등록한다.
public MemberRepository memberRepository() { // return new MemoryMemberRepository(); return new JdbcMemberRepository(dataSource); } }
  • DataSource는 데이터베이스 연결을 설정하는 데 사용되는 개체입니다.
  • Spring Boot는 데이터베이스 연결 정보를 기반으로 DataSource를 생성하고 Spring Bean을 생성합니다.
    DI를 얻는 방법은 다음과 같습니다.
  • SpringConfig코드가 변경되지 않은 것을 제외하고.
  • Spring 컨테이너는 매우 편리하게 다형성을 지원합니다.

구현 클래스에 이미지 추가


[Spring] 스프링 입문 - 4

  • MemberService~이다 MemberRepository~에 달려있다
  • MemberRepository의 구현으로 MemoryMemberRepository그리고 JdbcMemberRepository있다

스프링 구성 이미지


[Spring] 스프링 입문 - 5

  • 이전에는 -버전 memberRepository 스프링 빈을 등록했다면 연결을 해제하고 -버전 뒤로 memberRepository 등록된 봄콩.
  • 개폐 원칙(OCP): 확장에 개방, 수정이나 변경에 폐쇄.
  • Springs DI(의존성 주입) 사용, 기존 코드를 건드리지 않고도 튜닝만으로 구현 클래스를 변경할 수 있습니다!
  • DB에 저장되기 때문에 Spring 서버를 재시작해도 데이터가 안전하게 저장된다.

실행


[Spring] 스프링 입문 - 6
결과가 DB에 잘 입력되고 데이터가 안정적으로 저장되는 것을 확인할 수 있습니다.

스프링 통합 테스트

  • 이전 섹션의 테스트는 Spring이 아닌 순수한 Java 코드와 관련된 테스트였습니다.
  • 이제부터 스프링 컨테이너와 DB를 연결하는 통합 테스트를 진행합니다.
  • 실제로는 테스트 전용 DB를 생성한 후 테스트를 진행합니다.

회원 서비스 스프링 통합 테스트

package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

@SpringBootTest
@Transactional // 테스트가 끝나면 롤백을 해준다.
따라서 DB에 넣었던 데이터들을 반영을 하지않고 지워준다.
테스트를 반복 실행할 수 있다.
class MemberServiceIntegrationTest { // 테스트코드를 작성할 때는 가장 편한 방법을 쓰면 된다.
// 기존 코드들은 생성자 주입이 좋다.
@Autowired MemberService memberService; @Autowired MemberRepository memberRepository; @Test void 회원가입() { //given Member member = new Member(); member.setName("spring"); //when Long saveId = memberService.join(member); //then Member findMember = memberService.findOne(saveId).get(); assertThat(member.getName()).isEqualTo(findMember.getName()); } @Test public void 중복_회원_예외() { //given Member member1 = new Member(); member1.setName("spring"); Member member2 = new Member(); member2.setName("spring"); //when memberService.join(member1); // memberService.join(member2) 로직을 실행하면 IllegalStateException 예외가 터져야한다.
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2)); assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.
"); } }
  • @SpringBootTest: Spring 컨테이너와 테스트를 함께 실행합니다.
  • @Transactional: 테스트 케이스에서 @Transactional 주석의 경우 테스트가 시작되기 전에 트랜잭션이 시작되고 테스트가 완료된 후 항상 롤백됩니다.
    이렇게 하면 데이터가 DB에 남아 있지 않아 다음 테스트에 영향을 주지 않습니다.
  • 테스트 코드를 작성할 때 가장 편리한 방법을 사용하십시오. 생성자 주입이 가장 좋지만 단순성을 위해 필드 주입이 사용됩니다.
  • 단위 테스트: 최소한의 단위로 테스트하는 방법입니다.
  • 좋은 테스트는 좋은 단위 테스트입니다!

실행


[Spring] 스프링 입문 - 7
Spring 컨테이너와 테스트가 정상적으로 실행되고 있음을 확인할 수 있습니다.

스프링 Jdbc 템플릿

  • 순수 jdbc와 동일한 환경 설정으로 진행합니다.
  • Spring의 JdbcTemplate은 JDBC API에서 본 대부분의 반복 코드를 제거하는 MyBatis와 유사한 라이브러리입니다.
    그러나 SQL은 직접 작성해야 합니다.
  • 실무에서 자주 사용하는 라이브러리입니다.

Spring JdbcTemplate 멤버 저장소

package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.springframework.beans.factory.annotation.Autowired;
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{
    /*
     injection을 받을 수 있는 것은 아니다.
DataSource가 필요하다.
스프링이 자동으로 DataSource injection을 해준다.
*/ private final JdbcTemplate jdbcTemplate; // @Autowired // 생성자가 하나만 있을 경우 생략 가능 public JdbcTemplateMemberRepository(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } @Override public Member save(Member member) { SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate); // jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id"); // query를 짤 필요가 없다.
Map<String, Object> parameters = new HashMap<>(); parameters.put("name", member.getName()); Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters)); member.setId(key.longValue()); return member; } // Optional로 감싸서 반환한다.
@Override public Optional<Member> findById(Long id) { // 결과를 RowMapper로 매핑을 해줘야한다.
List<Member> result = jdbcTemplate.query("select * from member where id = ?", memberRowMapper(), id); return result.stream().findAny(); } // Optional로 감싸서 반환한다.
@Override public Optional<Member> findByName(String name) { // 결과를 RowMapper로 매핑을 해줘야한다.
List<Member> result = jdbcTemplate.query("select * from member where name = ?", memberRowMapper(), name); return result.stream().findAny(); } @Override public List<Member> findAll() { // 결과를 RowMapper로 매핑을 해줘야한다.
return jdbcTemplate.query("select * from member", memberRowMapper()); } private RowMapper<Member> memberRowMapper() { // lambda로 변경해준다.
return (rs, rowNum) -> { Member member = new Member(); member.setId(rs.getLong("id")); member.setName(rs.getString("name")); return member; }; } }
  • 순수 Jdbc와 비교하여 JdbcTemplate을 사용할 때 반복되는 코드가 대폭 줄어드는 것을 확인할 수 있습니다.
  • 단, SQL은 개발자가 직접 작성해야 합니다.

JdbcTemplate을 사용하도록 Spring 구성 수정

package hello.hellospring;

import hello.hellospring.repository.JdbcMemberRepository;
import hello.hellospring.repository.JdbcTemplateMemberRepository;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
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.sql.DataSource;

@Configuration
public class SpringConfig {

    // JdbcMemberRepository에 필요한 DataSource
    // 스프링이 자동으로 dataSource를 만들어준다.
private final DataSource dataSource; // @Autowired 생략 가능 public SpringConfig(DataSource dataSource) { this.dataSource = dataSource; } @Bean // 스프링 빈을 직접 등록한다.
public MemberService memberService() { return new MemberService(memberRepository()); // memberRepository() 메소드 호출 } @Bean // 스프링 빈을 직접 등록한다.
public MemberRepository memberRepository() { // return new MemoryMemberRepository(); // return new JdbcMemberRepository(dataSource); return new JdbcTemplateMemberRepository(dataSource); } }
  • -버전 memberRepository 등록된 봄콩.

실행


[Spring] 스프링 입문 - 8
테스트가 정상적으로 진행되는지 확인할 수 있습니다.

JPA

  • JPA는 기본 SQL과 기존 반복 코드를 생성하고 실행합니다.
  • 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'
	}
}
  • jdbc 제거, spring-boot-starter-data-jpa에 추가
  • spring-boot-starter-data-jpajdbc 관련 라이브러리를 포함합니다.

Spring Boot에 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
  • show-sql=true: JPA에서 생성한 SQL을 출력합니다.
  • ddl-auto=none: JPA가 테이블을 직접 생성합니다.
    이미 테이블을 생성했으므로 자동 생성 기능을 해제하겠습니다.
  • ddl-auto=create: 엔터티 정보를 기반으로 테이블을 직접 생성합니다.

JPA 엔티티 매핑

  • JPA는 간단한 사양 인터페이스이고 Hibernate는 JPA를 구현하는 구현입니다.
  • JPA는 Java ORM(Object Relational Mapping) 기술에 대한 API 표준 사양입니다.
package hello.hellospring.domain;

import javax.persistence.*;

@Entity // JPA가 관리하는 엔티티가 된다.
public class Member { /* 요구사항 1. id 식별자가 있어야한다.
2. 이름이 있어야한다.
*/ // DB에 값을 넣으면 DB가 id값을 자동으로 생성해주는 것을 Identity 전략이라고 한다.
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 고객이 정하는 id가 아닌 데이터를 구분하기 위해 시스템이 정하는 id 값이다.
// @Column(name = "username") // 만약 컬럼 명이 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; } }
  • IDENTITY 전략: 기본 키 생성을 데이터베이스에 위임하기 위한 전략입니다.
  • @Column: JPA에서 DB 테이블의 컬럼을 매핑할 때 사용한다.

JPA 회원 저장소

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{

    /*
     JPA는 EntityManger로 모든것을 동작한다.
build.gradle에서 data-jpa를 받았다.
스프링 부트가 자동으로 EntityManger를 생성해준다.
만들어진걸로 Injection을 받으면 된다.
*/ private final EntityManager em; public JpaMemberRepository(EntityManager em) { this.em = em; } @Override public Member save(Member member) { // JPA가 다 해준다.
em.persist(member); // 영구 저장한다는 뜻이다.
return member; } // Optional로 감싸서 반환한다.
@Override public Optional<Member> findById(Long id) { Member member = em.find(Member.class, id); return Optional.ofNullable(member); } // Optional로 감싸서 반환한다.
@Override public Optional<Member> findByName(String name) { // 객체 지향 쿼리인 JPQL을 사용해야 한다.
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() { /* PK 기반이 아닌 것들은 객체 지향 쿼리인 JPQL을 사용해야 한다 객체를 대상으로 Query를 짠다.
객체 자체를 select 한다.
*/ return em.createQuery("select m from Member m", Member.class) .getResultList(); } }
  • JPA는 EntityManager에서 모든 것을 실행하고 Spring Boot는 EntityManager를 자동으로 생성합니다.
  • ⌥ + ⌘ + N 단축키: 인라인 코드.
  • 개발해야 할 코드가 대폭 줄어든 것을 확인할 수 있습니다.

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

import org.springframework.transaction.annotation.Transactional

// JPA는 항상 Transaction이 있어야한다.
@Transactional public class MemberService {}
  • org.springframework.transaction.annotation.Transactional사용
  • Spring은 클래스의 메소드가 실행될 때 트랜잭션을 시작하고 메소드가 정상적으로 완료되면 트랜잭션을 커밋합니다.
    런타임 예외가 발생하면 재설정합니다.
  • JPA에 의한 모든 데이터 변경은 트랜잭션 내에서 수행되어야 합니다.

JPA를 사용하도록 스프링 구성 변경

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;
import javax.sql.DataSource;

@Configuration
public class SpringConfig {
	
    private final DataSource dataSource;
    private final EntityManager em;

    @Autowired
    public SpringConfig(DataSource dataSource, EntityManager em) {
        this.dataSource = dataSource;
        this.em = em;
    }

    @Bean // 스프링 빈을 직접 등록한다.
public MemberService memberService() { return new MemberService(memberRepository()); // memberRepository() 메소드 호출 } @Bean // 스프링 빈을 직접 등록한다.
public MemberRepository memberRepository() { // return new MemoryMemberRepository(); // return new JdbcMemberRepository(dataSource); // return new JdbcTemplateMemberRepository(dataSource); return new JpaMemberRepository(em); // EntityManager가 필요하다.
} }
  • -버전 memberRepository 등록된 봄콩.

실행


[Spring] 스프링 입문 - 9
테스트가 정상적으로 진행되는지 확인할 수 있습니다.

  • 기본적으로 Hibernate 구현이 사용됩니다.

스프링 데이터 JPA

  • Spring Data JPA를 사용하면 저장소에 클래스를 구현하지 않고 인터페이스만으로 개발을 완료할 수 있습니다.
  • 반복적으로 개발된 기본 CRUD 기능은 모두 Spring Data JPA에서 제공합니다.
  • 실제로 관계형 데이터베이스를 사용한다면 Spring Data JPA는 더 이상 선택이 아니라 필수입니다!
  • 이전 JPA 구성을 있는 그대로 사용하십시오.

주의: Spring Data JPA는 JPA를 편리하게 사용할 수 있도록 도와주는 기술입니다.
따라서 JPA를 먼저 배워야 합니다.

스프링 데이터 JPA 멤버 리포지토리

package hello.hellospring.repository;

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

import java.util.Optional;

/*
 인터페이스가 인터페이스를 받을 경우 extends를 사용한다.
인터페이스는 다중 상속이 가능하다.
JpaRepository를 받고 있으면 스프링 데이터 JPA가 구현체를 자동으로 만들어줘서 스프링 빈에 등록해준다.
*/ public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository { // findByName // JPQL select m from Member m where m.name = ? 이렇게 쿼리를 짠다.
@Override Optional<Member> findByName(String name); }
  • 인터페이스는 여러 번 상속될 수 있습니다.
  • 인터페이스가 인터페이스를 받을 때 extends사용
  • JpaRepositorySpring Data JPA는 자동으로 구현을 생성하고 Spring bean을 등록합니다.
  • 인터페이스 메서드만 선언프링 데이터 JPA 메서드 이름으로 JPQL 쿼리를 생성하고 실행합니다.

Spring 데이터 JPA 멤버 저장소를 사용하도록 Spring 구성 수정

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;
import javax.sql.DataSource;

@Configuration
public class SpringConfig {

    private final MemberRepository memberRepository;

    // 스프링 데이터 JPA가 만든 구현체가 등록이 된다.
@Autowired public SpringConfig(MemberRepository memberRepository) { this.memberRepository = memberRepository; } @Bean // 스프링 빈을 직접 등록한다.
public MemberService memberService() { return new MemberService(memberRepository); } }
  • 스프링 데이터 jpa SpringDataJpaMemberRepository스프링 빈을 자동으로 등록합니다.

Spring Data JPA에서 제공하는 클래스


[Spring] 스프링 입문 - 10

Spring Data JPA에서 제공하는 기능

  • 인터페이스를 통해 기본 CRUD 기능을 제공합니다.
  • findByName(), findByEmail()메서드 이름만 검색하는 기능을 제공합니다.
  • 페이징 기능은 자동으로 제공됩니다.

참고: 실제로 JPA 및 Spring Data JPA가 기본적으로 사용되며 복잡한 동적 쿼리는 Querydsl 라이브러리를 사용합니다.
Querydsl을 사용하면 Java 코드로 쿼리를 안전하게 작성할 수 있으며 동적 쿼리를 편리하게 작성할 수 있습니다.
이 조합으로 해결하기 어려운 쿼리의 경우 JPA에서 제공하는 네이티브 쿼리나 앞서 배운 Spring JdbcTemplate을 사용할 수 있습니다.