<토비의스프링> 7.1~7.2 SQL DAO 분리하기
by BFine7.1 SQL과 DAO의 분리
DB 테이블과 필드정보를 담고있는 SQL 문장의 문제점
=> 로직변경이 없더라도 SQL은 빈번히 번경되는데 DAO코드를 수정해야함
7.1.1. XML 설정을 이용한 분리
- XML 설정 파일의 프로퍼티 값으로 DAO에 SQL을 주입할 수 있다.
<property name="sqlAdd" value="insert into ~ values ~">
- 단점은 매번 프로퍼티를 추가해야한다. => SQL을 하나의 컬렉션으로 만들자
<bean id="sqlService" class="SimpleSqlService">
<property name="sqlMap">
<map>
<entry key="userAdd" value="insert into users " />
<entry key="userGet" value="select * from " />
<entry key="userGetAll" value="select * from " />
<entry key="userDeleteAll" value="delete from " />
<entry key="userGetCount" value="select count(*) " />
<entry key="userUpdate" value="update users " />
</map>
</property>
</bean>
- 문자열로된 키값을 사용하기 떄문에 오타 발생할 경우 확인하기가 힘들다
7.1.2. SQL 제공 서비스
- SQL과 DI 설정 정보가 섞여있는건 바람직하지 못함
=> 독립적인 SQL 제공서비스가 필요하다
- SQL 인터페이스를 설계 해보자
public interface SqlService{
String getSql(String key) throws SqlRetrivevalFailureExcption;
}
<bean id="userDao">
<property name="dataSource" ref="dataSource">
<property name="sqlService" ref="sqlService">
</bean>
<bean id="sqlService" class="SimpleSqlService">
<property name="sqlMap">
<map>
<entry key="userAdd" value="insert into users " />
<entry key="userGet" value="select * from " />
<entry key="userGetAll" value="select * from " />
<entry key="userDeleteAll" value="delete from " />
<entry key="userGetCount" value="select count(*) " />
<entry key="userUpdate" value="update users " />
</map>
</property>
</bean>
- DAO는 이제 어디서 SQL을 저장해두고 가져오는지 신경쓰지 않아도 된다. 응집도↑
7.2. 인터페이스의 분리와 자기참조 빈
7.2.1. XML 파일 매핑
- <bean>에 SQL 정보를 넣고 활용하는 건 좋은 방법이 아니다. => XML 포맷에 SQL을 두자
- JAXB는 XML 문서정보를 거의 동일한 구조의 객체로 직접매핑
=> 객체트리구조로 만들어줌
xjc -p [생성할 패키지명] [변환할 스키마 파일] -d [파일저장위치] 바인딩용 클래스를 자동 생성해줌
7.2.2. XML 파일을 이용하는 SQL 서비스
- SQL이 담긴 XML 문서
<?xml version="1.0" encoding="UTF-8"?>
<sqlmap
xmlns="http://www.epril.com/sqlmap"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.epril.com/sqlmap
http://www.epril.com/sqlmap/sqlmap.xsd ">
<sql key="userAdd">insert into </sql>
<sql key="userGet">select * frome</sql>
<sql key="userGetAll">select * from </sql>
<sql key="userDeleteAll">delete from</sql>
<sql key="userGetCount">select count(*)</sql>
<sql key="userUpdate">update users</sql>
</sqlmap>
- 매번 XML 파일을 읽어서 SQL을 찾는건 비효율적인 방법이다.
=> 생성자에서 SQL을 읽어서 내부에 저장해보자
7.2.3 빈의 초기화 작업
- 생성자에서 예외가 발생할 수 있는 초기화 작업은 다루지 않는게 좋다.
=> 별도의 초기화 메서드를 사용해라
- 어느시점에 초기화를 해야할까?
=> 스프링이 제어권을 가지고 있으므로 스프링에게 맡겨야한다.
- <context:annotation-config/> 설정을 해주면 빈 후처리기들이 등록된다(어노테이션)
- 여기서 초기화는 @PostConstruct를 이용하자
- 동작 순서
XML 빈 설정을 읽는다. ▽ 빈의 오브젝트를 생성한다. ▽ 프로퍼티에 값을 주입한다. ▽ 후처리기를 동작시킨다. --> @PostConstruct |
7.2.4. 인터페이스 분리
- XML 대신에 다른 포멧 파일에서 SQL을 읽으려면 새롭게 만들어야하는 문제가 있다.
=> XmlSqlService를 고처야 하는데 단일 책임원칙을 위배
- SQL 정보를 외부 리소스로 부터 읽자, SQL 보관해두고 있다가 필요할떄 제공해주자
- 애플리케이션을 재설치하지 않고도 SQL을 긴급히 변경해야하는 경우가 있다???
- SqlService의 구현 클래스를 두개로 분리하자
1. SqlReader - 읽기 요청
2. SqlRegistry - 등록 및 조회
- SqlReader 에게는 SqlRegistry 전략을 제공해주고 여기에 저장하게 하자
7.2.5. 자기 참조 빈으로 시작하기
- XmlSqlService 클래스에 3개의 인터페이스를 구현한다(SqlReader, SqlService, SqlRegistry)
public class XmlSqlService implements SqlService{
private SqlReader sqlReader;
private SqlRegistry sqlRegistry;
}
public class XmlSqlService implements SqlService, SqlRegistry{
private Map<String, String> sqlMap = new HashMap<String,String>();
public String findSql(String key) throws SqlNotFoundExcption{
String sql = sqlMap.get(key);
...
}
public void registerSql(String key, String sql){
sqlMap.put(key,sql);
...
}
}
public class XmlSqlService implements SqlService, SqlRegistry, SqlReader{
private String sqlmapFile;
public void read(SqlRegistry sqlRegistry){
String contextPath = Sqlmap.class.getPackage().getName();
try{
JAXBContext context = JAXBContext.newInstance(contextPath);
Unmarshaller unmarshaller = context.createUnmarshaller();
InputStream is = UserDao.class.getResourceAsStream(sqlmapFile);
Sqlmap = sqlmap = (Sqlmap)unmarshaller.unmarshal(is);
for(SqlType sql : sqlmap.getSql()){
sqlRegistry.registerSql(sql.getKey(),sql.getValue());
}
}catch(...){
...
}
}
@PostConstruct
public void loadSql(){
this.sqlReader.read(this.sqlRegistry);
}
public String getSql(String key){
return this.sqlRegistry.findSql(key);
}
}
- SqlReader, SqlRegistry 객체의 전략을 사용, 인터페이스를 통해 간접적으로 접근!
- 클래스 하나, 빈도 하나지만 마치 세개의 빈이 등록된 것처럼 주입
<bean id="sqlService" class="XmlSqlService">
<property name="sqlReader" ref="sqlService">
<property name="sqlRegistry" ref="sqlService">
</bean>
- 프로퍼티는 자기 자신을 참조할 수 있다.
7.2.6. 디폴트 의존관계
- XmlSqlService 코드에서 의존 인터페시으와 구현 코드를 제거
public class BaseSqlService implements SqlService{
protected SqlReader sqlReader; // 상속을 통한 확장
protected SqlRegistry sqlRegistry;
public void setSqlReader(SqlReader sqlReader){
this.sqlReader = sqlReader;
}
public void setSqlRegistry(SqlRegistry sqlRegistry){
this.sqlRegistry = sqlRegistry;
}
@PostConstruct
public void loadSql(){
this.sqlReader.read(this.sqlRegistry);
}
public String getSql(String key) throws SqlRetrievalFailureException{
try {
return this.sqlRegistry.findSql(key);
} catch (SqlNotFoundException e) {
throw new SqlRetrievalFailureException(e);
}
}
- SqlReader, SqlRegistry 구현 클래스를 빈으로 등록해준다.
public class HashMapSqlRegistry implements SqlRegistry{
private Map<String, String> sqlMap = new HashMap<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 class JaxbXmlSqlReader implements SqlReader{
private String sqlmapFile;
public void read(SqlRegistry sqlRegistry){...}
}
- 늘어난 클래스와 인터페이스 구현과 의존관계 설정에 대한 부담은 감수해야함
- 디폴트 의존관계란 외부에서 DI받지 않은 경우 기본적으로 자동 적용되는 의존관계
public class DefaultSqlService extends BaseSqlService{
public DefaultSqlService(){
setSqlReader(new JaxbXmlSqlReader());
setSqlRegistry(new HashMapSqlRegistry());
}
}
- 생성자에서 직접 만들어서 스스로 DI 해준다.
- 단점은 설정을 통해 다른 오브젝트를 DI하면 디폴트주입은 그대로 발생하여 사용하지 않는 객체가 만들어진다.
'개발서적 > 토비의스프링' 카테고리의 다른 글
<토비의스프링> 7.5~7.6.1 내장형 데이터베이스 (0) | 2021.02.20 |
---|---|
<토비의스프링> 7.3~7.4 서비스 추상화 (0) | 2021.02.20 |
<토비의스프링> 6.7~6.8 트랜잭션 어노테이션 (0) | 2021.02.20 |
<토비의스프링> 6.4~6.5 AOP(2) (0) | 2021.02.20 |
<토비의스프링> 6.1~6.3 AOP (0) | 2021.02.18 |
블로그의 정보
57개월 BackEnd
BFine