You will be fine

<토비의스프링> 7.1~7.2 SQL DAO 분리하기

by BFine
반응형

7.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하면 디폴트주입은 그대로 발생하여 사용하지 않는 객체가 만들어진다.

반응형

블로그의 정보

57개월 BackEnd

BFine

활동하기