SpringBoot #2.3 JDBC - DataSource & JdbcTemplate

텐저린티·2023년 6월 28일
0

데브코스

목록 보기
16/41

DataSource

  • DataBase Connection Pool (DBCP) 만들고 반환하는 녀석

- 커넥션이 미리 저장되어있는 풀에서 DataSource가 커넥션을 가져와서 시작
- 커넥션 사용 후 커넥션을 제거하는 것이 아니라, 다시 풀에 반환
// DI
private final DataSource dataSource;

try (
    var connection = dataSource.getConnection();
    var statement = connection.createStatement();
    var resultSet = statement.executeQuery("select * from customers");
) {

SimpleDriverDataSource

  • SpringBoot에서 제공하는 아주 간단한 DataSource
  • 이거는 풀에서 가져오고 반환하는게 아니라 직접 생성하고 제거하는 녀석
  • 테스트용으로 많이 씀
  • gradle
    • implementation "org.springframework.boot:spring-boot-starter-jdbc"

HikariCP

  • Brett Wooldridge가 개발한 매우 가볍고 빠른 JDBC 커넥션 풀
  • 한 번에 100개의 커넥션(스레드)을 풀에 만들어 둠
@SpringJUnitConfig
class CustomerJdbcRepositoryTest {

    @Configuration
    @ComponentScan(
            basePackages = {"com.devcourse.springorder.customer"}
    )
    static class Config {
        @Bean
        public DataSource dataSource() {
            var dataSource = DataSourceBuilder.create()
                    .url("jdbc:mysql://localhost/order_mgmt")
                    .username("root")
                    .password("root1234!")
                    .type(HikariDataSource.class)
                    .build();
            // HikariCP가 풀에 커넥션(스레드)를 100개 만들어두고 시작한다는 것을 확인하기 위한 코드
            // Customer show status '%Threads%' 쿼리랑 함께 써서 확인할 수 있다.
            dataSource.setMaximumPoolSize(1000);
            dataSource.setMinimumIdle(100);
            return dataSource;
        }
    }

    // 위에 Configuration으로 등록해놨으므로 이제 Autowired 사용 가능 -> ComponentScan을 해주기 때문
    @Autowired
    CustomerJdbcRepository customerJdbcRepository;

    @Autowired
    DataSource dataSource;
}

Jdbc Template

  • Jdbc connection 연결 부분이 계속 중복됨.
  • 이걸 JdbcTemplate 가 해소
@Override
public Optional<Customer> findByName(String name) {
	List<Customer> list = new ArrayList<>();
	try (
		var connection = dataSource.getConnection();
		var statement = connection.prepareStatement(쿼리);
	) {
		...
	} catch (SQLException throwable) {
		logger.error("Got error", e);
		throw new RuntimeError(e);
	}
}

→ 이 공통부분을 템플릿이 제공

private final DataSource dataSource;
private final JdbcTemplate jdbcTemplate;

private static final RowMapper<Customer> customerRowMapper = (resultSet, rowNum) -> {
    var customerId = toUUID(resultSet.getBytes("customer_id"));
    var customerName = resultSet.getString("name");
    var email = resultSet.getString("email");
    var lastLoginAt = resultSet.getTimestamp("last_login_at") != null ?
            resultSet.getTimestamp("last_login_at").toLocalDateTime() : null;
    var createdAt = resultSet.getTimestamp("created_at").toLocalDateTime();
    return new Customer(customerId, customerName, email, lastLoginAt, createdAt);
};

public CustomerJdbcRepository(DataSource dataSource, JdbcTemplate jdbcTemplate) {
    this.dataSource = dataSource;
    this.jdbcTemplate = jdbcTemplate;
}

@Override
public List<Customer> findAll() {
    return jdbcTemplate.query(SELECT_ALL_SQL, customerRowMapper);
}
  • 코드량이 극적으로 낮아진다.
  • 대신 테스트할 때 JdbcTemplate Bean을 ComponentScan 할 수 있도록 Configuration에 추가해줘함.
  • query : 여러 줄을 받는 경우
  • queryForObject : 하나를 받는 경우
  • update : select 이외의 DML 쿼리 실행

이건 JdbcTemplate 이용해서 만든 DML

@Override
public Customer insert(Customer customer) {
    var update = jdbcTemplate.update(INSERT_SQL,
            customer.getCustomerId().toString().getBytes(),
            customer.getName(),
            customer.getEmail(),
            Timestamp.valueOf(customer.getCreateAt())
    );
    if (update != 1) {
        throw new RuntimeException("Nothing was inserted");
    }
    return customer;
}

@Override
public Customer update(Customer customer) {
    var update = jdbcTemplate.update(UPDATE_NAME_BY_ID,
            customer.getName(),
            customer.getEmail(),
            customer.getLastLoginAt() != null ? Timestamp.valueOf(customer.getLastLoginAt()) : null,
            customer.getCustomerId().toString().getBytes()
    );
    if (update != 1) {
        throw new RuntimeException("Nothing was updated");
    }
    return customer;
}

@Override
public List<Customer> findAll() {
    return jdbcTemplate.query(SELECT_ALL_SQL, customerRowMapper);
}

@Override
public Optional<Customer> findById(UUID customerId) {
    try {
        return Optional.ofNullable(jdbcTemplate.queryForObject(SELECT_BY_ID_SQL, customerRowMapper, customerId.toString()));
    } catch (EmptyResultDataAccessException e) {
        logger.error("Got empty result", e);
        return Optional.empty();
    }
}

@Override
public Optional<Customer> findByName(String name) {
    try {
        return Optional.ofNullable(jdbcTemplate.queryForObject(SELECT_BY_NAME_SQL, customerRowMapper, name));
    } catch (EmptyResultDataAccessException e) {
        logger.error("Got empty result", e);
        return Optional.empty();
    }
}

@Override
public Optional<Customer> findByEmail(String email) {
    try {
        return Optional.ofNullable(jdbcTemplate.queryForObject(SELECT_BY_EMAIL_SQL, customerRowMapper, email));
    } catch (EmptyResultDataAccessException e) {
        logger.error("Got empty result", e);
        return Optional.empty();
    }
}

@Override
public void deleteAll() {
    jdbcTemplate.update(DELETE_ALL_SQL);
}

@Override
public int count() {
    return jdbcTemplate.queryForObject(SELECT_COUNT_ALL, Integer.class);
}
  • 테스트 코드에서 DI
@Configuration
@ComponentScan(
        basePackages = {"com.devcourse.springorder.customer"}
)
static class Config {
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}
profile
개발하고 말테야

0개의 댓글

관련 채용 정보