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

[JDBC 라이브러리 구현하기 - 3단계] 도이(유도영) 미션 제출합니다. #463

Merged
merged 6 commits into from
Oct 8, 2023
Merged
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
47 changes: 37 additions & 10 deletions app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.techcourse.dao;

import com.techcourse.domain.User;
import java.sql.Connection;
import java.util.List;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.TransactionManager;

public class UserDao {

Expand All @@ -14,24 +16,45 @@ public class UserDao {
resultSet.getString(3),
resultSet.getString(4)
);

private final TransactionManager transactionManager;
private final JdbcTemplate jdbcTemplate;

public UserDao(JdbcTemplate jdbcTemplate) {
public UserDao(final TransactionManager transactionManager, final JdbcTemplate jdbcTemplate) {
this.transactionManager = transactionManager;
this.jdbcTemplate = jdbcTemplate;
}

public void insert(final User user) {
final var sql = "insert into users (account, password, email) values (?, ?, ?)";
jdbcTemplate.executeUpdate(sql,
user.getAccount(),
user.getPassword(),
user.getEmail()
);
transactionManager.save(
(connection, entity) -> jdbcTemplate.executeUpdate(
connection,
sql,
user.getAccount(),
user.getPassword(),
user.getEmail()
), user);
}

public void update(final User user) {
final var sql = "update users set account = ?, password = ?, email = ? where id = ?";
jdbcTemplate.executeUpdate(sql,
transactionManager.save(
(connection, entity) -> jdbcTemplate.executeUpdate(
connection,
sql,
user.getAccount(),
user.getPassword(),
user.getEmail(),
user.getId()
), user);
}

public void update(final Connection connection, final User user) {
final var sql = "update users set account = ?, password = ?, email = ? where id = ?";
jdbcTemplate.executeUpdate(
connection,
sql,
user.getAccount(),
user.getPassword(),
user.getEmail(),
Expand All @@ -41,16 +64,20 @@ public void update(final User user) {

public List<User> findAll() {
final var sql = "select id, account, password, email from users";
return jdbcTemplate.executeQueryForList(sql, USER_ROW_MAPPER);
return transactionManager.find((connection, parameters) ->
jdbcTemplate.executeQueryForList(connection, sql, USER_ROW_MAPPER));
}

public User findById(final Long id) {
final var sql = "select id, account, password, email from users where id = ?";
return jdbcTemplate.executeQueryForObject(sql, USER_ROW_MAPPER, id);
return transactionManager.find(
(connection, parameters) ->
jdbcTemplate.executeQueryForObject(connection, sql, USER_ROW_MAPPER, parameters), id);
}

public User findByAccount(final String account) {
final var sql = "select id, account, password, email from users where account = ?";
return jdbcTemplate.executeQueryForObject(sql, USER_ROW_MAPPER, account);
return transactionManager.find((connection, parameters) ->
jdbcTemplate.executeQueryForObject(connection, sql, USER_ROW_MAPPER, parameters), account);
}
}
26 changes: 24 additions & 2 deletions app/src/main/java/com/techcourse/dao/UserHistoryDao.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,46 @@
package com.techcourse.dao;

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

public class UserHistoryDao {

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

private final TransactionManager transactionManager;
private final JdbcTemplate jdbcTemplate;

public UserHistoryDao(JdbcTemplate jdbcTemplate) {
public UserHistoryDao(final TransactionManager transactionManager, final JdbcTemplate jdbcTemplate) {
this.transactionManager = transactionManager;
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 (?, ?, ?, ?, ?, ?)";
log.info("query: {}", sql);
jdbcTemplate.executeUpdate(sql,
transactionManager.save(
(connection, entity) -> jdbcTemplate.executeUpdate(
connection,
sql,
userHistory.getUserId(),
userHistory.getAccount(),
userHistory.getPassword(),
userHistory.getEmail(),
userHistory.getCreatedAt(),
userHistory.getCreateBy()
), userHistory);
}

public void log(final Connection connection, final UserHistory userHistory) {
final var sql = "insert into user_history (user_id, account, password, email, created_at, created_by) values (?, ?, ?, ?, ?, ?)";
log.info("query: {}", sql);
jdbcTemplate.executeUpdate(
connection,
sql,
userHistory.getUserId(),
userHistory.getAccount(),
userHistory.getPassword(),
Expand Down
16 changes: 13 additions & 3 deletions app/src/main/java/com/techcourse/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@
import com.techcourse.dao.UserHistoryDao;
import com.techcourse.domain.User;
import com.techcourse.domain.UserHistory;
import org.springframework.jdbc.core.TransactionManager;

public class UserService {

private final TransactionManager transactionManager;
private final UserDao userDao;
private final UserHistoryDao userHistoryDao;

public UserService(final UserDao userDao, final UserHistoryDao userHistoryDao) {
public UserService(
final TransactionManager transactionManager,
final UserDao userDao,
final UserHistoryDao userHistoryDao) {
this.transactionManager = transactionManager;
this.userDao = userDao;
this.userHistoryDao = userHistoryDao;
}
Expand All @@ -26,7 +32,11 @@ public void insert(final User user) {
public void changePassword(final long id, final String newPassword, final String createBy) {
final var user = findById(id);
user.changePassword(newPassword);
userDao.update(user);
userHistoryDao.log(new UserHistory(user, createBy));
transactionManager.save(
Copy link

Choose a reason for hiding this comment

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

트랜젝션 메니저를 통해 Dao 메서드에 트랜젝션을 구현하셨군요! 기존 API에 큰 변경 없이 잘 설계 해주셨네요! 감동입니다 ㅜㅜ 저는 이부분 고민 많이했거든요.. 도이처럼 JDBCTemplate API 명세를 보니 Callback 함수형 인터페이스를 사용해서 저도 적극 활용을 해봤는데 Transaction이 들어가고 특히나 두 메서드를 동시에 트랜젝션 거는 작업이 어려웠던 것 같습니다.. 제 말이 길었네요! 트랜젝션 메서드로 일관되게 트랜젝션 걸어주셔서 잘하신것 같습니다!

(connection, entity) -> {
userDao.update(connection, entity);
userHistoryDao.log(connection, new UserHistory(entity, createBy));
},
user);
}
}
3 changes: 3 additions & 0 deletions app/src/main/resources/schema.sql
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
drop table if exists users;
drop table if exists user_history;

create table if not exists users (
id bigint auto_increment,
account varchar(100) not null,
Expand Down
6 changes: 3 additions & 3 deletions app/src/test/java/com/techcourse/dao/UserDaoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@

import com.techcourse.config.DataSourceConfig;
import com.techcourse.domain.User;
import com.techcourse.support.jdbc.init.PooledDataSourceConnectionManager;
import com.techcourse.support.jdbc.init.DatabasePopulatorUtils;
import com.techcourse.support.jdbc.init.PooledDataSourceConnectionManager;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.TransactionManager;

class UserDaoTest {

Expand All @@ -17,8 +18,7 @@ class UserDaoTest {
@BeforeEach
void setup() {
DatabasePopulatorUtils.execute(DataSourceConfig.getInstance());

userDao = new UserDao(new JdbcTemplate(new PooledDataSourceConnectionManager()));
userDao = new UserDao(new TransactionManager(new PooledDataSourceConnectionManager()), new JdbcTemplate());
final var user = new User("gugu", "password", "[email protected]");
userDao.insert(user);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@

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

public class MockUserHistoryDao extends UserHistoryDao {

public MockUserHistoryDao(final JdbcTemplate jdbcTemplate) {
super(jdbcTemplate);
public MockUserHistoryDao(final TransactionManager transactionManager, final JdbcTemplate jdbcTemplate) {
super(transactionManager, jdbcTemplate);
}

@Override
public void log(final UserHistory userHistory) {
public void log(final Connection connection, final UserHistory userHistory) {
throw new DataAccessException();
}
}
19 changes: 10 additions & 9 deletions app/src/test/java/com/techcourse/service/UserServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,25 @@
import com.techcourse.dao.UserDao;
import com.techcourse.dao.UserHistoryDao;
import com.techcourse.domain.User;
import com.techcourse.support.jdbc.init.PooledDataSourceConnectionManager;
import com.techcourse.support.jdbc.init.DatabasePopulatorUtils;
import com.techcourse.support.jdbc.init.PooledDataSourceConnectionManager;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.TransactionManager;

@Disabled
class UserServiceTest {

private TransactionManager transactionManager;
private JdbcTemplate jdbcTemplate;
private UserDao userDao;

@BeforeEach
void setUp() {
this.jdbcTemplate = new JdbcTemplate(new PooledDataSourceConnectionManager());
this.userDao = new UserDao(jdbcTemplate);
this.transactionManager = new TransactionManager(new PooledDataSourceConnectionManager());
this.jdbcTemplate = new JdbcTemplate();
this.userDao = new UserDao(transactionManager, jdbcTemplate);

DatabasePopulatorUtils.execute(DataSourceConfig.getInstance());
final var user = new User("gugu", "password", "[email protected]");
Expand All @@ -33,8 +34,8 @@ void setUp() {

@Test
void testChangePassword() {
final var userHistoryDao = new UserHistoryDao(jdbcTemplate);
final var userService = new UserService(userDao, userHistoryDao);
final var userHistoryDao = new UserHistoryDao(transactionManager, jdbcTemplate);
final var userService = new UserService(transactionManager, userDao, userHistoryDao);

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

final var newPassword = "newPassword";
final var createBy = "gugu";
Expand Down

This file was deleted.

53 changes: 28 additions & 25 deletions jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,29 @@ public class JdbcTemplate {

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

private final ConnectionManager connectionManager;

public JdbcTemplate(final ConnectionManager connectionManager) {
this.connectionManager = connectionManager;
}

public int executeUpdate(final String query, final Object... parameters) {
return execute(query, (connection, preparedStatement) -> preparedStatement.executeUpdate(), parameters);
public int executeUpdate(final Connection connection, final String query, final Object... parameters) {
PreparedStatementCallback<Integer> preparedStatementCallback = PreparedStatement::executeUpdate;
Copy link

Choose a reason for hiding this comment

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

뭔기 이번 단계에서 final var를 주로 사용하신 것 같은데, 이 부분에서는 일부로 타입을 명시해주신건가요? 왠지 함수형 인터페이스라 잘 읽히지 않을 것 같아서 명시적으로 타입을 적으신거 같아서요!

Copy link
Author

Choose a reason for hiding this comment

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

var를 쓰려고 하면 Cannot infer type: method reference requires an explicit target type라고 하면서 빨간 줄이 뜨더라고요!

그런데 final은 실수로 빼먹었고 / 위 부분은 애초에 변수로 뺄 필요가 없어 보이기도 하네요 ㅎㅎㅎ
다음 단계에서 같이 수정하겠습니당

return execute(connection, query, preparedStatementCallback, parameters);
}

public <T> T executeQueryForObject(
final Connection connection,
final String query,
final RowMapper<T> rowMapper,
final Object... parameters
) {
final ResultSetExtractor<T> resultSetExtractor = resultSet -> {
if (resultSet.next()) {
return rowMapper.mapRow(resultSet);
}
final var results = executeQueryForList(connection, query, rowMapper, parameters);
if (results.isEmpty()) {
return null;
};

return executeQuery(query, resultSetExtractor, parameters);
}
if (results.size() > 1) {
throw new SqlQueryException(query, "cannot map for single result");
}
return results.get(0);
}

public <T> List<T> executeQueryForList(
final Connection connection,
final String query,
final RowMapper<T> rowMapper,
final Object... parameters
Expand All @@ -52,31 +49,37 @@ public <T> List<T> executeQueryForList(
return results;
};

return executeQuery(query, resultSetExtractor, parameters);
return executeQuery(connection, query, resultSetExtractor, parameters);
}

public <T> T executeQuery(
final Connection connection,
final String query,
final ResultSetExtractor<T> resultSetExtractor,
final Object... parameters
) {
return execute(query, (connection, preparedStatement) -> {
try (final ResultSet resultSet = preparedStatement.executeQuery()) {
return resultSetExtractor.extract(resultSet);
}
}, parameters);
return execute(
connection,
query,
preparedStatement -> {
try (final ResultSet resultSet = preparedStatement.executeQuery()) {
return resultSetExtractor.extract(resultSet);
}
}, parameters);
}

private <T> T execute(
final Connection connection,
final String query,
final ConnectionCallback<T> callback,
final PreparedStatementCallback<T> callback,
final Object... parameters
) {
try (final Connection connection = connectionManager.getConnection();
final PreparedStatement preparedStatement = connection.prepareStatement(query)) {
try (final Connection conn = connection;
final PreparedStatement preparedStatement = conn.prepareStatement(query)) {
log.info("query: {}", query);
setParameters(preparedStatement, parameters);
return callback.doInConnection(connection, preparedStatement);

return callback.doInConnection(preparedStatement);
} catch (SQLException exception) {
throw new SqlQueryException(exception.getMessage(), query);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.springframework.jdbc.core;

import java.sql.PreparedStatement;
import java.sql.SQLException;

@FunctionalInterface
public interface PreparedStatementCallback<T> {

T doInConnection(final PreparedStatement preparedStatement) throws SQLException;

}
Loading
Loading