데이터베이스에 관련된 로직을 실행할 때 읽기전용이라면 동시성 문제가 발생하지 않는다.
하지만 수정할 때는 동시성 문제가 발생할 수 있기 때문에 ConcurrentHashMap을 이용해서
여러개의 map을 하나의 메서드에서 실행시킬 때 동시성 문제를 해결할 수 있다.
ConcurrentHashMap
public class ConcurrentHashMapSqlRegistry implements UpdatableSqlRegistry {
private Map<String, String> sqlMap = new ConcurrentHashMap<String, String>();
public String findSql(String key) throws SqlNotFoundException {
String sql = sqlMap.get(key);
if (sql == null) throw new SqlNotFoundException(key + "를 이용해서 SQL을 찾을 수 없습니다");
else return sql;
}
public void registerSql(String key, String sql) { sqlMap.put(key, sql); }
public void updateSql(String key, String sql) throws SqlUpdateFailureException {
if (sqlMap.get(key) == null) {
throw new SqlUpdateFailureException(key + "에 해당하는 SQL을 찾을 수 없습니다");
}
sqlMap.put(key, sql);
}
public void updateSql(Map<String, String> sqlmap) throws SqlUpdateFailureException {
for(Map.Entry<String, String> entry : sqlmap.entrySet()) {
updateSql(entry.getKey(), entry.getValue());
}
}
}
테스트 코드
public class ConcurrentHashMapSqlRegistryTest {
UpdatableSqlRegistry sqlRegistry;
@BeforeEach
public void setUp(){
sqlRegistry = new ConcurrentHashMapSqlRegistry();
sqlRegistry.registerSql("KEY1", "SQL1");
sqlRegistry.registerSql("KEY2", "SQL2");
sqlRegistry.registerSql("KEY3", "SQL3");
}
@Test
public void find() {
checkFindResult("SQL1", "SQL2", "SQL3");
}
private void checkFindResult(String expected1, String expected2, String expected3) {
Assertions.assertThat(sqlRegistry.findSql("KEY1")).isEqualTo(expected1);
Assertions.assertThat(sqlRegistry.findSql("KEY2")).isEqualTo(expected2);
Assertions.assertThat(sqlRegistry.findSql("KEY3")).isEqualTo(expected3);
}
@Test
public void unknownKey(){
Assertions.assertThatThrownBy( () -> sqlRegistry.findSql("SQL9999!#@$")).
isInstanceOf(SqlNotFoundException.class);
}
@Test
public void updateSingle() {
sqlRegistry.updateSql("KEY2", "Modified2");
checkFindResult("SQL1", "Modified2", "SQL3");
}
@Test
public void updateMulti() {
Map<String, String> sqlmap = new HashMap<String, String>();
sqlmap.put("KEY1", "Modified1");
sqlmap.put("KEY3", "Modified3");
sqlRegistry.updateSql(sqlmap);
checkFindResult("Modified1", "SQL2", "Modified3");
}
@Test
public void updateWithNotExistingKey(){
Assertions.assertThatThrownBy( () -> sqlRegistry.updateSql("SQL9999!@#$", "Modified2")
).isInstanceOf(SqlUpdateFailureException.class);
}
}
애플리케이션 개발단계에서 많이 사용하는 내장 데이터베이스를 사용하는 방법을 학습해보자.
hsql을 build.gradle에 추가해주고 테스트 코드를 통해 확인해본다.
schema.sql
CREATE TABLE SQLMAP (
KEY_ VARCHAR(100) PRIMARY KEY,
SQL_ VARCHAR(100) NOT NULL
);
data.sql
INSERT INTO SQLMAP(KEY_, SQL_) values('KEY1', 'SQL1');
INSERT INTO SQLMAP(KEY_, SQL_) values('KEY2', 'SQL2');
build.gradle 의존성 추가 (hsql)
implementation group: 'org.testifyproject.local-resources', name: 'hsql', version: '1.0.2'
테스트 코드
public class EmbeddedDbTest {
EmbeddedDatabase db;
JdbcTemplate template;
@BeforeEach
public void setUp() {
db = new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:/embedded/schema.sql")
.addScript("classpath:/embedded/data.sql")
.build();
template = new JdbcTemplate(db);
}
@AfterEach
public void tearDown() {
db.shutdown();
}
@Test
public void initData() {
Assertions.assertThat(template.queryForObject("select count(*) from sqlmap",Integer.class) ).isEqualTo(2);
List<Map<String,Object>> list = template.queryForList("select * from sqlmap order by key_");
Assertions.assertThat((String)list.get(0).get("key_")).isEqualTo("KEY1");
Assertions.assertThat((String)list.get(0).get("sql_")).isEqualTo("SQL1");
Assertions.assertThat((String)list.get(1).get("key_")).isEqualTo("KEY2");
Assertions.assertThat((String)list.get(1).get("sql_")).isEqualTo("SQL2");
}
@Test
public void insert() {
template.update("insert into sqlmap(key_, sql_) values(?,?)", "KEY3", "SQL3");
Assertions.assertThat(template.queryForObject("select count(*) from sqlmap",Integer.class))
.isEqualTo((3));
}
}
이제 테스트 코드도 상속으로 중복을 제거하려고 한다.
ConcurrentHashMapSqlRegistryTest와 EmbeddedDbSqlRegistry는 공통적으로 UpdatableSqlRegistry 인터페이스를
구현하기 때문에 추상 클래스로 공통된 부분을 정의하고 상속을 이용해서 중복된 코드를 제거한다.
AbstractUpdatableSqlRegistryTest
public abstract class AbstractUpdatableSqlRegistryTest {
UpdatableSqlRegistry sqlRegistry;
@BeforeEach
public void setUp(){
sqlRegistry = new ConcurrentHashMapSqlRegistry();
sqlRegistry.registerSql("KEY1", "SQL1");
sqlRegistry.registerSql("KEY2", "SQL2");
sqlRegistry.registerSql("KEY3", "SQL3");
}
@Test
public void find() {
checkFindResult("SQL1", "SQL2", "SQL3");
}
private void checkFindResult(String expected1, String expected2, String expected3) {
Assertions.assertThat(sqlRegistry.findSql("KEY1")).isEqualTo(expected1);
Assertions.assertThat(sqlRegistry.findSql("KEY2")).isEqualTo(expected2);
Assertions.assertThat(sqlRegistry.findSql("KEY3")).isEqualTo(expected3);
}
@Test
public void unknownKey(){
Assertions.assertThatThrownBy( () -> sqlRegistry.findSql("SQL9999!#@$")).
isInstanceOf(SqlNotFoundException.class);
}
@Test
public void updateSingle() {
sqlRegistry.updateSql("KEY2", "Modified2");
checkFindResult("SQL1", "Modified2", "SQL3");
}
@Test
public void updateMulti() {
Map<String, String> sqlmap = new HashMap<String, String>();
sqlmap.put("KEY1", "Modified1");
sqlmap.put("KEY3", "Modified3");
sqlRegistry.updateSql(sqlmap);
checkFindResult("Modified1", "SQL2", "Modified3");
}
@Test
public void updateWithNotExistingKey(){
Assertions.assertThatThrownBy( () -> sqlRegistry.updateSql("SQL9999!@#$", "Modified2")
).isInstanceOf(SqlUpdateFailureException.class);
}
}
EmbeddedDbSqlRegistryTest
public class EmbeddedDbSqlRegistryTest extends AbstractUpdatableSqlRegistryTest {
EmbeddedDatabase db;
protected UpdatableSqlRegistry createUpdatableSqlRegistry() {
db = new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:spring/sql/updatable/sqlRegistrySchema.sql")
.build();
EmbeddedDbSqlRegistry embeddedDbSqlRegistry = new EmbeddedDbSqlRegistry();
embeddedDbSqlRegistry.setDataSource(db);
return embeddedDbSqlRegistry;
}
@AfterEach
public void tearDown() {
db.shutdown();
}
}
ConcurrentHashMapSqlRegistryTest
public class ConcurrentHashMapSqlRegistryTest extends AbstractUpdatableSqlRegistryTest {
protected UpdatableSqlRegistry createUpdatableSqlRegistry() {
return new ConcurrentHashMapSqlRegistry();
}
}
'WEB > SPRING' 카테고리의 다른 글
토비의 스프링 7.6 스프링 3.1의 DI (1) | 2024.02.27 |
---|---|
토비의 스프링 7.5.2 트랜잭션 적용하기 (0) | 2024.02.19 |
토비 스프링 7.2 인터페이스의 분리와 자기참조 빈 (0) | 2024.02.15 |
토비의 스프링 7.1 SQL과 DAO의 분리 (0) | 2024.02.13 |
6.5 스프링 AOP (1) | 2024.02.10 |