자바 어플리케이션에서 DataBase와 Connection을 맺고, 객체를 관리하기 위한 DataBase Pool이다.
JDBC(Java DataBase Connectivity) : 자바 어플리케이션과 DataBase를 연결하기 위한 인터페이스로 아래와 같은 프로세스를 실행한다.
1) DriverManager를 통한 DataBase Connection 객체 생성
2) Connection 객체에서 Statement 객체 생성 및 쿼리 실행
3) ResultSet 결과 처리
4) Connection Close
JDBC는 DataBase를 연결할때마다 1~4번 과정을 실행하는데, 1번과 4번 수행하는 것은 오랜 시간을 필요로 하게된다.
-> DBCP는 이러한 부분을 개선한것이라고 보면된다. 한번 DataBase Connection 객체를 생성했으면 DataBase Connection 객체를 Close 하지않고 DataBase Connection Pool에서 오픈된 상태의 Connection을 가지고 있다가, Connection이 필요할 때 할당을해주고, 작업이 끝나면 Connection을 반환받아서 Connection Pool에서 관리를한다.
스프링 부트 2.0 전에는 톰캣에서 제공해준 DBCP(아파치 DBCP랑 동일)를 사용했고, 스프링 부트 2.0 이후부터는 HikariCP 를 이용한다.
//jdbc dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<scope>test</scope>
</dependency>
Connection Pool 이 실제로 어떻게 사용되는지 확인해보자
DataSource를 통해 HikariConnectionPool을 연결하고, 실제로 필요할때만 Connection Pool에서 객체를 가져다 쓰는지 확인해보자.
show status like '%Threads%';
다음 명령어를 통해서 DataBase Threads와 관련된 상태변수를 확인할 수 있다. 6번째줄을 보면 Threads_connected가 1개 연결된걸 확인할 수 있다.
아래 테스트를 실행시키고 다시 show status like '%Threads%'; 를 실행시켜보면 Threads_connected 개수가 늘어났다가, 테스트가 끝나면 Threads_connected 개수가 1개로 바뀌는걸 확인할 수 있는데, 작업이 끝나서 Connection Pool에 반환했다고 볼 수 있다.
@SpringJUnitConfig
class CustomerRepositoryTest {
@Configuration
@ComponentScan(
basePackages = {"org.prgrms.kdt"}
)
static class Config {
@Bean
public DataSource dataSource() {
return DataSourceBuilder.create()
.url(dbInfo)
.username(userInfo)
.password(password)
.type(HikariDataSource.class)
.build();
}
}
@Autowired
CustomerRepository customerRepository;
@Autowired
DataSource dataSource;
@Test
public void testHikariConnectionPool() {
assertThat(dataSource.getClass().getName(), is("com.zaxxer.hikari.HikariDataSource"));
}
@Test
@DisplayName("전체 고객을 조회할 수 있다.")
public void testFindAll() {
var customers = regularCustomerRepository.findAll();
assertThat(customers.isEmpty(), is(false));
}
}
dataSource.setMaximumPoolSize(1000);
dataSource.setMinimumIdle(100);
TestInstance Lifecycle
테스트가 실행되기전에 1번 실행할 수 있도록 하는 어노테이션으로 아래와 같이 static 메서드여야 한다.
@BeforeAll
static void clean() {
}
그런데! clean() 메서드에서 non-static 메서드를 참조하려면 어떻게 해야할까?
Junit 에서 제공하는 @TestInstance() 어노테이션을 활용하면 된다.
테스트 메서드의 순서를 지정하고 싶으면 @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 와 @Order(1)를 이용하면 된다.
@SpringJUnitConfig
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class RegularCustomerRepositoryTest {
...
@BeforeAll
void setUp() {
...
}
@Test
@Order(1)
public void testHikariConnectionPool() {
...
}
@Test
@Order(2)
@DisplayName("고객을 추가할 수 있다.")
public void testInsert() {
...
}
}
테스트할 때 메서드의 실행 순서를 정하고 싶으면 @Order(3) 과 같이 설정하면 된다.
JDBC Template 은 전통방식으로 구현하는 JDBC로직을 간결하게 처리할 수 있는 Spring API이다. 반복되는 부분을 Template으로 만들고, 변경이 되는 코드를 Callback을 이용해서 JDBC Template으로 제공하고 있다.
아래 이미지는 JDBC Template 적용 전 예시 코드인데, 코드를 짜다보면 표시해놓은 부분이 중복되는 경우가 많다. 이를 JDBC Template을 이용하면 중복코드를 줄이고 간결하게 코드를 작성할 수 있다.
JDBC Template은 기본적으로 DataSource만 있으면 쓸 수 있고, jdbcTemplate.query(sql, rowMapper)로 사용할 수 있다.
JDBC Template을 사용하려면 아래와 같이 빈으로 등록해주고, DataSourece를 주입받아야 한다.
public class RegularCustomerRepository implements CustomerRepository{
private final DataSource dataSource;
private final JdbcTemplate jdbcTemplate;
private static final RowMapper<Customer> customerRowMapper = (resultSet, i) -> {
String customerName = resultSet.getString("name");
String email = resultSet.getString("email");
UUID customerId = toUUID(resultSet);
LocalDateTime lastLoginAt = resultSet.getTimestamp("last_login_at") != null ?
resultSet.getTimestamp("last_login_at").toLocalDateTime() : null;
LocalDateTime createdAt = resultSet.getTimestamp("created_at").toLocalDateTime();
return new RegularCustomer(customerId, customerName, email, lastLoginAt, createdAt);
};
public RegularCustomerRepository(DataSource dataSource, JdbcTemplate jdbcTemplate) {
this.dataSource = dataSource;
this.jdbcTemplate = jdbcTemplate;
}
@Override
public Optional<Customer> findByName(String name) {
try {
return Optional.ofNullable(jdbcTemplate.queryForObject("SELECT * FROM customers WHERE name = ?",
customerRowMapper,
name));
} catch (EmptyResultDataAccessException e) {
logger.error("Got empty result", e);
return Optional.empty();
}
}
}
class RegularCustomerRepositoryTest {
@Configuration
@ComponentScan(
basePackages = {"org.prgrms.kdt"}
)
static class Config {
@Bean
public DataSource dataSource() {
return DataSourceBuilder.create()
.url(dbInfo)
.username(user)
.password(password)
.type(HikariDataSource.class)
.build();
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource;
}
}
leetcode complex-number-multiplication
class Solution {
public String complexNumberMultiply(String num1, String num2) {
// (a+bi)(c+di) => ac + adi + bci +(-bd) => ac + (ad + bc)i +(-bd)
String[] s1 = num1.split("\\+");
String[] s2 = num2.split("\\+");
int a = Integer.parseInt(s1[0]);
int b = Integer.parseInt(s1[1].replace("i", ""));
int c = Integer.parseInt(s2[0]);
int d = Integer.parseInt(s2[1].replace("i", ""));
String real = Integer.toString(a*c+(-1*b*d));
String imaginary = Integer.toString(a*d+b*c);
StringBuilder sb = new StringBuilder();
sb.append(real);
sb.append("+");
sb.append(imaginary);
sb.append("i");
return sb.toString();
}
}
알고리즘 자체는 논의할 내용이 없었는데, 출력 결과를 어떻게 하냐에 따라서 실행속도 차이가 많이났다.
String + 연산자를 쓰는건 새로운 String 인스턴스를 만들어 내기때문에 느린걸 알고있었는데, String.format(), String split() 이 느린건 처음알게 됐다. 이 친구들은 regex를 이용하는데 regex가 느리다고 한다.
regex가 느린 이유
동섭님이 공유해주신 String Performance
재원님이 공유해주신 split vs StringTokenizer
연수님이 공유해주신 각종 String 비교 자료
08.25.(수) W4D1, W4D2 학습 내용 발표 범위
- 소프트웨어 테스팅 - 효희님
- 단위테스트 - 효희님
- 통합테스트 - 효희님
- JUnit - 재원님
- JUnit 실습 - 재원님
- Mock Object - 나
- 유닛테스트 실습 - 나
- Spring Test - 연수님
- Spring Test 실습 - 연수님
- JDBC 알아보기 - 동섭님
- JDBC 준비하기 - 동섭님