Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.interface21.jdbc.core.JdbcTemplate;
import com.techcourse.domain.User;
import java.sql.Connection;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -17,27 +18,43 @@ public UserDao(final JdbcTemplate jdbcTemplate) {
}

public void insert(final User user) {
final var sql = """
final String sql = """
INSERT INTO users (account, password, email)
VALUES (?, ?, ?)
""";
log.debug("Inserting user: {}", user);
jdbcTemplate.insert(sql, user.getAccount(), user.getPassword(), user.getEmail());
}

public void update(final User user) {
final var sql = """
final String sql = """
UPDATE users
SET password = ?
WHERE id = ?
""";
log.debug("Updating user: {}", user);
jdbcTemplate.update(sql, user.getPassword(), user.getId());
}

public void update(
final Connection connection,
final User user
) {
final String sql = """
UPDATE users
SET password = ?
WHERE id = ?
""";
log.debug("Updating user: {}", user);
jdbcTemplate.update(connection, sql, user.getPassword(), user.getId());
}

public List<User> findAll() {
final var sql = """
final String sql = """
SELECT id, account, password, email
FROM users
""";
log.debug("Finding all users");
return jdbcTemplate.findAll(sql, (rs) -> new User(
rs.getLong(1),
rs.getString(2),
Expand All @@ -47,11 +64,12 @@ public List<User> findAll() {
}

public User findById(final Long id) {
final var sql = """
final String sql = """
SELECT id, account, password, email
FROM users
WHERE id = ?
""";
log.debug("Finding user by id: {}", id);
return jdbcTemplate.queryForObject(sql, (rs) -> new User(
rs.getLong(1),
rs.getString(2),
Expand All @@ -61,11 +79,12 @@ public User findById(final Long id) {
}

public User findByAccount(final String account) {
final var sql = """
final String sql = """
SELECT id, account, password, email
FROM users
WHERE account = ?
""";
log.debug("Finding user by account: {}", account);
return jdbcTemplate.queryForObject(sql, (rs) -> new User(
rs.getLong("id"),
rs.getString("account"),
Expand Down
79 changes: 35 additions & 44 deletions app/src/main/java/com/techcourse/dao/UserHistoryDao.java
Original file line number Diff line number Diff line change
@@ -1,62 +1,53 @@
package com.techcourse.dao;

import com.techcourse.domain.UserHistory;
import com.interface21.jdbc.core.JdbcTemplate;
import com.techcourse.domain.UserHistory;
import java.sql.Connection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class UserHistoryDao {

private static final Logger log = LoggerFactory.getLogger(UserHistoryDao.class);

private final DataSource dataSource;

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

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

public void log(final UserHistory userHistory) {
final var sql = "insert into user_history (user_id, account, password, email, created_at, created_by) values (?, ?, ?, ?, ?, ?)";

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

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

pstmt.setLong(1, userHistory.getUserId());
pstmt.setString(2, userHistory.getAccount());
pstmt.setString(3, userHistory.getPassword());
pstmt.setString(4, userHistory.getEmail());
pstmt.setObject(5, userHistory.getCreatedAt());
pstmt.setString(6, userHistory.getCreateBy());
pstmt.executeUpdate();
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
} finally {
try {
if (pstmt != null) {
pstmt.close();
}
} catch (SQLException ignored) {}
final var sql = """
INSERT INTO user_history (user_id, account, password, email, created_at, created_by)
VALUES (?, ?, ?, ?, ?, ?)
""";
log.debug("User history log: {}", userHistory);
jdbcTemplate.update(sql,
userHistory.getUserId(),
userHistory.getAccount(),
userHistory.getPassword(),
userHistory.getEmail(),
userHistory.getCreatedAt(),
userHistory.getCreateBy()
);
}

try {
if (conn != null) {
conn.close();
}
} catch (SQLException ignored) {}
}
public void log(
final Connection connection,
final UserHistory userHistory
) {
final String sql = """
INSERT INTO user_history (user_id, account, password, email, created_at, created_by)
VALUES (?, ?, ?, ?, ?, ?)
""";
log.debug("User history log: {}", userHistory);
jdbcTemplate.update(connection, sql,
userHistory.getUserId(),
userHistory.getAccount(),
userHistory.getPassword(),
userHistory.getEmail(),
userHistory.getCreatedAt(),
userHistory.getCreateBy()
);
}
}
37 changes: 31 additions & 6 deletions app/src/main/java/com/techcourse/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
package com.techcourse.service;

import com.interface21.dao.DataAccessException;
import com.techcourse.dao.UserDao;
import com.techcourse.dao.UserHistoryDao;
import com.techcourse.domain.User;
import com.techcourse.domain.UserHistory;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;

public class UserService {
public final class UserService {

private final UserDao userDao;
private final UserHistoryDao userHistoryDao;
private final DataSource dataSource;

public UserService(final UserDao userDao, final UserHistoryDao userHistoryDao) {
public UserService(
final UserDao userDao,
final UserHistoryDao userHistoryDao,
final DataSource dataSource
) {
this.userDao = userDao;
this.userHistoryDao = userHistoryDao;
this.dataSource = dataSource;
}

public User findById(final long id) {
Expand All @@ -23,10 +33,25 @@ public void insert(final User user) {
userDao.insert(user);
}

public void changePassword(final long id, final String newPassword, final String createBy) {
final var user = findById(id);
public void changePassword(
final long id,
final String newPassword,
final String createBy
) {
final User user = findById(id);
user.changePassword(newPassword);
userDao.update(user);
userHistoryDao.log(new UserHistory(user, createBy));
try (final Connection connection = dataSource.getConnection()) {
connection.setAutoCommit(false);
try {
userDao.update(connection, user);
userHistoryDao.log(connection, new UserHistory(user, createBy));
connection.commit();
} catch (final Exception e) {
connection.rollback();
throw new DataAccessException("비밀번호 변경 중 오류가 발생했습니다.", e);
}
} catch (final SQLException e) {
throw new DataAccessException("데이터베이스 연결 중 오류가 발생했습니다.", e);
}
}
}
12 changes: 8 additions & 4 deletions app/src/test/java/com/techcourse/service/MockUserHistoryDao.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package com.techcourse.service;

import com.techcourse.dao.UserHistoryDao;
import com.techcourse.domain.UserHistory;
import com.interface21.dao.DataAccessException;
import com.interface21.jdbc.core.JdbcTemplate;
import com.techcourse.dao.UserHistoryDao;
import com.techcourse.domain.UserHistory;
import java.sql.Connection;

public class MockUserHistoryDao extends UserHistoryDao {

Expand All @@ -12,7 +13,10 @@ public MockUserHistoryDao(final JdbcTemplate jdbcTemplate) {
}

@Override
public void log(final UserHistory userHistory) {
throw new DataAccessException();
public void log(
final Connection connection,
final UserHistory userHistory
) {
throw new DataAccessException("Mock exception for transaction test");
}
}
16 changes: 7 additions & 9 deletions app/src/test/java/com/techcourse/service/UserServiceTest.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
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 {

private JdbcTemplate jdbcTemplate;
Expand All @@ -33,7 +31,7 @@ void setUp() {
@Test
void testChangePassword() {
final var userHistoryDao = new UserHistoryDao(jdbcTemplate);
final var userService = new UserService(userDao, userHistoryDao);
final var userService = new UserService(userDao, userHistoryDao, DataSourceConfig.getInstance());

final var newPassword = "qqqqq";
final var createBy = "gugu";
Expand All @@ -48,7 +46,7 @@ void testChangePassword() {
void testTransactionRollback() {
// 트랜잭션 롤백 테스트를 위해 mock으로 교체
final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate);
final var userService = new UserService(userDao, userHistoryDao);
final var userService = new UserService(userDao, userHistoryDao, DataSourceConfig.getInstance());

final var newPassword = "newPassword";
final var createBy = "gugu";
Expand Down
26 changes: 26 additions & 0 deletions jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ public int update(
return executeUpdate(sql, args);
}

public int update(
final Connection connection,
final String sql,
final Object... args
) {
try (final PreparedStatement ps = createPreparedStatement(connection, sql, args)) {
return ps.executeUpdate();
} catch (final SQLException e) {
log.error("데이터베이스 처리 중 예외 발생", e);
throw new DataAccessException(e);
}
}

public <T> List<T> findAll(
final String sql,
final RowMapper<T> rowMapper,
Expand Down Expand Up @@ -103,6 +116,7 @@ private PreparedStatement createPreparedStatement(
final String sql,
final Object[] args
) throws SQLException {
validateParameterCount(sql, args);
final PreparedStatement ps = con.prepareStatement(sql);
log.debug("query : {}", sql);
log.trace("parameters : {}", Arrays.toString(args));
Expand All @@ -111,4 +125,16 @@ private PreparedStatement createPreparedStatement(
}
return ps;
}

private void validateParameterCount(
final String sql,
final Object[] args
) {
final long placeholderCount = sql.chars()
.filter(ch -> ch == '?')
.count();
if (placeholderCount != args.length) {
throw new DataAccessException("SQL 파라미터 개수 불일치: ? %d개, 인자 %d개".formatted(placeholderCount, args.length));
}
}
}
29 changes: 29 additions & 0 deletions jdbc/src/test/java/com/interface21/jdbc/core/JdbcTemplateTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,33 @@ void throwDataAccessExceptionOnSqlException() throws SQLException {
assertThatThrownBy(() -> jdbcTemplate.findAll(sql, userRowMapper))
.isInstanceOf(DataAccessException.class);
}

@DisplayName("SQL의 파라미터 개수와 인자의 개수가 다르면 예외를 던진다 - 인자가 부족할 때")
@Test
void validateParameterCountWithNotEnoughArgs() {
// given
final String sql = "UPDATE users SET password = ?, email = ? WHERE account = ?"; // 3 params
final String newPassword = "new_password";
final String account = "gugu"; // 2 args

// when & then
assertThatThrownBy(() -> jdbcTemplate.update(sql, newPassword, account))
.isInstanceOf(DataAccessException.class)
.hasMessage("SQL 파라미터 개수 불일치: ? 3개, 인자 2개");
}

@DisplayName("SQL의 파라미터 개수와 인자의 개수가 다르면 예외를 던진다 - 인자가 많을 때")
@Test
void validateParameterCountWithTooManyArgs() {
// given
final String sql = "UPDATE users SET password = ? WHERE account = ?"; // 2 params
final String newPassword = "new_password";
final String account = "gugu";
final String extraArg = "extra"; // 3 args

// when & then
assertThatThrownBy(() -> jdbcTemplate.update(sql, newPassword, account, extraArg))
.isInstanceOf(DataAccessException.class)
.hasMessage("SQL 파라미터 개수 불일치: ? 2개, 인자 3개");
}
}
2 changes: 1 addition & 1 deletion study/src/test/java/connectionpool/stage0/Stage0Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ void driverManager() throws SQLException {
* 구현체는 각 vendor에서 제공한다.
* 테스트 코드의 JdbcDataSource 클래스는 h2에서 제공하는 클래스다.
*
* DirverManager가 아닌 DataSource를 사용하는 이유
* DriverManager가 아닌 DataSource를 사용하는 이유
* - 애플리케이션 코드를 직접 수정하지 않고 properties로 디비 연결을 변경할 수 있다.
* - 커넥션 풀링(Connection pooling) 또는 분산 트랜잭션은 DataSource를 통해서 사용 가능하다.
*
Expand Down