-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #34 from this-is-spear/feature/find-account
자신 계좌 조회할 수 있는 기능을 추가한다.
- Loading branch information
Showing
25 changed files
with
383 additions
and
161 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,16 @@ | ||
package bankingapi.alarm.infra; | ||
|
||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.scheduling.annotation.Async; | ||
import org.springframework.stereotype.Service; | ||
|
||
import bankingapi.alarm.domain.AlarmService; | ||
|
||
@Slf4j | ||
@Service | ||
public class NumbleAlarmService implements AlarmService { | ||
@Async | ||
public void notify(Long userId, String message) { | ||
try { | ||
Thread.sleep(500); | ||
} catch (InterruptedException e) { | ||
throw new RuntimeException(e); | ||
} | ||
log.info("send message user id is {}, {}", userId, message); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
165 changes: 110 additions & 55 deletions
165
src/main/java/bankingapi/concurrency/ConcurrencyManagerWithNamedLock.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,71 +1,126 @@ | ||
package bankingapi.concurrency; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.dao.ConcurrencyFailureException; | ||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; | ||
import org.springframework.stereotype.Service; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import javax.sql.DataSource; | ||
import java.sql.Connection; | ||
import java.sql.ResultSet; | ||
import java.sql.SQLException; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.stream.Stream; | ||
|
||
@Slf4j | ||
@Service | ||
@Slf4j | ||
@RequiredArgsConstructor | ||
public class ConcurrencyManagerWithNamedLock implements ConcurrencyManager { | ||
private static final String GET_LOCK = "SELECT GET_LOCK(:userLockName, :timeoutSeconds)"; | ||
private static final String RELEASE_SESSION_LOCKS = "SELECT RELEASE_ALL_LOCKS()"; | ||
private static final String EXCEPTION_MESSAGE = "LOCK 을 수행하는 중에 오류가 발생하였습니다."; | ||
private static final int TIMEOUT_SECONDS = 2; | ||
private static final String EMPTY_RESULT_MESSAGE = "USER LEVEL LOCK 쿼리 결과 값이 없습니다. type = [{}], userLockName : [{}]"; | ||
private static final String INVALID_RESULT_MESSAGE = "USER LEVEL LOCK 이 존재하지 않습니다. type = [{}], result : [{}] userLockName : [{}]"; | ||
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; | ||
private static final String GET_LOCK = "SELECT GET_LOCK(?, ?)"; | ||
private static final String RELEASE_SESSION_LOCKS = "SELECT RELEASE_ALL_LOCKS()"; | ||
private static final String RELEASE_LOCK = "SELECT RELEASE_LOCK(?)"; | ||
private static final String EXCEPTION_MESSAGE = "LOCK 을 수행하는 중에 오류가 발생하였습니다."; | ||
private static final int TIMEOUT_SECONDS = 5; | ||
private static final String EMPTY_RESULT_MESSAGE = "USER LEVEL LOCK 쿼리 결과 값이 NULL 입니다. type = [{}], userLockName : [{}]"; | ||
private static final String INVALID_RESULT_MESSAGE = "USER LEVEL LOCK 쿼리 결과 값이 0 입니다. type = [{}], result : [{}] userLockName : [{}]"; | ||
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; | ||
private final DataSource userLoackDataSource; | ||
|
||
@Override | ||
public void executeWithLock(String lockName1, String lockName2, Runnable runnable) { | ||
try (var connection = userLoackDataSource.getConnection()) { | ||
try { | ||
log.debug("start getLock=[{}], timeoutSeconds : [{}], connection=[{}]", getMultiLockName(lockName1, lockName2), TIMEOUT_SECONDS, connection); | ||
getLock(connection, getMultiLockName(lockName1, lockName2)); | ||
try { | ||
log.debug("start getLock=[{}], timeoutSeconds : [{}], connection=[{}]", lockName1, TIMEOUT_SECONDS, connection); | ||
getLock(connection, lockName1); | ||
try { | ||
log.debug("start getLock=[{}], timeoutSeconds : [{}], connection=[{}]", lockName2, TIMEOUT_SECONDS, connection); | ||
getLock(connection, lockName2); | ||
runnable.run(); | ||
} finally { | ||
log.debug("start releaseLock=[{}], connection=[{}]", lockName2, connection); | ||
releaseLock(connection, lockName2); | ||
} | ||
}finally { | ||
log.debug("start releaseLock=[{}], connection=[{}]", lockName1, connection); | ||
releaseLock(connection, lockName1); | ||
} | ||
} finally { | ||
log.debug("start releaseLock=[{}], connection=[{}]", getMultiLockName(lockName1, lockName2), connection); | ||
releaseLock(connection, getMultiLockName(lockName1, lockName2)); | ||
} | ||
} catch (SQLException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
@Override | ||
public void executeWithLock(String lockName1, String lockName2, Runnable runnable) { | ||
try { | ||
getLock(lockName1); | ||
getLock(lockName2); | ||
runnable.run(); | ||
} finally { | ||
releaseSessionLocks(); | ||
} | ||
} | ||
@Override | ||
public void executeWithLock(String lockName, Runnable runnable) { | ||
try (var connection = userLoackDataSource.getConnection()) { | ||
log.info("start getLock=[{}], timeoutSeconds : [{}], connection=[{}]", lockName, TIMEOUT_SECONDS, connection); | ||
getLock(connection, lockName); | ||
try { | ||
runnable.run(); | ||
} finally { | ||
log.info("start releaseLock, connection=[{}]", connection); | ||
releaseLock(connection, lockName); | ||
} | ||
} catch (SQLException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
@Override | ||
public void executeWithLock(String lockName, Runnable runnable) { | ||
try { | ||
getLock(lockName); | ||
runnable.run(); | ||
} finally { | ||
releaseSessionLocks(); | ||
} | ||
} | ||
private void getLock(Connection connection, String userLockName) { | ||
try (var preparedStatement = connection.prepareStatement(GET_LOCK)) { | ||
preparedStatement.setString(1, userLockName); | ||
preparedStatement.setInt(2, TIMEOUT_SECONDS); | ||
var resultSet = preparedStatement.executeQuery(); | ||
validateResult(resultSet, userLockName, "GetLock"); | ||
} catch (SQLException e) { | ||
log.error("GetLock_{} : {}", userLockName, e.getMessage()); | ||
throw new IllegalStateException("SQL Exception"); | ||
} | ||
} | ||
private void releaseLock(Connection connection, String userLockName) { | ||
try (var preparedStatement = connection.prepareStatement(RELEASE_LOCK)) { | ||
preparedStatement.setString(1, userLockName); | ||
preparedStatement.executeQuery(); | ||
} catch (SQLException e) { | ||
log.error("Release Lock : {}", e.getMessage()); | ||
throw new IllegalStateException("SQL Exception"); | ||
} | ||
} | ||
private void releaseSessionLocks(Connection connection) { | ||
try (var preparedStatement = connection.prepareStatement(RELEASE_SESSION_LOCKS)) { | ||
preparedStatement.executeQuery(); | ||
} catch (SQLException e) { | ||
log.error("ReleaseSessionLocks : {}", e.getMessage()); | ||
throw new IllegalStateException("SQL Exception"); | ||
} | ||
} | ||
|
||
private void getLock(String userLockName) { | ||
Map<String, Object> params = new HashMap<>(); | ||
params.put("userLockName", userLockName); | ||
params.put("timeoutSeconds", ConcurrencyManagerWithNamedLock.TIMEOUT_SECONDS); | ||
Integer result = namedParameterJdbcTemplate.queryForObject(GET_LOCK, params, Integer.class); | ||
validateResult(result, userLockName, "GetLock"); | ||
} | ||
private void releaseSessionLocks() { | ||
Map<String, Object> params = new HashMap<>(); | ||
namedParameterJdbcTemplate.queryForObject(RELEASE_SESSION_LOCKS, params, Integer.class); | ||
} | ||
|
||
private void releaseSessionLocks() { | ||
Map<String, Object> params = new HashMap<>(); | ||
Integer result = namedParameterJdbcTemplate.queryForObject(RELEASE_SESSION_LOCKS, params, Integer.class); | ||
validateResult(result, "SESSION", "ReleaseLock"); | ||
} | ||
private void validateResult(ResultSet resultSet, String userLockName, String type) throws SQLException { | ||
if (!resultSet.next()) { | ||
log.error(EMPTY_RESULT_MESSAGE, type, userLockName); | ||
throw new ConcurrencyFailureException(EXCEPTION_MESSAGE); | ||
} | ||
int result = resultSet.getInt(1); | ||
if (result == 0) { | ||
log.error(INVALID_RESULT_MESSAGE, type, result, userLockName); | ||
throw new ConcurrencyFailureException(EXCEPTION_MESSAGE); | ||
} | ||
} | ||
|
||
private void validateResult(Integer result, String userLockName, String type) { | ||
if (result == null) { | ||
log.error(EMPTY_RESULT_MESSAGE, type, userLockName); | ||
throw new ConcurrencyFailureException(EXCEPTION_MESSAGE); | ||
} | ||
if (result == 0) { | ||
log.error(INVALID_RESULT_MESSAGE, type, result, | ||
userLockName); | ||
throw new ConcurrencyFailureException(EXCEPTION_MESSAGE); | ||
} | ||
} | ||
private static String getMultiLockName(String lockName1, String lockName2) { | ||
return Stream.of(lockName1, lockName2).sorted().reduce((a, b) -> a + b).get(); | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
src/main/java/bankingapi/util/config/DatasourceConfiguration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package bankingapi.util.config; | ||
|
||
import com.zaxxer.hikari.HikariDataSource; | ||
import org.springframework.boot.context.properties.ConfigurationProperties; | ||
import org.springframework.boot.jdbc.DataSourceBuilder; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.context.annotation.Primary; | ||
|
||
import javax.sql.DataSource; | ||
|
||
@Configuration | ||
public class DatasourceConfiguration { | ||
@Primary | ||
@Bean | ||
@ConfigurationProperties("spring.datasource.hikari") | ||
public DataSource dataSource() { | ||
return DataSourceBuilder.create().type(HikariDataSource.class).build(); | ||
} | ||
|
||
@Bean | ||
@ConfigurationProperties("userlock.datasource.hikari") | ||
public DataSource userLockDataSource() { | ||
return DataSourceBuilder.create().type(HikariDataSource.class).build(); | ||
} | ||
} |
Oops, something went wrong.