본문 바로가기

스프링

[spring] xml_springOxm_내장형데이터베이스(hsqldb)_레지스트리_7

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.org.hsqldb : spring 내장형 DataBase

7.     Com.springsource.junit : junit

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

9.     Mail : java-mail

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

11.   Mysql-connector : Mysql JDBC

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

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

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

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

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

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

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

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

20.   Spring-test.jar

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

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

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

21.   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이 편리한 포맷

 

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

 

* Context에 등록된 내장형DBDataSourceDI 받아서 SQL을 저장하는 레지스트리

16. Context에서 주입받은 EmbeddedDatabase DataSource 타입의 서브인터페이스이다. EmbededDatabase가 아닌 DataSource로 주입을 받았을까?

> 클라이언트(EmbededDbSqlRegistry)자신이 필요로 하는 기능을 가진 인터페이스를 통해 의존 오브젝트를 DI 해야한다. EmbededDbSqlRegistrysms JDBC를 이용해 DB에 접근할 수만 있으면 된다. 따라서, 초기화작업 등이 필요없는 DataSource 인터페이스가 가장 적합하다. 따라서 사용하지 않을 DB 종료 기능을 가진 EmbededDatabase 대신 DataSource 인터페이스를 사용.

* 서브인터페이스 : SqlRegistry의 서브인터페이스는 UpdateableSqlRegistry (상속을 통해 기능을 확장한 인터페이스

 

프로퍼티 설정을 통한 초기화 작업을 제외하면 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 수정 실패 익셉션

 

* EmbeddedDbSqlRegistryConcurrentHashMapRegistry 등의 UpdateableSqlRegistry 인터페이스를 구현한 모든 클래스에 대한 테스트하기 위한 추상클래스.

- Registry 클래스 모두 UpdatableSqlRegistry 인터페이스를 구현하고 있기 때문에 테스트 내용이 중복될 가능성이 높다.

- 또한, DAO 테스트는 DB까지 연동하는 테스트로 두고, EmbeddedDbSqlRegistry는 뒤의 내장형 DB까지 연동돼서 테스트하는 것이 간편하다.

- 결국 ConcurrentHashMapRegistry EmbeddedDbSqlRegistry의 테스트 방법은 특별히 차이 날 것이 없다. (아래 스샷참고)

20, 26. 위처럼 추상클래스로 Registry를 생성하는 부분을 추상메소드로 만들어 서브클래스에서 구현하도록 만들고, 나머지는 공통으로 사용하면 된다. 구현된 코드를 공유하는 가장 쉬운 방법은 상속이다. Abstract 접근제한자는 private 일 수 없다. 자식에 의해 구현되기 때문에.

 

기존(지금은 사용안할거임)의 테스트. ConcurrentHashMapSqlRegistry에 의존하는 부분을 제외하고 나머지 EmbeddedDbSqlRegistry와 테스트는 같다고 볼 수 있다.

 

ConcurrentHashMapRegistry 테스트 클래스. @Test가 없지만 슈퍼클래스인 AbstractUpdateable SqlRegistryTest@Test를 실행하고 createtableSqlRegistry를 통해 생성된 오브젝트로 테스트를 진행할 것이다.

 

테이블을 생성하는 SQL스크립트는 sql 파일에 저장해두고 내장형 DB빌더가 사용할 수 있게함.

 

* EmbeddedDbSqlRegistry 테스트를 위한 클래스

import static org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType.HSQL;

> 위 소스에서 누락 static import

14. 스프링에서 간단히 내장형 DB를 이용하려면 EmbeddedDatabaseBuilder를 사용하면 된다. 그런데 EmbeddedDatabaseBuilder를 빈으로 등록한다고 해서 바로 사용할 수 없다. 위처럼 적절한 메소드를 호출해주는 초기화 코드가 필요하다. 초기화 코드가 필요하다면 팩토리 빈(스프링을 대신해서 오브젝트의 생성로직을 담당하도록 만든 특별한 Bean)으로 만드는 것이 좋다.

15~25. ConcurrentHashMapRegistry 와는 달리 초기화 등 준비과정이 많고, DB를 닫아주는 작업도 필요하다.

 

내장형 DB를 등록하기 위하여 jdbc 스키마 선언

 

스프링에는 팩토리 빈을 만드는 번거로운 작업대신 내장형 DB를 손쉽게 팩토리빈으로 생성해주는 전용 태그가 있다. 이렇게 설정하면 EmbeddedDatabase 타입 오브젝트를 이용해 내장형 DB를 자유롭게 사용할 수 있다.(EmbeddedDatabase DataSource 타입의 서브인터페이스)

* 서브인터페이스 : SqlRegistry의 서브인터페이스는 UpdateableSqlRegistry (상속을 통해 기능을 확장한 인터페이스)

59. type : 스프링이 지원하는 세 가지 내장형 DB(Derby, HSQL, H2)중에서 하나를 선택하면 된다.

59. jdbc:embedded-database : 이 태그에 의해 만들어지는 EmbeddedDatabase 타입 빈은 스프링 컨테이너가 종료될 때 자동으로 shutdown() 메소드가 호출되도록 설정되어 있다. 내장형 DB를 종료시키기 위한 별도의 코드나 설정은 필요하지 않다.

 

 

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

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

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

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

 

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

 

타겟인터페이스 구현

 

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

 

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

 

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

 

트랜잭션 테스트

 

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