📹 참고 : 인프런 [ 스프링 입문 - 김영한 ]
이번에는 JDBC Template을 사용하여 H2 DB와 연결해보자.
스프링 JdbcTemplate과 MyBatis 같은 라이브러리는 JDBC API에서 본 반복 코드를 대부분 제거해준 다. 하지만 SQL은 직접 작성해야 한다.
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
공백주의,공백은모두제거해야한다.
스프링 JdbcTemplate 회원 리포지토리
java/hello/hellospring/repository/JdbcTemplateMemberRepository.java
public class JdbcTemplateMemberRepository implements MemberRepository{
private final JdbcTemplate jdbcTemplate;// JdbcTemplate 선언
@Autowired // 자동 주입된 데이터 소스를 사용하여 객체 생성 , 생략 가능(생성자가 하나이기 때문)
public JdbcTemplateMemberRepository(DataSource dataSource) { //데이터 소스 인젝션 받음
this.jdbcTemplate = new JdbcTemplate(dataSource);// 주입받은 데이터 소스로 JdbcTemplate 초기화
}
@Override
public Member save(Member member) {
// SimpleJdbcInsert를 사용하여 INSERT 쿼리 실행 및 자동 생성된 키 반환
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");// 테이블 이름 및 자동 생성된 키 컬럼 설정(DB 컬럼값과 동일해야함)
Map<String, Object> parameters = new HashMap<>();// 매개변수 맵 생성
parameters.put("name", member.getName());// 이름 설정
Number key = jdbcInsert.executeAndReturnKey(new
MapSqlParameterSource(parameters)); //INSERT 쿼리를 실행한 후에 자동으로 생성된 키(primary key)를 반환하는 메서드
member.setId(key.longValue());// 반환된 키를 사용하여 회원 객체에 ID 설정
return member;// 저장된 회원 객체 반환
}
@Override
public Optional<Member> findById(Long id) {
// 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();
}
// ResultSet의 row를 매핑하기 위한 RowMapper 구현
//row 단위로 ResultSet의 row를 매핑하기 위해 JdbcTemplate에서 사용하는 인터페이스
private RowMapper<Member> memberRowMapper(){
return (rs, rowNum) -> { //람다
Member member = new Member();// 새로운 회원 객체 생성
member.setId(rs.getLong("id"));// ResultSet에서 ID 가져와서 회원 객체에 설정
member.setName(rs.getString("name"));// ResultSet에서 이름 가져와서 회원 객체에 설정
return member;// 매핑된 회원 객체 반환
};
}
}
🔎 SimpleJdbcInsert 란 ?
Spring JDBC의 일부인 클래스로, 데이터베이스 테이블에 간단한 INSERT 쿼리를 실행할 때 사용된다.
이 클래스를 사용하면 INSERT 쿼리를 직접 작성하지 않고도 테이블에 데이터를 삽입할 수 있다.수행 가능 기능
- 특정 테이블에 데이터를 삽입할 때 사용.
- 자동 생성된 키(primary key) 값을 가져올 수 있다.
- 쿼리를 직접 작성할 필요 없이 간단한 설정만으로 INSERT 작업을 수행.
MapSqlParameterSource: 이 클래스는 이름-값 쌍을 저장하는 Spring의SqlParameterSource인터페이스의 구현체. 여기서는 parameters라는 맵을 사용하여 SqlParameterSource를 생성.executeAndReturnKey(): 이 메서드는SimpleJdbcInsert객체를 사용하여 INSERT 쿼리를 실행하고, 그 결과로 생성된 자동 키(primary key)를 반환. 이때MapSqlParameterSource로부터 파라미터 값을 받아서 쿼리에 삽입.반환값:executeAndReturnKey()메서드의 반환값은Number타입. 이 반환값은 일반적으로 자동으로 생성된 primary key 값임. 이 값은 삽입된 레코드의 primary key 컬럼이 숫자 형식인 경우에 사용됨.
RowMapper: Spring JDBC에서 사용되는 인터페이스로, SQL 쿼리의 결과 집합(ResultSet)에서 각 행(row)에 대한 매핑을 담당. 이를 통해 데이터베이스로부터 검색된 결과를 자바 객체로 변환./* RowMapper 인터페이스 */ public interface RowMapper<T> { T mapRow(ResultSet rs, int rowNum) throws SQLException; }
mapRow()메서드는ResultSet의 각 행에 대해 호출되며, 해당 행을 자바 객체로 변환하여 반환. 메서드의 두 번째 매개변수인rowNum은 현재 행의 인덱스를 나타냄.
RowMapper를 구현하여 사용하면ResultSet의 각 행을 돌면서 원하는 데이터를 추출하여 자바 객체에 매핑할 수 있음. 일반적으로는 해당 객체의 생성자나 setter 메서드를 사용하여 데이터를 설정.
스프링 설정 변경
java/hello/hellospring/SpringConfig.java
@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);
return new JdbcTemplateMemberRepository(dataSource); //jdbcTemplate
}
}

DataSource는 데이터베이스 커넥션을 획득할 때 사용하는 객체다. 스프링 부트는 데이터베이스 커넥션 정 보를 바탕으로 DataSource를 생성하고 스프링 빈으로 만들어둔다. 그래서 DI를 받을 수 있다.스프링 컨테이너와 DB까지 연결한 통합 테스트를 진행해보자.
/test/java/hello/hellospring/service/MemberServiceIntegrationTest.java
@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("이미 존재하는 회원입니다.");
} }
@SpringBootTest : 스프링 컨테이너와 테스트를 함께 실행한다.@Transactional : 테스트 케이스에 이 애노테이션이 있으면, 테스트 시작 전에 트랜잭션을 시작하고, 테 스트 완료 후에 항상 롤백한다. 이렇게 하면 DB에 데이터가 남지 않으므로 다음 테스트에 영향을 주지 않는다.
assertEquals(): Unit 프레임워크에서 제공하는 메서드 중 하나로, 두 값이 동일한지를 확인하는 데 사용. 동일하지 않으면 테스트 실패.public static void assertEquals(Object expected, Object actual) //파라미터 : 기대값, 실제 반환값
assertThrows(): JUnit 프레임워크에서 제공하는 메서드 중 하나로, 예외를 발생시키는 코드 블록을 테스트하는 데 사용.public static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable) //파라미터 : 예외가 발생할 것으로 예상되는 특정 예외 클래스,예외가 발생할 것으로 예상되는 테스트 대상 코드를 포함

테스트 성공~



브라우저에서도 회원이 제대로 등록되는 것을 확인했다.
개발 과정에 있어서 테스트 케이스를 꼼꼼하게 작성하는 것이 중요하다!