Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1단계 - JDBC 라이브러리 구현하기] - 알파카(최휘용) 미션 제출합니다. #698

Merged
merged 10 commits into from
Oct 8, 2024
117 changes: 27 additions & 90 deletions app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
@@ -1,121 +1,58 @@
package com.techcourse.dao;

import com.techcourse.domain.User;
import com.interface21.jdbc.RowMapper;
import com.interface21.jdbc.core.JdbcTemplate;
import com.techcourse.domain.User;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

public class UserDao {

private static final Logger log = LoggerFactory.getLogger(UserDao.class);
private static final RowMapper<User> USER_ROW_MAPPER = (rs, rowNum) -> new User(
rs.getLong(1),
rs.getString(2),
rs.getString(3),
rs.getString(4)
);

private final DataSource dataSource;

public UserDao(final DataSource dataSource) {
this.dataSource = dataSource;
}
private final JdbcTemplate jdbcTemplate;

public UserDao(final JdbcTemplate jdbcTemplate) {
this.dataSource = null;
this.jdbcTemplate = jdbcTemplate;
}

public void insert(final User user) {
final var sql = "insert into users (account, password, email) values (?, ?, ?)";

Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = dataSource.getConnection();
pstmt = conn.prepareStatement(sql);

log.debug("query : {}", sql);
String sql = "insert into users (account, password, email) values (?, ?, ?)";

pstmt.setString(1, user.getAccount());
pstmt.setString(2, user.getPassword());
pstmt.setString(3, user.getEmail());
pstmt.executeUpdate();
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
} finally {
try {
if (pstmt != null) {
pstmt.close();
}
} catch (SQLException ignored) {}

try {
if (conn != null) {
conn.close();
}
} catch (SQLException ignored) {}
}
jdbcTemplate.update(sql, user.getAccount(), user.getPassword(), user.getEmail());
}

public void update(final User user) {
// todo
String sql = "update users set account = ?, password = ?, email = ?";

jdbcTemplate.update(sql, user.getAccount(), user.getPassword(), user.getEmail());
}

public List<User> findAll() {
// todo
return null;
String sql = "select id, account, password, email from users";
log.debug("query : {}", sql);

return jdbcTemplate.query(sql, USER_ROW_MAPPER);
}

public User findById(final Long id) {
final var sql = "select id, account, password, email from users where id = ?";

Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setLong(1, id);
rs = pstmt.executeQuery();
String sql = "select id, account, password, email from users where id = ?";
log.debug("query : {}", sql);

log.debug("query : {}", sql);

if (rs.next()) {
return new User(
rs.getLong(1),
rs.getString(2),
rs.getString(3),
rs.getString(4));
}
return null;
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
} finally {
try {
if (rs != null) {
rs.close();
}
} catch (SQLException ignored) {}

try {
if (pstmt != null) {
pstmt.close();
}
} catch (SQLException ignored) {}

try {
if (conn != null) {
conn.close();
}
} catch (SQLException ignored) {}
}
return jdbcTemplate.queryForObject(sql, USER_ROW_MAPPER, id);
}

public User findByAccount(final String account) {
// todo
return null;
String sql = "select id, account, password, email from users where account = ?";
log.debug("query : {}", sql);

return jdbcTemplate.queryForObject(sql, USER_ROW_MAPPER, account);
}
}
13 changes: 9 additions & 4 deletions app/src/test/java/com/techcourse/dao/UserDaoTest.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
package com.techcourse.dao;

import static org.assertj.core.api.Assertions.assertThat;

import com.interface21.jdbc.core.JdbcTemplate;
import com.techcourse.config.DataSourceConfig;
import com.techcourse.domain.User;
import com.techcourse.support.jdbc.init.DatabasePopulatorUtils;
import javax.sql.DataSource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class UserDaoTest {

private UserDao userDao;

@BeforeEach
void setup() {
DatabasePopulatorUtils.execute(DataSourceConfig.getInstance());
DataSource dataSource = DataSourceConfig.getInstance();
DatabasePopulatorUtils.execute(dataSource);
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

userDao = new UserDao(DataSourceConfig.getInstance());
userDao = new UserDao(jdbcTemplate);
jdbcTemplate.update("truncate table users restart identity");
final var user = new User("gugu", "password", "[email protected]");
userDao.insert(user);
}
Expand Down
10 changes: 5 additions & 5 deletions app/src/test/java/com/techcourse/service/UserServiceTest.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package com.techcourse.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

import com.interface21.dao.DataAccessException;
import com.interface21.jdbc.core.JdbcTemplate;
import com.techcourse.config.DataSourceConfig;
import com.techcourse.dao.UserDao;
import com.techcourse.dao.UserHistoryDao;
import com.techcourse.domain.User;
import com.techcourse.support.jdbc.init.DatabasePopulatorUtils;
import com.interface21.dao.DataAccessException;
import com.interface21.jdbc.core.JdbcTemplate;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

@Disabled
class UserServiceTest {

Expand Down
9 changes: 9 additions & 0 deletions jdbc/src/main/java/com/interface21/jdbc/RowMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.interface21.jdbc;

import java.sql.ResultSet;
import java.sql.SQLException;

public interface RowMapper<T> {

T mapRow(ResultSet rs, int rowNum) throws SQLException;
}
59 changes: 57 additions & 2 deletions jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package com.interface21.jdbc.core;

import com.interface21.dao.DataAccessException;
import com.interface21.jdbc.RowMapper;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;

public class JdbcTemplate {

private static final Logger log = LoggerFactory.getLogger(JdbcTemplate.class);
Expand All @@ -14,4 +21,52 @@ public class JdbcTemplate {
public JdbcTemplate(final DataSource dataSource) {
this.dataSource = dataSource;
}

public <T> List<T> query(String sql, RowMapper<T> rowMapper, Object... args) {
try (Connection conn = dataSource.getConnection();
PreparedStatement psmt = conn.prepareStatement(sql)
) {
setPreparedStatementParameters(args, psmt);
ResultSet rs = psmt.executeQuery();

return mapResultSetToList(rowMapper, rs);
} catch (SQLException e) {
throw new DataAccessException(e);
}
}

private <T> List<T> mapResultSetToList(RowMapper<T> rowMapper, ResultSet rs) throws SQLException {
List<T> result = new ArrayList<>();
while (rs.next()) {
T element = rowMapper.mapRow(rs, rs.getRow());
result.add(element);
}
return result;
}

private void setPreparedStatementParameters(Object[] args, PreparedStatement psmt) throws SQLException {
for (int i = 0; i < args.length; i++) {
psmt.setObject(i + 1, args[i]);
}
}

public <T> T queryForObject(String sql, RowMapper<T> rowMapper, Object... args) {
List<T> result = query(sql, rowMapper, args);
if (result.size() != 1) {
throw new DataAccessException("조회하려는 데이터가 여러 개입니다.");
}
return result.get(0);
}

public int update(String sql, Object... args) {
try (Connection conn = dataSource.getConnection();
PreparedStatement psmt = conn.prepareStatement(sql)
) {
setPreparedStatementParameters(args, psmt);

return psmt.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
94 changes: 94 additions & 0 deletions jdbc/src/test/java/com/interface21/jdbc/core/JdbcTemplateTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,99 @@
package com.interface21.jdbc.core;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;

import com.interface21.dao.DataAccessException;
import com.interface21.jdbc.RowMapper;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.List;
import javax.sql.DataSource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

class JdbcTemplateTest {
@Mock
private DataSource dataSource;
@Mock
private Connection connection;
@Mock
private PreparedStatement preparedStatement;
@Mock
private ResultSet resultSet;

private JdbcTemplate jdbcTemplate;

@BeforeEach
public void setUp() throws Exception {
MockitoAnnotations.openMocks(this);
when(dataSource.getConnection()).thenReturn(connection);
when(connection.prepareStatement(anyString())).thenReturn(preparedStatement);
jdbcTemplate = new JdbcTemplate(dataSource);
}

@DisplayName("여러 데이터를 조회하는 쿼리를 처리한다.")
@Test
public void query() throws Exception {
when(preparedStatement.executeQuery()).thenReturn(resultSet);
when(resultSet.next()).thenReturn(true, true, true, false);
when(resultSet.getString("name")).thenReturn("이은정", "클로버", "지니아");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오.... ㅋㅅㅋ


RowMapper<String> rowMapper = (rs, rowNum) -> rs.getString("name");

List<String> result = jdbcTemplate.query("select name from users", rowMapper);

assertAll(
() -> assertThat(result.size())
.isEqualTo(3),
() -> assertThat(result)
.isEqualTo(List.of("이은정", "클로버", "지니아"))
);
}

@DisplayName("데이터를 업데이트하는 쿼리를 처리한다.")
@Test
public void update() throws Exception {
when(preparedStatement.executeUpdate()).thenReturn(1);

int rowsAffected = jdbcTemplate.update("update users set name = ? where id = ?", "클로버지니아", 1);

assertThat(rowsAffected)
.isEqualTo(1);
}

@DisplayName("한 개의 데이터를 조회하는 쿼리를 처리한다.")
@Test
public void queryForObject() throws Exception {
when(preparedStatement.executeQuery()).thenReturn(resultSet);
when(resultSet.next()).thenReturn(true, false);
when(resultSet.getString("name")).thenReturn("킹로버");

RowMapper<String> rowMapper = (rs, rowNum) -> rs.getString("name");

String result = jdbcTemplate.queryForObject("select name from users where id = ?", rowMapper, 1);

assertThat(result)
.isEqualTo("킹로버");
}

@DisplayName("조회하려는 데이터가 여러 개일 경우 예외로 처리한다.")
@Test
public void queryForObjectFailedWithMultipleResults() throws Exception {
when(preparedStatement.executeQuery()).thenReturn(resultSet);
when(resultSet.next()).thenReturn(true, true, false);

RowMapper<String> rowMapper = (rs, rowNum) -> rs.getString("name");

assertThatThrownBy(() -> jdbcTemplate.queryForObject("select name from users where id = ?", rowMapper, 1))
.isInstanceOf(DataAccessException.class)
.hasMessage("조회하려는 데이터가 여러 개입니다.");
}
}
4 changes: 2 additions & 2 deletions study/src/main/resources/schema.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# mysql 8.0.30부터는 statement.execute()으로 여러 쿼리를 한 번에 실행할 수 없다.
# 멀티 쿼리 옵션을 url로 전달하도록 수정하는 방법을 찾아서 적용하자.
-- mysql 8.0.30부터는 statement.execute()으로 여러 쿼리를 한 번에 실행할 수 없다.
-- 멀티 쿼리 옵션을 url로 전달하도록 수정하는 방법을 찾아서 적용하자.
CREATE TABLE IF NOT EXISTS users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
account VARCHAR(100) NOT NULL,
Expand Down
Loading