DEVELOPMENT/Spring

7. 스프링 DB 접근 기술2

Tiny Commit 2025. 5. 25. 13:02

 

 

 

 

4. 스프링 JdbcTemplate

1. JdbcTemplate이란?

  • JDBC
    자바가 데이터베이스에 쿼리(SQL)를 보내고 결과를 받는 표준 API예요.
    하지만 매번 접속 열고(Connection), 쿼리 문장 만들고(PreparedStatement), 결과 읽고(ResultSet), 예외 처리하는 반복 코드가 많아요.
  • JdbcTemplate
    스프링이 만든 도우미(helper) 클래스예요.
    “연결부터 정리까지” 복잡한 반복 코드를 대부분 대신 해 주고,
    우리가 할 일순수한 SQL(문장) 작성과
    “결과를 자바 객체로 mapping”하는 부분뿐이에요.

2. 코드 한 줄씩 보기

2-1. 생성자에서 DataSource 주입받기

public class JdbcTemplateMemberRepository implements MemberRepository {
    private final JdbcTemplate jdbcTemplate;

    public JdbcTemplateMemberRepository(DataSource dataSource) {
        // 스프링이 만들어 놓은 DataSource(커넥션 풀)를 받아서
        // JdbcTemplate 객체를 직접 생성해 둡니다.
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }
    …
}
  • DataSource:
    DB 접속 정보(URL, 아이디, 비밀번호 등)를 담고 있는 커넥션 풀(pool)이에요.
  • JdbcTemplate(dataSource):
    이걸 통해 “커넥션 가져오기 → 쿼리 실행 → 커넥션 반납” 과정을 모두 처리해 줍니다.

2-2. 회원 저장하기 (save)

@Override
public Member save(Member member) {
    // 간단한 insert 작업을 도와주는 유틸
    SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate)
        .withTableName("member")                 // 테이블 이름
        .usingGeneratedKeyColumns("id");         // 자동 생성된 PK 컬럼

    Map<String, Object> parameters = new HashMap<>();
    parameters.put("name", member.getName());   // 넣을 값(name 컬럼)

    // 실행하고 자동 생성된 id 값을 받아옴
    Number key = jdbcInsert.executeAndReturnKey(
        new MapSqlParameterSource(parameters)
    );

    member.setId(key.longValue());              // 엔티티에 id 세팅
    return member;
}
  • SimpleJdbcInsert:
    매번 INSERT INTO member(name) VALUES(?) 쓰지 않고, 더 편하게 사용 가능해요.
  • executeAndReturnKey:
    DB가 만들어 준 자동 증가(id)를 돌려받아서, 우리 객체에도 반영합니다.

2-3. 조회하기 (findById, findAll, findByName)

@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();
}
  • query(sql, rowMapper, 파라미터…)
    1. SQL 실행
    2. ResultSet의 각 행(row)을 memberRowMapper()로 Member 객체로 바꿔줌
    3. List<Member> 반환

2-4. 결과 매핑 규칙 (RowMapper)

private RowMapper<Member> memberRowMapper() {
    // 람다식 사용: ResultSet → Member 객체로 변환
    return (rs, rowNum) -> {
        Member member = new Member();
        member.setId(rs.getLong("id"));      // id 컬럼 읽어서 세팅
        member.setName(rs.getString("name"));// name 컬럼 읽어서 세팅
        return member;
    };
}
  • RowMapper:
    DB가 돌려준 한 줄(row) 의 데이터를
    우리가 쓰는 Member 객체로 변환(mapping) 해 주는 역할입니다.

3. Spring 설정에서 JdbcTemplate 사용하기

3-1. SpringConfig에 빈 등록

@Configuration
public class SpringConfig {

    private final DataSource dataSource;   // 스프링이 자동으로 만들어 줌

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

    @Bean
    public MemberService memberService() {
        // 서비스는 레포지토리를 생성자 주입으로 받습니다.
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        // 원하는 구현체로 바꿔 끼워쓸 수 있어요!
        //return new MemoryMemberRepository();           // 메모리 저장소
        //return new JdbcMemberRepository(dataSource);  // 순수 JDBC
        return new JdbcTemplateMemberRepository(dataSource); // JdbcTemplate 사용
    }
}
  • @Bean:
    이 메서드의 리턴 값을 스프링이 관리하는 “빈(Component)”으로 만들어 줍니다.
  • 구현체 선택:
    MemoryMemberRepository, JdbcMemberRepository, JdbcTemplateMemberRepository 중
    하나만 주석 해제해서 사용하면, 나머지 코드는 전혀 변경 없이 동작합니다.

4. 요약: 왜 이렇게 쓰는 걸까?

  1. 반복 코드 제거
    • JDBC 고유의 복잡한 연결/정리 코드를 JdbcTemplate이 대신 처리해 줘요.
  2. 직접 SQL 제어
    • MyBatis, JPA와 달리 SQL을 “내가 원하는 대로” 직접 쓸 수 있어요.
    • 복잡한 쿼리나 성능 튜닝이 필요할 때 유리합니다.
  3. 스프링과의 결합(Integration)
    • DataSource·Transaction 같은 스프링 기능을 그대로 이용할 수 있어요.
    • 설정만 바꾸면 H2, MySQL, Oracle 등 다른 DB로 손쉽게 전환 가능!

🔥 꼭 기억해야 할 포인트

  • JdbcTemplate 은 “JDBC API 위에 얹힌 편리한 래퍼(wrapper)”다!
  • SimpleJdbcInsert 로는 INSERT가, query() 로는 SELECT가 간단해진다.
  • RowMapper 는 “한 줄 → 객체” 변환기.
  • @Bean 설정 하나만 바꿔서 메모리 저장소 ↔ 순수 JDBC ↔ JdbcTemplate 저장소를 자유롭게 바꿀 수 있다!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

5. JPA

1. JPA란 무엇인가요?

  • 원래 JDBC를 쓰면
    1. Connection 열고,
    2. PreparedStatement 만들고(SQL 써야 하고),
    3. ResultSet 돌면서 컬럼 하나하나 꺼내서 객체에 담고,
    4. 예외 처리하고,
    5. 커넥션 닫고…
      매번 이런 반복 코드가 너무 많았어요.
  • JPA
    • 이 반복 코드를 거의 땡겨 줍니다.
    • 우리가 작성하는 건 **“내가 저장하고 싶은 자바 객체”**와
    • “어떤 객체를 저장할 건지 선언”(애너테이션)뿐이에요.
    • SQL을 직접 쓰지 않아도 JPA가 자동으로 만들어서 실행해 줍니다.
  • 장점!
    1. 객체 중심 설계로 패러다임 전환
    2. 개발 생산성↑ (반복 코드↓)
    3. 복잡한 쿼리는 JPQL(객체지향 SQL)로, 간단한 건 자동 생성!

2. 필요한 라이브러리(Gradle에 추가)

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
  runtimeOnly 'com.h2database:h2'            // 테스트·개발용 가벼운 DB
  implementation 'org.springframework.boot:spring-boot-starter-web'
  implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
  testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
  • spring-boot-starter-data-jpa 안에 JDBC도 다 포함돼 있어요.
  • H2는 메모리 DB라 가볍게 실험할 때 좋아요.

3. 스프링 부트에 JPA 설정하기

src/main/resources/application.yml (또는 .properties) 에 이렇게 쓰면 돼요:

spring:
  datasource:
    url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1  
    username: sa
    password: 
    driver-class-name: org.h2.Driver
  jpa:
    show-sql: true              # 콘솔에 SQL이 찍히게
    hibernate:
      ddl-auto: create-drop     # 애플리케이션 실행할 때 테이블 자동 생성·삭제
  • ddl-auto 옵션
    • create-drop → 시작할 때 테이블 만들고, 종료할 때 지워요.
    • update → 변경된 엔티티에 맞춰 테이블을 “수정”
    • none → 자동 생성·수정을 끄기

4. 엔티티(Entity) 만들기

@Entity                 // 이 클래스가 DB 테이블과 1:1 매핑된다는 표시
public class Member {
  @Id                  // 이 필드가 PK(기본키)란 뜻
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;     // DB가 자동으로 만들어 주는 번호

  private String name; // 회원 이름

  // ↓ 여기에 getter/setter만 있으면 끝!
}
  • **@Entity**를 붙이면
    → JPA가 이 클래스를 “DB 테이블처럼” 관리해 줘요.
  • @Id + @GeneratedValue
    → id 컬럼을 자동 증가(1, 2, 3…)로 만들어 줍니다.

5. JPA Repository 직접 구현하기 - JpaMemberRepository

package hello.hello_spring.repository;

import hello.hello_spring.domain.Member;
import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;

import java.util.List;
import java.util.Optional;

@Transactional
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();
    }
}
  • EntityManager는 JPA를 쓰려면 Entity Manager가 필요하다. 
    → JPA가 제공하는 “DB에 실제로 저장·조회”해 주는 객체예요.
    -> JPA entity manager로 도근 것이 동작한다. 
    • 만들어진 EntityManager을 인잭션 받으면 된다: 프로퍼티랑 데이터 베이스를 이용해서 만들어 진다. 떄문에 데이터 소스를 다 내부적으로 들고 있어서 DB 통신을 내부적으로 할 수 있다. 
  • JPQL
    → "select m from Member m" 이렇게 객체 기준으로 조회합니다.

6. 서비스에 트랜잭션(@Transactional) 적용

@Service
@Transactional     // 이 클래스의 public 메서드를 실행할 때 트랜잭션을 자동으로 처리
public class MemberService {
  // …
}
  • 트랜잭션 안에서만 JPA의 모든 저장·수정·삭제(데이터 변경)가 제대로 동작해요.
  • 메서드가 정상 종료되면 commit,
  • 런타임 예외 터지면 rollback!

7. 스프링 설정(SpringConfig)에서 JPA 사용하기

package hello.hello_spring;

import hello.hello_spring.repository.JpaMemberRepository;
import hello.hello_spring.repository.MemberRepository;
import hello.hello_spring.service.MemberService;
import jakarta.persistence.EntityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class SpringConfig {

    private final DataSource dataSource;
    private final EntityManager em;

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

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        //return new MemoryMemberRepository(); //인터페이스에는 new 안됨. 구현체를 리턴.
        //return new MemoryMemberRepository();
        //return new JdbcMemberRepository(dataSource);//인터페이스에는 new 안됨. 구현체를 리턴.
        return new JpaMemberRepository(em);
    }
}
  • new JpaMemberRepository(em) 한 줄로
    JPA가 대신 SQL 짜고, 테이블 만들고, 저장·조회까지 다 해 줍니다.
  • Hibernate: 오픈 소스 구현체이다.    


8. 한 눈에 보는 흐름

  1. 의존성(라이브러리) 추가 → JPA, H2
  2. 설정(application.yml) → DB 접속 정보 + ddl-auto
  3. 엔티티(@Entity) 만들기
  4. Repository 구현 → EntityManager 사용
  5. Service에 @Transactional → 트랜잭션 관리
  6. SpringConfig에 Bean 등록 → JPA 구현체 선택

이제 “객체 중심”으로 코드를 짜고, 반복되는 JDBC 코드는 JPA가 모두 처리해 주는 마법 같은 세계에 오신 걸 환영해요! 😄

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

6. 스프링 데이터 JPA

1. 스프링 데이터 JPA란?

  • JPA 위에 올라탄, “반복되는 데이터베이스 CRUD 코드를 자동으로 만들어 주는 프레임워크”예요.
  • 인터페이스 선언만으로(구현 클래스 없이)
    • 저장, 조회, 수정, 삭제 같은 기본 기능이 모두 제공되고,
    • 메서드 이름만 보고도 원하는 조회(SQL)를 자동으로 만들어 줍니다.
  • 덕분에 개발자는 오직 비즈니스 로직(예: “회원 가입 처리”나 “이메일 중복 확인”)에만 집중할 수 있어요.

 

 

2. 기본 설정 — JPA까지는 그대로

스프링 데이터 JPA를 쓰려면, 앞에서 썼던 **JPA 설정(application.yml, 엔티티, spring-boot-starter-data-jpa)**은 전부 그대로 사용하면 됩니다.
(추가로 라이브러리 더 넣을 필요 없이, 이미 JPA 스타터 안에 다 포함돼 있어요.)

 

 

3. 핵심 코드: 인터페이스만 선언하기 - SpringDataJpaMemberRepository

package hello.hellospring.repository;

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

import java.util.Optional;

// 1) JpaRepository가 제공하는 모든 기본 CRUD를
//    MemberRepository 인터페이스에 “자동 상속” 시켜 줍니다.
//    <Member, Long> 은 <엔티티 클래스, PK 타입> 지정
//
// 2) MemberRepository라는 나만의 인터페이스도 함께 상속하여
//    기존 서비스 코드(Controller/Service)에서 MemberRepository 타입을
//    그대로 사용할 수 있게 해 줍니다.
public interface SpringDataJpaMemberRepository
        extends JpaRepository<Member, Long>, MemberRepository {

    // 이름으로 조회하는 메서드 시그니처만 선언해 주면,
    // “select m from Member m where m.name = ?” 쿼리가 자동으로 생성됩니다.
    Optional<Member> findByName(String name);
}
  • 스프링 데이터 JPA가 JPA 리포지토리를 받고 있으면 구현제를 자동으로 만들어줘서 스프링Bean를 자동으로 등록한다. 
  • extends JpaRepository<Member, Long>
    → save(), findAll(), findById(), delete() 등 모든 기본 메서드바로 사용 가능
  • , MemberRepository
    → 기존에 만든 MemberRepository(메서드 정의된 인터페이스)를 함께 상속해서,
    → 서비스 코드는 아무 수정 없이 새 리포지토리를 바로 주입받아 쓸 수 있어요.

 

4. 스프링 설정 — 빈(Bean) 등록이 필요 없다! - SpringConfig

package hello.hellospring;

import hello.hellospring.repository.MemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// 스프링 데이터 JPA 인터페이스만 있으면,
// 스프링 부트가 애플리케이션 구동 시에
// SpringDataJpaMemberRepository 구현체를 **자동 생성**해서
// 스프링 빈으로 **등록**해 줍니다.
@Configuration
public class SpringConfig {
    private final MemberRepository memberRepository;

    // 스프링 빈(MemberRepository 구현체)이 자동 주입됨
    public SpringConfig(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Bean
    public MemberService memberService() {
        // 기존과 똑같이 MemberService에 주입해 주면 끝!
        return new MemberService(memberRepository);
    }
}
  • MemberRepository memberRepository 파라미터로 받기만 하면,
  • 스프링 데이터 JPA가 만든 실제 구현체(SpringDataJpaMemberRepository)를 주입해 줍니다.
  • 따라서 @Bean memberRepository() 같은 코드도 아예 필요 없어져요.

 

 

5. 자동 제공 기능

기능 설명

기본 CRUD save(), findById(), findAll(), delete(), count() 등을 별도 구현 없이 사용 가능
메서드 이름만으로 조회 findByName(), findByEmail(), findByAgeGreaterThan() 등 메서드 이름만 선언하면 쿼리 자동 생성
페이징 & 정렬 findAll(Pageable pageable) 같은 메서드로 페이지별 조회정렬을 자동 지원

 

6. 복잡한 쿼리가 필요하다면?

  • Querydsl: 자바 코드로 “타입 안전한 동적 쿼리”를 작성
  • 네이티브 쿼리: @Query(value = "SELECT ...", nativeQuery = true)
  • JdbcTemplate: 아주 복잡하거나 튜닝이 필요한 SQL은 직접 작성

하지만 일반적인 CRUD와 간단한 조회모두 스프링 데이터 JPA만으로 거의 커버할 수 있어서,
개발자는 중요한 로직에만 집중할 수 있습니다.

 

 


출처 :  김영한, 『스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술』, 인프런 강의.