
1. H2 데이터베이스 설치
https://h2database.com/html/main.html
H2 Database Engine
H2 Database Engine Welcome to H2, the Java SQL database. The main features of H2 are: Very fast, open source, JDBC API Embedded and server modes; in-memory databases Browser based Console application Small footprint: around 2.5 MB jar file size Supp
h2database.com
실행


- JDBC URL: 내 로컬에 있는 파일 경로를 말해준다. test에 있는 파일을 말한다.
연결

"jdbc:h2:tcp://localhost/~/test" 여리로 연결하면 애플리케이션이랑 콘솔이랑 같이 접근 할 수 있다.
- 파일을 직접 접근하는 것이 아니라 소켓을 통해서 접근해서 여러군데에서 접근할 수 있다.

테이블 만들기
drop table if exists member CASCADE;
create table member
(
id bigint generated by default as identity,
name varchar(255),
primary key (id)
);

- generated by default as identity: null값, 값을 세팅하지 않고 인서트하면 DB가 들어왔을 때 자동으로 이 아이디값을 채워줌.
조회

insert into member(name) values('spring')

2. 순수 JDBC
// 자바는 DB랑 붙으려면 JDBC 드라이버가 꼭 필요하다.
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
// DB랑 붙을 때 데이터베이스가 제공하는 클라이언트가 필요한데, 여기서 부른다.
runtimeOnly 'com.h2database:h2'
접속정보 넣기 - application.properties
// url 만 넣어주면 spring boot가 알아서 해준다.
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
// h2 DB에 접근
spring.datasource.driver-class-name=org.h2.Driver
jdbc api 개발
- 회원 저장은 멤버 리포지토리
- JDBC멤버 리포지토리: 구현을 메모리에, DB랑 연동해서 JDBC로 할거야
- 인터페이스에서 구현제를 바꾸면서도 기존 코드를 변경하지 않고 바꿀 수 있따.
3.0 MySQL로 DB바꾸기
1. application.yml 파일 만들기
spring:
datasource:
url: jdbc:mysql://localhost:3306/introduction
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL8Dialect
format_sql: true
use_sql_comments: true
jdbc:
batch_size: 1000
2. JdbcMemberRepository.java
package hello.hello_spring.repository;
import hello.hello_spring.domain.Member;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import javax.sql.DataSource;
import java.util.List;
import java.util.Optional;
public class JdbcMemberRepository implements MemberRepository {
private final JdbcTemplate jdbcTemplate;
public JdbcMemberRepository(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public Member save(Member member) {
jdbcTemplate.update("insert into member(name) values(?)", member.getName());
Long id = jdbcTemplate.queryForObject("select max(id) from member", Long.class);
member.setId(id);
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 Optional<Member> findByName(String name) {
List<Member> result = jdbcTemplate.query("select * from member where name = ?", memberRowMapper(), name);
return result.stream().findAny();
}
@Override
public List<Member> findAll() {
return jdbcTemplate.query("select * from member", memberRowMapper());
}
private RowMapper<Member> memberRowMapper() {
return (rs, rowNum) -> {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return member;
};
}
}
3. SpringConfig.java
package hello.hello_spring;
import hello.hello_spring.repository.JdbcMemberRepository;
import hello.hello_spring.repository.MemberRepository;
import hello.hello_spring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@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(); //인터페이스에는 new 안됨. 구현체를 리턴.
//return new MemoryMemberRepository();
return new JdbcMemberRepository(dataSource);//인터페이스에는 new 안됨. 구현체를 리턴.
}
}
- 의존성 주입(Dependency Injection)
- DataSource 같은 외부 자원을 스프링이 관리하게 해서, 코드는 오직 “무엇을 할지(Repository)”만 선언하고, “어떻게 연결할지(DataSource 설정)”는 설정 파일(application.yml)과 스프링 컨테이너에 맡깁니다.
- 덕분에 테스트할 땐 메모리용 MemoryMemberRepository로, 운영에선 JdbcMemberRepository로 손쉽게 교체할 수 있어요.
- 관심사의 분리(Separation of Concerns)
- MemberService는 비즈니스 로직(회원 가입, 조회)에만 집중하고, DB 저장/조회 로직은 JdbcMemberRepository가 담당합니다.
- 설정(SpringConfig), 도메인(Member), 서비스, 저장소(Repository)가 각자 역할에만 집중하니 코드가 더 깔끔하고 유지보수가 쉬워집니다.
- 설정의 외부화 & 유연성
- DB 커넥션 정보와 JPA 옵션을 application.yml에 둬서, 환경(로컬·테스트·운영)에 따라 별도 수정 없이 프로파일만 바꿔주면 됩니다.
- 쿼리·매핑 방식(JdbcTemplate, RowMapper)도 필요에 따라 다른 방식(JPA, MyBatis 등)으로 바꾸기 쉽습니다.



3. 스프링 통합 테스트
1. 통합 테스트 - MemberServiceIntergationTest.java
package hello.hello_spring.service;
import hello.hello_spring.domain.Member;
import hello.hello_spring.repository.MemberRepository;
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.AssertionsForClassTypes.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@SpringBootTest
@Transactional
class MemberServiceIntegrationTest {
@Autowired
MemberService memberService;
@Autowired
MemberRepository memberRepository;
@Test
public void 회원가입() throws Exception {
//Given
Member member = new Member();
member.setName("hello");
//When
Long saveId = memberService.join(member);
//Then
Member findMember = memberRepository.findById(saveId).get();
assertEquals(member.getName(), findMember.getName());
}
@Test
public void 중복_회원_예외() throws Exception {
//Given
Member member1 = new Member();
member1.setName("spring");
Member member2 = new Member();
member2.setName("spring");
//When
memberService.join(member1);
IllegalStateException e = assertThrows(IllegalStateException.class,
() -> memberService.join(member2));//예외가 발생해야 한다.
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
}
}
1. 테스트 환경 설정: @SpringBootTest + @Transactional
- @SpringBootTest
- 스프링 컨테이너와 테스트를 함께 실행한다.
- 애플리케이션의 모든 스프링 빈을 실제로 로드해서 테스트합니다.
- 서비스ㆍ레포지토리 등 스프링 컨테이너에 등록된 컴포넌트 전체를 통합적으로 검증할 수 있어요.
- @Transactional
- 테스트 케이스에 이 애노테이션이 있으면, 테스트 시작 전에 트랜잭션을 시작하고, 테스트 완료 후에 항상 롤백한다. 이렇게 하면 DB에 데이터가 남지 않으므로 다음 테스트에 영향을 주지 않는다.
- 각 테스트 메서드가 끝나면 수행된 DB 작업을 롤백합니다.
- 실제 데이터를 더티할 걱정 없이, 반복 실행 가능한 깨끗한 테스트 환경을 보장해 줍니다.
2. 의존성 주입 확인: @Autowired
@Autowired
MemberService memberService;
@Autowired
MemberRepository memberRepository;
- 스프링 컨테이너가 생성한 빈을 실제로 주입받아 사용합니다.
- 수동으로 new 하지 않고도, 설정(SpringConfig)에 따라 H2든 MySQL이든 같은 빈을 주입받도록 해 줘야 합니다.
3. 기본 회원 가입 테스트 (회원가입)
// Given
Member member = new Member();
member.setName("hello");
// When
Long saveId = memberService.join(member);
// Then
Member findMember = memberRepository.findById(saveId).get();
assertEquals(member.getName(), findMember.getName());
- Given–When–Then 패턴: 테스트 가독성·유지보수를 위해 꼭 익혀야 합니다.
- memberService.join() 호출 후, 실제로 DB에 저장된 엔티티를 memberRepository.findById()로 꺼내와서 비교합니다.
- 서비스 레이어가 “정상 흐름(회원 저장 → 아이디 반환)”을 제대로 수행하는지 검증.
4. 예외 상황 테스트 (중복_회원_예외)
// When
memberService.join(member1);
IllegalStateException e = assertThrows(IllegalStateException.class,
() -> memberService.join(member2));
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
- 중복 회원 예외가 발생하는지 확인하는 부분
- 첫 번째 가입은 성공, 두 번째 동일 이름 가입에서 IllegalStateException이 터져야 합니다.
- assertThrows 사용법:
- 어떤 예외를 기대하는지,
- 그리고 예외 메시지까지 일치하는지 검증함으로써, 비즈니스 룰(“이름 중복 금지”)이 정확히 적용됐는지 확인.
5. 통합 테스트에서 학생이 꼭 알아야 할 점
- 단위 테스트(Unit Test)와의 차이
- 단위 테스트는 @SpringBootTest 없이, Mockito 같은 목업으로 서비스 혹은 리포지토리만 검증.
- 통합 테스트는 실제 스프링 컨테이너와 DB까지 모두 포함해 “실제 구동 환경”을 그대로 재현.
- 트랜잭션 롤백
- 테스트마다 데이터가 남지 않으니, 테스트 순서나 반복 실행에 따른 사이드 이펙트 걱정이 없습니다.
- 테스트 설계 원칙
- Given–When–Then 구조로 역할을 명확히 구분
- 정상 흐름과 예외 흐름을 각각 검증
- 의미 있는 이름(예: 중복_회원_예외)으로 테스트 목적을 바로 알 수 있게 할 것
- 스프링 빈 주입 검증
- @Autowired로 주입된 빈이 null 이 아니어야 합니다.
- 설정(SpringConfig, application.yml)에 문제가 있으면 통합 테스트 단계에서 바로 에러가 나므로, 환경 설정 오류를 빠르게 잡아낼 수 있습니다.
출처 : 김영한, 『스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술』, 인프런 강의.
'DEVELOPMENT > Spring' 카테고리의 다른 글
| [스프링부트 완전정복] 1. 스프링 부트 소개 (1) | 2025.09.30 |
|---|---|
| 7. 스프링 DB 접근 기술2 (0) | 2025.05.25 |
| 5. 회원 관리 예쩨 - 웹 MVC 개발 (0) | 2025.05.12 |
| 4. 스프링 빈과 의존관계 (0) | 2025.05.12 |
| 3. 회원 관리 예제 - 백엔드 개발 (0) | 2025.05.12 |