[스프링 입문] 스프링 JdbcTemplate, 스프링통합테스트

atdawn·2024년 5월 14일

SPRING BOOT+JPA

목록 보기
10/49

📹 참고 : 인프런 [ 스프링 입문 - 김영한 ]


JDBC

  • 자바는 DB와 연결해서 데이터 입출력 작업을 할 수 있도록 JDBC 라이브러리 (java.sql패키지) 제공
  • JDBC는 DBMS의 종류와 상관없이 동일하게 사용할 수 있는 클래스와 인터페이스 제공

JDBC Driver

  • JDBC 인터페이스를 구현한 것으로, DBMS마다 별도로 다운로드받아 사용
    -DriverManger 클래스: JDBC Driver를 관리하며 DB와 연결해서 Connection 구현 객체 생성
    -Connection 인터페이스 : Statement, PreparedStatement, CallableStatement 구현객체를 생성하며,
    트랜잭션 처리 및 DB 연결을 끊을 때 사용
  • Statement 인터페이스 : SQL의 DDL과 DML 실행시 사용 ( Statement 보다는 PreparedStatement를 더 사용)
  • PreparedStatement : SQL 의 DDL,DML 문 실행시 사용. 매개변수화된 SQL문을 써 편리성과 보안성 유리
  • CallableStatement : DB에 저장된 프로시저와 함수를 호출
  • ResultSet : DB에서 가져온 데이터를 읽음

JDBC Template

이번에는 JDBC Template을 사용하여 H2 DB와 연결해보자.

스프링 JdbcTemplate과 MyBatis 같은 라이브러리는 JDBC API에서 본 반복 코드를 대부분 제거해준 다. 하지만 SQL은 직접 작성해야 한다.

환경설정

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

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를 받을 수 있다.
  • 스프링의 DI (Dependencies Injection)을 사용하면 기존 코드를 전혀 손대지 않고, 설정만으로 구현 클 래스를 변경할 수 있다.
  • 데이터를 DB에 저장하므로 스프링 서버를 다시 실행해도 데이터가 안전하게 저장된다.

스프링 통합 테스트

스프링 컨테이너와 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)
//파라미터 : 예외가 발생할 것으로 예상되는 특정 예외 클래스,예외가 발생할 것으로 예상되는 테스트 대상 코드를 포함


테스트 성공~



브라우저에서도 회원이 제대로 등록되는 것을 확인했다.

개발 과정에 있어서 테스트 케이스를 꼼꼼하게 작성하는 것이 중요하다!

profile
복습 복습 복습

0개의 댓글