Skip to content

Commit

Permalink
[JDBC 라이브러리 구현하기 - 3단계] 이리내(성채연) 미션 제출합니다 (#514)
Browse files Browse the repository at this point in the history
이리내 안녕하세요~! 서비스 테스트까지 잘 구현해 주셨군요~! 다음 단계가 얼마 남지 않았고 요구사항을 잘 지켜주셔서 머지 하겠습니다~! (제잘못..) 이리내에게 물어보고 싶은 부분에 대해서 커멘트 남겨놓았고 리팩터링 하고싶은 부분이 생겼다면 다음 단계에서 해주시면 될 것 같아요! 고생하셨습니다~!
  • Loading branch information
hectick authored Oct 8, 2023
1 parent 266190f commit 1a6cb92
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 70 deletions.
8 changes: 7 additions & 1 deletion app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.techcourse.dao;

import com.techcourse.domain.User;
import java.sql.Connection;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
Expand Down Expand Up @@ -36,9 +37,14 @@ public void update(final User user) {
jdbcTemplate.update(sql, user.getAccount(), user.getPassword(), user.getEmail(), user.getId());
}

public void update(final Connection connection, final User user) {
final String sql = "update users set account = ?, password = ?, email = ? where id = ?";
jdbcTemplate.update(connection, sql, user.getAccount(), user.getPassword(), user.getEmail(), user.getId());
}

public List<User> findAll() {
final String sql = "select id, account, password, email from users";
return jdbcTemplate.query(sql, ROW_MAPPER);
return jdbcTemplate.queryForList(sql, ROW_MAPPER);
}

public User findById(final Long id) {
Expand Down
16 changes: 16 additions & 0 deletions app/src/main/java/com/techcourse/dao/UserHistoryDao.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.techcourse.dao;

import com.techcourse.domain.UserHistory;
import java.sql.Connection;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;

Expand Down Expand Up @@ -29,4 +30,19 @@ public void log(final UserHistory userHistory) {
userHistory.getCreateBy()
);
}

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 (?, ?, ?, ?, ?, ?)";

jdbcTemplate.update(
connection,
sql,
userHistory.getUserId(),
userHistory.getAccount(),
userHistory.getPassword(),
userHistory.getEmail(),
userHistory.getCreatedAt(),
userHistory.getCreateBy()
);
}
}
42 changes: 37 additions & 5 deletions app/src/main/java/com/techcourse/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package com.techcourse.service;

import com.techcourse.config.DataSourceConfig;
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;
import org.springframework.dao.DataAccessException;

public class UserService {

Expand All @@ -23,10 +28,37 @@ 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);
user.changePassword(newPassword);
userDao.update(user);
userHistoryDao.log(new UserHistory(user, createBy));
public void changePassword(final long id, final String newPassword, final String createBy) {
final DataSource dataSource = DataSourceConfig.getInstance();
final Connection connection = getConnection(dataSource);
try (connection) {
connection.setAutoCommit(false);

final User user = findById(id);
user.changePassword(newPassword);
userDao.update(connection, user);
userHistoryDao.log(connection, new UserHistory(user, createBy));

connection.commit();
} catch (SQLException | DataAccessException e) {
rollback(connection);
throw new DataAccessException(e);
}
}

private Connection getConnection(final DataSource dataSource) {
try {
return dataSource.getConnection();
} catch (SQLException e) {
throw new DataAccessException();
}
}

private void rollback(final Connection connection) {
try {
connection.rollback();
} catch (SQLException e) {
throw new DataAccessException();
}
}
}
8 changes: 8 additions & 0 deletions app/src/test/java/com/techcourse/dao/UserDaoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import com.techcourse.config.DataSourceConfig;
import com.techcourse.domain.User;
import com.techcourse.support.jdbc.init.DatabasePopulatorUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;

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

Expand All @@ -21,6 +23,12 @@ void setup() {
userDao.insert(user);
}

@AfterEach
void tearDown() {
final JdbcTemplate jdbcTemplate = new JdbcTemplate(DataSourceConfig.getInstance());
jdbcTemplate.update("TRUNCATE TABLE users RESTART IDENTITY");
}

@Test
void findAll() {
final var users = userDao.findAll();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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;

Expand All @@ -15,4 +16,9 @@ public MockUserHistoryDao(final JdbcTemplate jdbcTemplate) {
public void log(final UserHistory userHistory) {
throw new DataAccessException();
}

@Override
public void log(final Connection connection, final UserHistory userHistory) {
throw new DataAccessException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

@Disabled
class UserServiceTest {

private JdbcTemplate jdbcTemplate;
Expand Down
43 changes: 25 additions & 18 deletions jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
import javax.sql.DataSource;
import org.springframework.dao.DataAccessException;

Expand All @@ -23,9 +22,22 @@ public int update(final String sql, final Object... args) {
}

private <T> T execute(final PreparedStatementCallback preparedStatementCallback,
final ExecutionCallback<T> executionCallback) {
try (final Connection connection = dataSource.getConnection();
final PreparedStatement pstmt = preparedStatementCallback.prepareStatement(connection)) {
final ExecutionCallback<T> executionCallback) {
try (final Connection connection = dataSource.getConnection()) {
return executeWithConnection(connection, preparedStatementCallback, executionCallback);
} catch (SQLException e) {
throw new DataAccessException(e);
}
}

public int update(final Connection connection, final String sql, final Object... args) {
return executeWithConnection(connection, conn -> prepareStatement(sql, conn, args), PreparedStatement::executeUpdate);
}

private <T> T executeWithConnection(final Connection connection,
final PreparedStatementCallback preparedStatementCallback,
final ExecutionCallback<T> executionCallback) {
try (final PreparedStatement pstmt = preparedStatementCallback.prepareStatement(connection)) {
return executionCallback.execute(pstmt);
} catch (SQLException e) {
throw new DataAccessException(e);
Expand All @@ -45,31 +57,26 @@ private void setParameters(final PreparedStatement pstmt, final Object[] args) t
}
}

@Nullable
public <T> T queryForObject(final String sql, final RowMapper<T> rowMapper, final Object... args) {
final List<T> results = executeQuery(connection -> prepareStatement(sql, connection, args), rowMapper);
final List<T> results = queryForList(sql, rowMapper, args);
if (results.isEmpty()) {
return null;
throw new DataAccessException();
}
if (results.size() > 1) {
throw new DataAccessException();
}
return results.get(0);
return results.iterator().next();
}

private <T> List<T> executeQuery(
final PreparedStatementCallback preparedStatementCallback,
final RowMapper<T> rowMapper
) {
return execute(preparedStatementCallback, pstmt -> {
public <T> List<T> queryForList(final String sql, final RowMapper<T> rowMapper, final Object... args) {
return execute(connection -> prepareStatement(sql, connection, args), pstmt -> {
try (final ResultSet rs = pstmt.executeQuery()) {
List<T> results = new ArrayList<>();
final List<T> results = new ArrayList<>();
while (rs.next()) {
results.add(rowMapper.mapRow(rs));
}
return results;
}
});
}

public <T> List<T> query(final String sql, final RowMapper<T> rowMapper, final Object... args) {
return executeQuery(connection -> prepareStatement(sql, connection, args), rowMapper);
}
}
33 changes: 17 additions & 16 deletions study/src/test/java/transaction/stage1/Stage1Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ private void setUp(final DataSource dataSource) {
* Read phenomena | Dirty reads
* Isolation level |
* -----------------|-------------
* Read Uncommitted |
* Read Committed |
* Repeatable Read |
* Serializable |
* Read Uncommitted | +
* Read Committed | -
* Repeatable Read | -
* Serializable | -
*/
@Test
void dirtyReading() throws SQLException {
Expand All @@ -81,7 +81,7 @@ void dirtyReading() throws SQLException {
final var subConnection = dataSource.getConnection();

// 적절한 격리 레벨을 찾는다.
final int isolationLevel = Connection.TRANSACTION_NONE;
final int isolationLevel = Connection.TRANSACTION_SERIALIZABLE;

// 트랜잭션 격리 레벨을 설정한다.
subConnection.setTransactionIsolation(isolationLevel);
Expand Down Expand Up @@ -111,10 +111,10 @@ void dirtyReading() throws SQLException {
* Read phenomena | Non-repeatable reads
* Isolation level |
* -----------------|---------------------
* Read Uncommitted |
* Read Committed |
* Repeatable Read |
* Serializable |
* Read Uncommitted | +
* Read Committed | +
* Repeatable Read | -
* Serializable | -
*/
@Test
void noneRepeatable() throws SQLException {
Expand All @@ -130,7 +130,7 @@ void noneRepeatable() throws SQLException {
connection.setAutoCommit(false);

// 적절한 격리 레벨을 찾는다.
final int isolationLevel = Connection.TRANSACTION_NONE;
final int isolationLevel = Connection.TRANSACTION_SERIALIZABLE;

// 트랜잭션 격리 레벨을 설정한다.
connection.setTransactionIsolation(isolationLevel);
Expand All @@ -154,7 +154,7 @@ void noneRepeatable() throws SQLException {
sleep(0.5);

// 사용자A가 다시 gugu 객체를 조회했다.
// 사용자B는 패스워드를 변경하고 아직 커밋하지 않았다.
// 사용자B는 패스워드를 변경하고 커밋헸다.
final var actual = userDao.findByAccount(connection, "gugu");

// 트랜잭션 격리 레벨에 따라 아래 테스트가 통과한다.
Expand All @@ -173,10 +173,10 @@ void noneRepeatable() throws SQLException {
* Read phenomena | Phantom reads
* Isolation level |
* -----------------|--------------
* Read Uncommitted |
* Read Committed |
* Repeatable Read |
* Serializable |
* Read Uncommitted | +
* Read Committed | +
* Repeatable Read | +
* Serializable | -
*/
@Test
void phantomReading() throws SQLException {
Expand All @@ -185,6 +185,7 @@ void phantomReading() throws SQLException {
final var mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0.30"))
.withLogConsumer(new Slf4jLogConsumer(log));
mysql.start();
mysql.withUrlParam("allowMultiQueries", "true");
setUp(createMySQLDataSource(mysql));

// 테스트 전에 필요한 데이터를 추가한다.
Expand All @@ -197,7 +198,7 @@ void phantomReading() throws SQLException {
connection.setAutoCommit(false);

// 적절한 격리 레벨을 찾는다.
final int isolationLevel = Connection.TRANSACTION_NONE;
final int isolationLevel = Connection.TRANSACTION_SERIALIZABLE;

// 트랜잭션 격리 레벨을 설정한다.
connection.setTransactionIsolation(isolationLevel);
Expand Down
10 changes: 5 additions & 5 deletions study/src/test/java/transaction/stage2/FirstUserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public Set<String> saveAndExceptionWithRequiredNew() {
throw new RuntimeException();
}

// @Transactional(propagation = Propagation.REQUIRED)
@Transactional(propagation = Propagation.REQUIRED)
public Set<String> saveFirstTransactionWithSupports() {
final var firstTransactionName = TransactionSynchronizationManager.getCurrentTransactionName();
userRepository.save(User.createTest());
Expand All @@ -77,7 +77,7 @@ public Set<String> saveFirstTransactionWithSupports() {
return of(firstTransactionName, secondTransactionName);
}

// @Transactional(propagation = Propagation.REQUIRED)
@Transactional(propagation = Propagation.REQUIRED)
public Set<String> saveFirstTransactionWithMandatory() {
final var firstTransactionName = TransactionSynchronizationManager.getCurrentTransactionName();
userRepository.save(User.createTest());
Expand All @@ -88,7 +88,7 @@ public Set<String> saveFirstTransactionWithMandatory() {
return of(firstTransactionName, secondTransactionName);
}

@Transactional(propagation = Propagation.REQUIRED)
//@Transactional(propagation = Propagation.REQUIRED)
public Set<String> saveFirstTransactionWithNotSupported() {
final var firstTransactionName = TransactionSynchronizationManager.getCurrentTransactionName();
userRepository.save(User.createTest());
Expand All @@ -99,7 +99,7 @@ public Set<String> saveFirstTransactionWithNotSupported() {
return of(firstTransactionName, secondTransactionName);
}

@Transactional(propagation = Propagation.REQUIRED)
//@Transactional(propagation = Propagation.REQUIRED)
public Set<String> saveFirstTransactionWithNested() {
final var firstTransactionName = TransactionSynchronizationManager.getCurrentTransactionName();
userRepository.save(User.createTest());
Expand All @@ -110,7 +110,7 @@ public Set<String> saveFirstTransactionWithNested() {
return of(firstTransactionName, secondTransactionName);
}

@Transactional(propagation = Propagation.REQUIRED)
//@Transactional(propagation = Propagation.REQUIRED)
public Set<String> saveFirstTransactionWithNever() {
final var firstTransactionName = TransactionSynchronizationManager.getCurrentTransactionName();
userRepository.save(User.createTest());
Expand Down
Loading

0 comments on commit 1a6cb92

Please sign in to comment.