스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 by 김영한
H2는 개발이나 테스트 용도로 가볍고 편리한 DB로, 웹 화면을 제공한다.
chmod 755 h2.sh
(윈도우 사용자는 x)./h2.sh
(윈도우 사용자는 h2.bat
)jdbc:h2:~/test
(최초 한번)~/test.mv.db
파일 생성 확인jdbc:h2:tcp://localhost/~/test
이렇게 접속H2 데이터베이스에 접근해서 member
테이블 생성
테이블 관리를 위해 프로젝트 루트에 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)
);
100.1.2.3
-> localhost
로 변경하고 Enter를 입력한다. 나머지 부분은 절대 변경하면 안된다. (특히 뒤에 세션 부분이 변경되면 안된다.)jdbc:h2:~/test
), 데이터베이스가 정상 생성된다.implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'
resources/application.properties
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
주의! 스프링부트 2.4부터는
spring.datasource.username=sa
를 꼭 추가해주어야 한다. 그렇지 않으면 Wrong user name or password 오류가 발생한다. 참고로 다음과 같이 마지막에 공백이 들어가면 같은 오류가 발생한다.spring.datasource.username=sa
공백 주의, 공백은 모두 제거해야 한다.
이전의 Memory 구현체와는 어떤 차이?
MemoryMemberRepository
:MemberRepository
인터페이스를 메모리에 구현
JdbcMemberRepository
: DB와 연결해서 Jdbc에 구현
public class JdbcMemberRepository implements MemberRepository {
private final DataSource dataSource;
// 생성자가 하나이면 @Autowired 생략 가능
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());
pstmt.executeUpdate();
rs = pstmt.getGeneratedKeys();
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);
}
}
...
}
@Bean
public MemberRepository memberRepository() {
// return new MemoryMemberRepository();
return new JdbcMemberRepository(dataSource);
}
스프링 컨테이너와 DB까지 연결한 통합 테스트를 진행해보자.
@SpringBootTest
@Transactional
class MemberServiceIntegrationTest {
@Autowired MemberService memberService;
@Autowired MemberRepository memberRepository;
@Test
public void 회원가입() throws Exception {...}
@Test
public void 중복_회원_예외() throws Exception {...}
}
@SpringBootTest
: 스프링 컨테이너와 테스트를 함께 실행한다.@Transactional
: 테스트 케이스에 이 애노테이션이 있으면, 테스트 시작 전에 트랜잭션을 시작하고, 테스트 완료 후에 항상 롤백한다. 이렇게 하면 DB에 데이터가 남지 않으므로 다음 테스트에 영향을 주지 않는다.@Rollback
: 테스트 완료 후에도 DB에 데이터가 남아있는 걸 확인해보고 싶다면 실행할 테스트 클래스 또는 메서드에 @Rollback
(org.springframework.test.annotation.Rollback) 애노테이션을 붙이고 value에 false
값을 주면 된다. ( @Rollback(false)
)스프링 JdbcTemplate과 MyBatis 같은 라이브러리는 JDBC API에서 본 반복 코드를 대부분 제거해준다. 하지만 SQL은 직접 작성해야 한다.
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;
}
}
@Bean
public MemberRepository memberRepository() {
// return new MemoryMemberRepository();
// return new JdbcMemberRepository(dataSource);
return new JdbcTemplateMemberRepository(dataSource);
}