2022년 4월 14일 TIL

yshjft·2022년 4월 14일
0

데브코스 TIL

목록 보기
19/45

JDBC

DB 연동 통합테스트 코드 작성하기

테스트가 외부 환경(데이터베이스)에 영향을 받을 경우 테스트 자동화가 어려워진다. 이러한 문제를 해결하기 위해서 embeded database를 사용하자.

Embeded Mysql

h2 데이터 베이스도 있지만 현재 UUID_TO_BIN이라는 MySql 함수를 사용하고 있어 h2 대신 embeded Mysql을 사용한다. 아래는 embeded Mysql을 사용하는 테스트 코드의 일부이다.

@Configuration
@ComponentScan(
        basePackages = {"org.prgms.kdt.customer"}
)
static class Config {
    @Bean
    public DataSource dataSource() {
        var datasource = DataSourceBuilder.create()
                .url("jdbc:mysql://localhost:2215/test-order-mgmt")
                .username("test")
                .password("test1234!")
                .type(HikariDataSource.class)
                .build();


        return datasource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}
// 현재 @TestInstance(TestInstance.Lifecycle.PER_CLASS)이다.

@BeforeAll
void setUp() {
    var mysqlConfig = aMysqldConfig(v8_0_11)
            .withCharset(Charset.UTF8)
            .withPort(2215)
            .withUser("test", "test1234!")
            .withTimeZone("Asia/Seoul")
            .build();

	// embeded MySql실행
    embeddedMysql = anEmbeddedMysql(mysqlConfig)
            .addSchema("test-order-mgmt", classPathScript("schema.sql"))
            .start();

     // embeded 사용할 경우 deleteAll 필요 없다. db가 내렸가다 올라오며 데이터 모두 사라질 것이기 때문
}

@AfterAll
void cleanUp() {
	// embeded MySql 종료
    embeddedMysql.stop();
}

NamedParameterJdbcTemplate

파리미터를 map을 이용하여 전달한다. 파리미터 표시는 ?에서 :parameter_name로 변경 되었다.

private Map<String, Object> toParaMap(Customer customer) {
    return new HashMap<>(){{
        put("customerId", customer.getCustomerId().toString().getBytes());
        put("name", customer.getName());
        put("email", customer.getEmail());
        put("createdAt", Timestamp.valueOf(customer.getCreatedAt()));
        put("lastLoginAt", customer.getLastLoginAt() != null ? Timestamp.valueOf(customer.getCreatedAt()) : null);
    }};
}
  • CREATE
    update의 경우 create와 매우 유사하여 그냥 CREATE 코드를 참고하면 된다.
@Override
public Customer insert(Customer customer) {
    var update = jdbcTemplate.update(
    		"INSERT INTO customers(customer_id, name, email, created_at) values(UUID_TO_BIN(:customerId), :name, :email, :createdAt)"
            , toParaMap(customer));

    if(update != 1) {
        throw new RuntimeException("Nothing was inserted");
    }

    return customer;
}
  • READ
@Override
public int count() {
    return jdbcTemplate.queryForObject("select count(*) from customers", Collections.emptyMap(), Integer.class);
}

@Override
public List<Customer> findAll() {
    return jdbcTemplate.query("select * from customers", customerRowMapper);
}

@Override
public Optional<Customer> findById(UUID customerId) {
    try {
        return Optional.ofNullable(jdbcTemplate.queryForObject("select * from customers where customer_id = UUID_TO_BIN(:customerId)",
                Collections.singletonMap("customerId", customerId.toString().getBytes()),
                customerRowMapper));
    }catch(EmptyResultDataAccessException e) {
        logger.error("Got empty result", e);
        return Optional.empty();
    }
}
  • DELETE
@Override
public void deleteAll() {
    jdbcTemplate.update("DELETE FROM customers", Collections.emptyMap());
}

테스트 코드

테스트 코드에서 NamedParameterJdbcTemplate을 사용하기 위해서는 아래와 같이 @Configuration을 설정해야 한다.

@Configuration
@ComponentScan(
        basePackages = {"org.prgms.kdt.customer"}
)
static class Config {
    @Bean
    public DataSource dataSource() {
        var datasource = DataSourceBuilder.create()
                .url("jdbc:mysql://localhost:2215/test-order-mgmt")
                .username("test")
                .password("test1234!")
                .type(HikariDataSource.class)
                .build();

        return datasource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    @Bean
    public NamedParameterJdbcTemplate namedParameterJdbcTemplate(JdbcTemplate jdbcTemplate) {
        return new NamedParameterJdbcTemplate(jdbcTemplate);
    }
}

DataAccessException

SQLException

  • vendor code를 이용하여 vendor 별로 작성된 코드를 통해 어떠한 exception인지 파악을 해야하기 때문에 예외처리가 어렵다.
  • JDBC

DataAccessException

  • 보편적인 예외들을 추상화한 것(예외 타입화)
  • 예외를 수월하게 해준다
  • JDBC template

트랜잭션

  • 논리적 작업 단위
  • 데이터베이스 상호작용의 단위
  • 하나의 그룹으로 처리되어야 하는 명령문들을 모아 놓은 논리적인 단위이다.
  • 원자성(Automicity)
    • all or nothing
    • commit과 RollBack에 의해서 보장된다.
      • commit을 해야 변경된 데이터가 반영된다.
  • 일관성(Consistency)
    • 트랜잭션 처리 결과가 항상 일관성이 있어야 한다.
    • 성공적으로 수행된 트랜잭션은 정당한 데이터들만을 데이터베이스에 반영해야 한다.
  • 독립성(Isolation)
    • 여러 트랜잭션이 동시에 수행되더라도 각각의 트랜잭션은 다른 트랜잭션의 수행에 영향을 받지 않고 독립적으로 수행되어야 한다.
  • 지속성(Durability)
    • 트랜잭션이 성공적으로 완료되었을 경우, 결과가 영구적으로 반영되어야 한다.

forEach

만약 단순 가독성을 위해 forEach를 사용한다면 Collection의 forEach를 사용하도록 하자! 단순 가독성 향상을 위해 stream.forEach를 사용하는 경우는 stream()으로 생성된 stream 객체가 버려지는 오버헤드가 있기 때문이다.

exception

개발자가 구현한 로직에서 발생하는 것으로서 개발자가 예외에 따른 처리 방법을 명확히 알고 적용해야한다.

Checked Exception(Exception)

  • 예외처리 강제한다.
  • 컴파일 단계에서 확인한다.
  • 트랜잭션 처리에서 roll-back 하지 않음

Unchecked Exception(RuntimeException)

  • 예외처리를 강제하지 않음
  • 실행 단계
  • 트랜잭션 처리에서 roll-back을 한다.
profile
꾸준히 나아가자 🐢

0개의 댓글