본문 바로가기

WEB/SPRING

토비의 스프링 7.5 DI를 이용해 다양한 구현 방법 적용하기

데이터베이스에 관련된 로직을 실행할 때 읽기전용이라면 동시성 문제가 발생하지 않는다.

하지만 수정할 때는 동시성 문제가 발생할 수 있기 때문에 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();
    }
}