일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- sope
- 징검다리
- 전화번호 목록
- 11723
- 스프링
- Singtone
- 구현
- Spring
- 플로이드워셜
- 플로이드와샬
- 이진검색
- 토비의스프링
- 가장먼노드
- 스프링프로젝트 시작하기
- 카카오
- 백준
- 프로그래머스
- Algorithm
- bitmasking
- @Profile
- 이진탐색
- Spring이란
- 카카오인턴
- 그래프
- BinarySearch
- 쇠막대기 문제
- Java
- 알고리즘
- 자바
- 스프링이란
- Today
- Total
육감적 코딩
토비의스프링3.1 [7장] 7.5 DI를 이용해 다양한 구현 방법 적용하기 본문
7.5 DI를 이용해 다양한 구현 방법 적용하기
7.5.1 ConcurrentHashMap을 이용한 수정 가능 SQL 레지스트리
멀티스레드 환경에서 안전하게 HashMap을 조작하려면 Collections.synchronizedMap()등을 이용해 외부에서 동기화해줘야한다. 하지만 이렇게 HashMap의 전 작업을 동기화하려면 고성능 서비스에서는 성능에 문제가 생긴다.
→ ConcurrentHashMap 사용이 권장된다.
ConcurrentHashMap은 데이터 조작 시 전체 데이터에 대해 락을 걸지 않고 조회는 락을 아예 사용하지않는다.
7.5.2 내장형 데이터베이스를 이용한 SQL 레지스트리 만들기
ConcurrentHashMap이 멀티 스레드 환경에서 성능이 그리 나쁜편은 아니지만, 잦은 조회와 변경이 일너나는 환경이라면 한계가 있다.
그래서 SQL레지스트리를 DB를 이용해 만들어보자.
그렇다고 DAO가 사용할 SQL을 저장해두고 관리할 목적으로 별도의 DB를 구성하면 배보다 배꼽이 더 크기때문에, 이런경우에는 내장형DB를 사용하는게 적당하다.
내장형 DB빌더 학습 테스트
내장형 DB는 애플리케이션을 통해 DB가 시작될 때마다 매번 테이블을 새롭게 생성한다. 따라서 테이블 생성SQL 스크립트와 레지스트리에 사용할 용도로 키와SQL 문장을 저장할 필드를 만들어두자.
CREATE TABLE SLQMAP(
KEY_ VARCHAR(100) PRIMARY KEY,
SQL_ VARCHAR(100) NOT NULL
);
INSERT INTO SQLMAP(KEY_, SQL_) values(‘KEY1’, ‘SQL1’);
INSERT INTO SQLMAP(KEY_, SQL_) values(‘KEY2’, ‘SQL2’);
내장형 DB빌더는 DB엔진을 생성하고 초기화 스크립트를 실행해서 테이블과 초기 데이터를 준비한 뒤에 DB에 접근할 수 있는 Connection을 생성해주는 DataSource 오브젝트를 돌려주게 된다. 정확히는DB셧다운 기능을 가진 EmbeddedDatabase 타입 오브젝트다.
new EmbeddedDatabaseBuilder() // 빌더오브젝트 생성
.setType(내장형DB종류)
.addScript(초기화에 사용할 DB 스크립트의 리소스)
…
.build(); // 주어진 조건에 맞는 내장형 DB를 준비하고 초기화를 한뒤 EmbeddedDatabase를 돌려준다.
public class EmbeddedDbTest {
EmbeddedDatabase db;
SimpleJdbcTemplate template;
@Before
public void setUp(){
db = new EmbeddedDatabaseBuilder()
.setType(HSQL)
.addScript("classpath:schema.sql")
.addScript("classpath:data.sql")
.build();
template = new SimpleJdbcTemplate(db);
}
@After
public void tearDown(){
db.shutdown();
}
@Test
public void initData(){
assertThat(template.queryForInt("select count(*) from sqlmap"),is(2));
List<Map<String,Object>> list = template.queryForList("select * from sqlmap order by key_");
assertThat(list.get(0).get("key_"),is("KEY1"));
assertThat(list.get(0).get("sql_"),is("SQL1"));
assertThat(list.get(1).get("key_"),is("KEY2"));
assertThat(list.get(1).get("sql_"),is("SQL2"));
}
@Test
public void insert(){
template.update("insert into sqlmap(key_, sql_) values(?,?)", "KEY3","SQL3");
assertThat(template.queryForInt("select count(*) from sqlmap"),is(3));
}
}
내장형 DB를 이용한 SqlRegistry 만들기
EmbeddedDataBuilder는 직접 빈으로 등록한다고 바로 사용할 수 있는게 아니다. 적절한 메소드를 호출해주는 초기화 코드가 필요하다. 초기화 코드가 필요하다면 팩토리 빈으로 만드는 것이 좋다.
스프링빈에는 팩토리 빈을 만드는 번거로운 작업을 대신해주는 전용 태그가 있다. jdbc네임스페이스를 선언해두고 간단한 전용 태그로 빈을 정의해주면 내장형 DB를 손쉽게 사용할 수 있다.
<jdbc:embedded-database id=”embeddedDatabase” type=”HSQL”>
<jdbc:script location=”classpath:schema.sql”/>
</jdbc:embedded-database>
public class EmbeddedDbSqlRegistry implements UpdatableSqlRegistry{
SimpleJdbcTemplate jdbc;
public void setDataSource(DataSource dataSource){
jdbc= new SimpleJdbcTemplate(dataSource); } // EmbeddedDatabase 타입이 아닌 DataSource 타입.
public void registerSql(String key, String sql){
jdbc.update(“insert into sqlmap(key_,sql_) values(?,?)”,key,sql);}
public String findSql(String key) throws SqlNotFoundException{
try{
return jdbc.queryForObject(“select sql_ from sqlmap where key_=?”, String.class,key);
}
catch(EmptyResultDataAccessException e){
throw new SqlNotFoundException(key+”에 해당하는 SQL을 찾을 수 없습니다.”.e);
}
}
public void updateSql(String key,String sql) throws SqlUpdateFailureException{
int affected = jdbc.update(“update sqlmap set sql_=? where key_=?”,sql,key);
if(affected == 0){
throw new SqlUpdateFailureException(key+”에 해당하는 SQL을 찾을 수 없습니다.”);
}
}
public void updateSql(Map<String, String> sqlmap) throws SqlUpdateFailureException{
for(Map.Entry<String, String> entry: sqlmap.entrySet()){
updateSql(entry.getKey(), entry.getValue());
}
}
}
내장형 DB로 정의한 빈의 타입은 EmbeddedDatabase 타입인데 DataSource를 주입받는 이유
-
EmbeddedDatabase는 DataSource의 서브타입이기 때문.
-
인터페이스 분리 원칙을 지키기 위해서
-
클라이언트는 자신이 필요한 기능만 DI해주면 되기때문.
7.5.3 트랜잭션 적용
EmbeddedSqlRegistry는 내장형 DB를 사용하여 안전하게 SQL을 수정하도록 보장해 준다.
하지만 하나 이상의 SQL을 맵으로 전달받아 한 번에 수정해야 하는 경우에는 문제가 생길 수 있다.
맵으로 SQL과 키의 쌍을 전달받은 updateSql()메소드는 한 번에 한개의 SQL을 수정해주는 같은 이름의 updateSql()메소드를 맵 안에 있는 SQL의 개수만큼 반복해서 호출하도록 되어있다.
만약 중간에 문제가 생긴다면 이미 수정한 SQL은 DB에 그대로 반영되고 예외가 발생한 SQL 부터 그 이후는 적용되지 않은 채로 작업을 마치게 된다.
따라서 트랜잭션을 적용해야한다.
AOP를 이용해도 되지만 트랜잭션 경계가 DAO밖에 있지않고 제한된 오브젝트내에서 간단한 트랜잭션이 필요하기때문에 트랜잭션 추상화 API를 사용하는게 편리하다.
다중 SQL수정에 대한 트랜잭션 테스트
public class EmbeddedDbSqlRegistryTest extends AbstractUpdatableSqlRegitstryTest{
…
@Test
public void transactionlUpdate(){
checkFind(“SQL1”,”SQL2”,”SQL3”); // 초기상태 확인
Map<String, String> sqlmap = new HashMap<String,String>();
sqlmap.put(“KEY1”,”Modified1”);
sqlmap.put(“KEY9999!@#$”,”Modified9999”); // 키를 존재하지 않는 것으로 지정해서 update시 실패를 유도하여 KEY1 의 내용이 롤백되는것을 확인하기 위해서
try{
sqlRegistry.updateSql(sqlmap);
fail(); //예외가 발생해서 catch로 넘어가지 않으면 뭔가 잘못된것.
}
catch(SqlUpdateFailureException e){}
checkFind(“SQL1”,”SQL2”,”SQL3”); // 초기상태와 같은 지 확인.
}
}
해당 테스트는 당연히 실패를 확인하는 것이 목적이다.
왜냐하면 아직 트랜잭션 기능을 추가하지 않았기 때문이다.
코드를 이용한 트랜잭션 적용
TransactionTemplate를 사용하는 편이 좋다
public class EmbeddedDbSqlRegistry implements UpdateSqlRegistry{
SimpleJdbcTemplate jdbc;
TransactionTemplate transactionTemplate; // (1)
public void setDataSource(DataSource dataSource){
jdbc = new SimpleJdbcTemplate(dataSource);
transactionTemplate = new TransactionTemplate( new DataSourceTransactionManager(dataSource));
}
…
// (2)
public void updateSql(final Map<String, String> sqlmap) throws SqlUpdateFailureException{
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status){
for(Map.Entry<String, String> entry: sqlmap.entrySet() ) {
updateSql(entry.getKey(), entry.getValue());
}
}
});
}
)
-
JdbcTemplate와 트랜잭션을 동기화해주는 트랜잭션 템플릿이다. 멀티스테환경에서 공유가능
-
익명 내부 클래스로 만들어지는 콜백오브젝트 안에서 사용되는 것이라 final로 선언.
'정리 > Spring' 카테고리의 다른 글
토비의스프링3.1 [8장] 스프링이란 무엇인가? (0) | 2020.08.05 |
---|---|
@Component와 컴포넌트 스캔 (0) | 2020.08.03 |
[Spring] 3. @Autowired (0) | 2020.07.23 |
[Spring] 2. ApplicationContext와 다양한 빈 설정 방법 (0) | 2020.07.22 |
[Spring] 1. IoC컨테이너와 빈 (0) | 2020.07.20 |