Spring

토비의 Spring_Chapter07_스프링 핵심 기술의 응용

강용민 2022. 11. 15. 12:27

지금까지 살펴봤던 세 가지 기술을 애플리케이션 개발에 활용해서 새로운 기능을 만들어보고 이를 통해 스프링의 개발철학과 추구하는 가치, 스프링 사용자에게 요구되는 게 무엇인지를 살펴보겠다.

 

7.1 SQL과 DAO의 분리

UserDao에 이제 남은건 SQL을 DAO에서 분리하는 것이다.

 

XML 설정을 이용한 분리

가장 손쉽게 생각해볼 수 있는 SQL 분리 방법은 SQL을 스프링의 XML 설정파일로 뺴내는 것이다. 스프링은 설정을 이용해 빈에 값을 주입해줄 수 있다.

 

개별 SQL 프로퍼티 방식

UserDaoJdb 클래스의 SQL문을 프로퍼티로 만들고 이를 XML에서 지정해보자.

 

UserDaoJdbc.java 일부

public class UserDaoJdbc implements UserDao {
    private String sqlAdd;
    
    public void setSqlAdd(String sqlAdd){
        this.sqlAdd = sqlAdd;
    }
    //..생략
}

add() 메소드의 SQL 문장을 제거하고 외부로 DI 받은 SQL 문장을 담은 sqlAdd를 사용하게 만든다.

public void add(User user){
    this.jdbcTemplate.update(
        this.sqlAdd,
        user.getId(), user.getName() //..생략);
}
<bean id="userDao" class="springbook.user.dao.UserDaoJdbc">
    <property name="dataSource" ref="dataSource"/>
    <property name="sqlAdd" value="insert into users(id, name, password, email, ~ />
    ...

 

SQL 맵 프로퍼티 방식

SQL을 하나의 컬렉션으로 담아두는 방법을 시도해보자. 맵을 이용하면 키 값을 이요해 SQL 문장을 가져올 수 있다.

public class UserDaoJdbc implements UserDao {
    private Map<String, String> sqlAdd;
    
    public void setSqlAdd(Map<String,String> sqlMap){
        this.sqlAdd = sqlAdd;
    }
    //..생략
}
public void add(User user){
    this.jdbcTemplate.update(
        this.sqlMap.get("add"),
        user.getId(), user.getName() //..생략);
}

sqlMap 프로퍼티 타입은 Map이다. Map은 하나 이상의 복잡한 정보를 담고 있기 때문에 <property> 태그의 value 애트리뷰트로는 정의해줄 수 없어 <map>태그를 사용해야 한다. <entry>태그에 선언된 키와 값을 담은 Map 타입 오브젝트가 만들어져서 프로퍼티에 주입된다.

<bean id="userDao" class="springbook.user.dao.UserDaoJdbc">
    <property name="dataSource" ref="dataSource"/>
    <property name="sqlMap">
        <map>
            <entry key="add" value="sqlAdd" value="insert into users(id, name, password, email, ~ />
            <entry key="get" vlaue=
            ...
        </map>
    </property>
</bean>

 

SQL 제공 서비스

스프링 설정파일 안에 SQL을 두고 이를 DI 해서 DAO가 사용하게 하면 손쉽게 SQL을 코드에서 분리해낼 수 있지만 문제점들이 몇 가지 있다.

  • 데이터 액세스 로직의 일부인 SQL 문장을 애플리케이션의 구성정보를 가진 설정정보와 함꼐 두는 건 바람직하기 못하다.
  • SQL을 꼭 스프링의 빈 설정 방법을 사용해 XML에 담아둘 이유도 없다.
  • 스프링의 설정파일로부터 생성된 오브젝트와 정보는 애플리케이션을 다시 시작하기 전에는 변경이 매우 어렵다.

DAO가 사용할 SQL을 제공해주는 기능을 독립시킬 필요가 있다.

 

SQL 서비스 인터페이스

SQL 서비스의 인터페이스를 설계해보자. DAO가 사용할 SQL 서비스의 기능은 간단하다. SQL에 대한 키 값을 전달하면 그에 해당하는 SQL을 돌려주는 것이다.

 

sqlService(Interface).java

public interface SqlService{
    String getSql (String key) throws SqlRetrievalFailureException;
}

 

UserDaoJdbc.java

public class UserDaoJdbc implements UserDao{
    private SqlService sqlService;
    
    public void setSqlService(SqlService sqlService){
        this.sqlService = sqlService;
    }
...
}

이제 모든 메소드에서 인스턴스 변수인 sqlService를 이용해 SQL을 가져오도록 수정한다. SqlService는 모든 DAO에서 서비스 빈을 사용하게 만들 것이다. 따라서 키 이름이 DAO별로 중복되지 않게 해야 한다.

 

UserDao.java 일부

public void add(User user){
    this.jdbcTemplate.update(this.sqlService.getSql("userAdd"),
        user.getId(), user.getName() ...);
}
 
...

 

스프링 설정을 사용하는 단순 SQL 서비스

SqlService 인터페이스에는 어떤 기술적인 조건이나 제약사항도 담겨 있지 않다. SqlService 인터페이스를 구현하는 클래스를 만들고 Map 타입 프로퍼티를 추가한다. 그리고 맵에서 SQL을 읽어서 돌려주도록 SqlService의 getSql() 메소드를 구현해보자.

 

SimpleSqlService.java

SimpleSqlService implements SqlService{
    private Map<String, String> sqlMap;
    
    public void setSqlMap(Map<String, String> sqlMap){
        this.sqlMap = sqlMap;
    }
    
    public String getSql(String key) throws SqlRetrievalFailureException{
        String sql = sqlMap.get(key);
        if (sql == null) throw new SqlRetrievalFailureException(key + "에 대한 SQL을 찾을 수 없습니다.");
        else return sql;
    }
}

SimpleSqlSrvice 클래스를 빈으로 등록하고 UserDao가 DI 받아 사용하도록 설정해준다. SQL 정보는 이 빈의 프로퍼티ㅐ에 <map>을 이용해 등록한다.

 

현재 코드와 설정만 놓고 보자면 앞에서 사용했던 방법과 별로 다를 게 없어 보인다. 하지만 이제 UserDao를 포함한 모든 DAO는 SQL을 어디에 저장해두고 가져오는지에 대해서는 전혀 신경 쓰지 않아도 된다. 동시에 sqlService 빈에는 DAO에는 전혀 영향을 주지 않은 채로 다양한 방법으로 구현된 SqlService 타입 클래스를 적용할 수 있다.