본문 바로가기

스프링

[spring] xml_springOxm_인터페이스상속_ConcurrentHashMap(동기화)_5

1.     Com.springsource.javax.activation : Spring java-mail

2.     Com.springsource.javax.mail : Spring java-mail

3.     Com.springsource.org.aopalliance : Spring ProxyFactoryBean

4.     Com.springsource.org.aspectj.tools : AspectJExpressionPointcut 포인트컷 표현식 지원

5.     Com.springsource.org.castor : OXM castor Framework

6.     Com.springsource.junit : junit

7.     Commons-logging : Spring-context가 사용

8.     Mail : java-mail

9.     Mockito : 목 프레임워크 중 Mockito

10.   Mysql-connector : Mysql JDBC

11.   Org.springframework.context.support : Spring java-mail

12.   Org.springframework.oxm : Spring OXM(Object XML Mapping) Jaxb .

13.   Spring-aop : 스프링 기능 자체의 aop, Spring ProxyFactoryBean

14.   Spring-bean : 스프링 코어와 함께 의존성 주입 제공 (Core Container)

15.   Spring-context : 스프링 코어, BeanFactory를 확장한 어플리케이션 컨텍스트 구현, 리소스 로드 및 국제화 지원(Core Container)

16.   Spring-core : 다른 스프링 모듈이 사용하는 유틸리티(Core Container)

17.   Spring-expression : EL 확장 Bean속성(배열, 컬렉션 포함).(Core Container)

18.   Spring-jdbc : 스프링이 지원하는 jdbc.

19.   Spring-test.jar

-       @RunWith : Junit 프레임워크의 테스트 실행방법을 확장시 사용.

-       SpringJUnit4ClassRunner : 어플리케이션컨텍스트를 만들고 관리하는 확장 클래스

-       @ContextConfiguration(경로) : 자동으로 만들어줄 어플리케이션 컨텍스트 설정파일

20.   Spring-tx : DuplicateKeyException.class 파일 존재 및 스프링 트랜잭션

 

레벨의 역할(다음레벨 셋팅 및 전달)은 레벨에게 위임

 

User의 역할은 User에게 User 레벨셋팅

 

 

JAXB sql 엘리먼트 domain

 

Jaxb Sqlmap 엘리먼트 domain

 

SpringUnmarshaller을 통해 마샬링/언마샬링을 하기 위해서는 해당 domain ObjectFactory를 생성해줘야 한다. 직접 생성해줘도 되고, Jaxb 컴파일러로 생성해도 됨.

 

JPA, Hibernate, JDBC, JDO 와 같이 변할 수 있으므로 인터페이스

 

JDBC 구현

 

쿼리 DAO와 분리 DBA에 의한 SQL 리뷰나 튜닝 필요시 해당 파일만 전달. SQL 내용을 변경하더라도 어플리케이션 코드나 DI 설정은 전혀 수정할 필요가 없어짐.

독립적인 파일 XML이 편리한 포맷

 

45. 리소스 로더(ApplicationContext)가 인식할 수 있는 문자열로 표현해주면 된다. Classpath는 디폴트이므로 생략 가능하다. 클래스 패스 루트부터 절대위치를 적어야함.

46. 디폴트로 준비된 HashMapSqlRegistry 대신 SQL 수정이 가능한 외부에 따로 정의한 SQL 레지스트리를 사용하도록 프로퍼티 명시 지정.

 

* XML을 읽어서 Map에 보관하고 필요할 때 제공 (템플릿 인터페이스) 할 일이 2가지 이상일 때는 관심사의 분리가 필요함.

 

* XML을 읽어서 Map에 보관하고 필요할 때 제공 관심사의 구분 중 읽어서 보관하기 위한 인터페이스(전략)

- 리턴타입 void : map으로 리턴해줘도 되지만 범용적인 인터페이스가 map을 사용안한다고 한다면?? 바로 Registry에 저장해 주는 방법 채택

- 파일이 없는 경우 등 대부분 복구가 불가능한 예외로 판단하여 예외 없음.

 

> 위처럼 직접 구현해줘도 되지만, Spring unmarshaller를 사용하고, OxmService에서 직접 Reader 설정을 주입받는 장점이 있기에 미사용함

> OxmService와 하나의 클래스로 만들어 두기 때문에 빈의 등록과 설정은 단순해지고 쉽게 사용.

> OXM이 변경될 경우 자꾸 늘어나는 빈의 개수와 반복되는 DI구조가 불편해진다.

> 디폴트의존오브젝트로 생성하는 것도 방법이지만 OXM 중 하나에 종속되므로 좀더 유연한 설계를 위해 필요하다.

 

* XML을 읽어서 Map에 보관하고 필요할 때 제공 관심사의 구분 중 보관되어 있는 Map 중 요청한 쿼리를 제공해주기 위한 인터페이스(전략)

- 레지스트리에서 검색 실패 등으로 다른 레지스트리에 검색 시도 등 복구할 여지가 있어 예외를 던짐.

 

HashMap에 저장하여 키를 받으면 쿼리를 주는 HashMapSqlRegistry

초기화 때 Map에 등록하고 읽기전용으로 동작하는 HashMapSqlRegistry는 동시성 문제가 발생할 일이 없다. 하지만 실시간으로 SQL을 변경해야한다면?

> HashMap은 멀티스레드 환경에서 동시 수정이 발생시 예상하지 못한 결과가 나올 수 있다. HashMap을 멀티스레드 환경에서 안전하게 조작하려면 Collections.synchronizedMap() 등을 이용해 외부에서 동기화 해줘야 하지만, 요청이 많은 고성능 서비스에서는 성능 문제가 발생한다.

 

1. 인터페이스 분리원칙 : 모든 클라이언트가 자신의 관심에 따른 접근 방식을 불필요한 간섭없이 유지. 기존 클라이언트에 영향을 주지 않은 채로 오브젝트의 기능을 확장하거나 수정시 새로운 인터페이스를 추가로 구현하는 경우뿐만 아니라 인터페이스를 상속해서 기능을 확장할 수도 있다.

2. 동기화를 위한 새로운 인터페이스가 필요한 상황이지만 새로운 클라이언트의 성격에 따라서 기존 SqlRegistry를 확장한 인터페이스를 이용할 수 있다.

3. 기존 SqlRegistry 인터페이스를 이용해 접근하는 클라이언트인 BaseSqlService 클래스와 그 서브클래스가 존재하기 때문에 SqlRegistry 인터페이스 자체를 수정하는 것은 바람직한 방법이 아니다.

4. SQL 저장소에 담긴 SQL 자체를 변경하는 기능을 추가하기로 했다. 이 때 새로운 클라이언트는 관리자가 사용할 클라이언트일 것이다. 관리기능에는 단순 업데이트 뿐만 아니라 등록이나 조회 같은 기본적인 기능도 필요할 테니 기존 SqlRegistry 인터페이스에 정의된 메소드도 사용할 필요가 있으므로 인터페이스를 상속하여 새로운 인터페이스로 정의. UpdateableSqlRegistry 인터페이스는 SqlRegistry의 서브인터페이스라고 볼 수 있다.

 

* 지금까지 써왔던 HashMapRegistryJDK HashMap을 사용하여 멀티스레드 환경에서SQL을 실시간으로 변경하는 작업을 만들 때 가장 먼저 고려해야할 사항 > 동시성 문제

> Collection.synchronizedMap() 을 이용해도 되지만 이렇게 동기화하면 성능문제생김

11. 따라서 동기화된 해시 데이터 조작에 최적화되도록 만들어진 ConcurrentHashMap 사용

21, 29. 업데이트 HashMap, ConcurrentHashMap은 키의 중복을 허용하지 않고, 동일한 키를 put 시키면 값을 업데이트하는 특성을 이용.

* ConcurrentHashMap이 멀티스레드 환경에서 최소한의 동시성을 보장해주고 성능도 그리 나쁜 편은 아니지만, 저장되는 데이터의 양이 많아지고 잦은 조회와 변경이 일어나는 환경이라면 한계가 있다. > 인덱스를 이용한 최적화된 검색을 지원하고 동시에 많은 요청을 처리하면서 안정적인 변경 작업이 가능한 기술은 데이터베이스이다. 다음편 내장형 데이터 베이스를 이용한 SQL 레지스트리 참조.

 

프로퍼티 설정을 통한 초기화 작업을 제외하면 BaseSqlServiceOxmSqlService에 중복된다. 무시할 수도 있겠지만 만약 BaseSqlService의 코드를 재사용한다고 이를 상속해서 OxmSqlService를 만들면 멤버 클래스로 통합시킨 OxmSqlReader를 생성하는 코드를 넣기가 애매하다. 또는 중복을 제거하기 위해 loadSql()getSql() 메소드를 추출해서 슈퍼클래스로 분리하는 방법도 있겠지만, 이 정도 코드로는 복잡한 계층구조로 만들기도 부담스럽다. 그래서 이런 경우에는 그냥 간단한 코드의 중복쯤은 허용하고 BaseSqlService와는 독립적으로 OxmSqlService를 관리해나가도 크게 문제될 것은 없어 보인다. 그런데 이 두 개의 중복되는 코드를 가진 loadSql()getSql()의 수정이 필요할 때마다 양쪽을 함께 변경해야하니 부담되고 실수할 가능성도 높아진다. 이런 경우 위임 구조를 이용해 코드의 중복을 제거할 수 있다.

 

loadSql()getSql()의 구현 로직은 BaseSqlService에만 두고, OxmSqlService는 일종의 설정과 기본 구성을 변경해주기 위한 어댑터 같은 개념으로 BaseSqlService의 앞에 두는 설계가 가능하다. OxmSqlService의 외형적인 틀은 유지한 채로 SqlService의 기능 구현은 BaseSqlService로 위임하는 것이다. 위임을 위해서는 두 개의 빈을 등록할 수도 있다. 하지만 OxmSqlServiceBaseSqlService를 위임구조로 만들기 위해 두 개의 빈을 등록하는 것은 불편한 일이다. 부가기능 프록시처럼 많은 타깃에 적용할 것도 아니고, 특화된 서비스를 위해 한번만 사용할 것이므로 유연한 DI방식은 포기하고 OxmSqlServiceBaseSqlService를 한 클래스로 묶는 방법을 생각해보자.

 

1. SQL 자체 업데이트 기능을 가진 새로운 인터페이스가 생겼다. UpdateableSqlRegistry 하지만 BaseSqlServiceSQL 등록과 조회만을 목적으로 SQL 레지스트리 오브젝트를 사용할 것이므로, 기존의 SqlRegistry 인터페이스를 통해 접근하면 충분하다.

 

48. SQL 매핑정보 소스의 타입을 Resource로 받는다.

58. 리소스의 종류(classpathFile,http,FTP)에 상관없이 스트림으로 가져올 수 있다.

* Resource는 단지 리소스에 접근할 수 있는 추상화된 핸들러일 뿐이다. 실제 리소스가 아니다.

 

* OXM 템플릿 클래스

: SQL을 읽는 방법을 OXM으로 제한해서 사용성을 극대화 하는 것이 목적(OXM에서만 읽는 방법이기 때문) 전략은 구분하되 이를 스태틱 멤버 클래스로 내장하고 자신만이 사용. 밖에서 볼 때는 OxmSqlService 하나의 오브젝트로 보이지만 내부에서는 의존관계를 가진 두 개의 오브젝트가 깔끔하게 결합돼서 사용된다. 유연성은 조금 손해를 보더라도 내부적으로 낮은 결합도를 유지한 채로 응집도가 높은 구현을 만들 때 유용하게 사용할 수 있다.

* 자바 멤버 클래스

> 하나의 클래스로 만들어 두기 때문에 빈의 등록과 설정은 단순해지고 쉽게 사용.

> OXM이 변경될 경우 자꾸 늘어나는 빈의 개수와 반복되는 DI구조가 불편해진다.

> 디폴트의존오브젝트로 생성하는 것도 방법이지만 OXM 중 하나에 종속되므로 좀더 유연한 설계를 위해 필요하다.

 

OxmSqlServiceSqlReader를 멤버 클래스로 고정시켜서 OXM에 특화된 형태로 재구성했기 때문에 설정은 간결해지고 의도되지 않은 방식으로 확장될 위험이 없다. 그렇지만 BaseSqlServiceloadSql() getSql() 이라는 핵심 메소드 구현코드가 동일하다는 점이 꺼림직하므로 BaseSqlService 사용.

 

 

 

SQL 등록실패 익셉션

 

SQL 수정 실패 익셉션

 

데코레이터패턴, 프록시, 트랜잭션을 위한 인터페이스.

트랜잭션  경계설정의 일원화(하나로 만듬)

비즈니스 로직을 담고 있는 서비스 계층 오브젝트에 트랜잭션 경계를 부여하기에 가장 적절한 대상이다.

예를 들어 UserService가 아니라면 UserDao를 직접 사용하지 않고, UserService를 사용하는 것이 바람직하다. 단순 조회나 간단한 수정이라면 직접 UserService외의 서비스 계층 오브젝트에서 UserDao를 사용해도 상관없다. 하지만 등록이나 수정, 삭제가 포함된 작업이라면 다른 모듈의 DAO를 직접 이용할 때 신중을 기해야 한다. 안전하게 사용하려면 다른 모듈의 서비스 계층을 통해 접근하는 방법이 좋다.(트랜잭션 모두 취소 혹은 모두 성공)

단순히 레코드 개수를 리턴하는 getCount()를 제외하면 나머지는 독자적인 트랜잭션을 가지고 사용될 가능성이 높다. 따라서 이 4개의 메소드를 추가함.

 

타겟인터페이스 구현

 

메일서버 과부하를 막기위한 테스트 스텁

 

등록 후 수정하는 테스트케이스

동시성에 대한 부분도 테스트하면 좋겠지만, 이는 간단하지 않다. 아쉽지만 이것으로 만족하자.

 

DB 접속불가를 위한 목오브젝트를 이용한 테스트

 

목 오브젝트 생성과 add 테스트

 

트랜잭션 테스트

 

번거롭게 목, 스텁 오브젝트를 생성하지 않고, 목 프레임웍을 활용한 테스트