UserDao를 꾸준하게 리팩토링 했지만, 그럼에도 남은 욕심은 Dao에서 SQL을 분리하는 것이다.
add() 메서드가 유저를 추가하는 메서드가 된다는 것은 변함없겠지만, column등이 추가 될 수 있고 그에 따라 Dao에 담은 SQL도 변환해줘야 한다.
이렇게 만드는 것 보다는 SQL을 분리해 다른 위치에 두고 작업하는 것이 더 좋을 것 같다.
XML 설정을 이용한 분리
가장 쉬운 방법은 XML로 추출하는 것이다.
XML을 통해서 빈으로 값을 주입할 수 있기 때문이다.
우선 Dao add() 메서드부터 작업을 해보자.
private final String sqlAdd;
일단 이렇게 add() 메서드를 주입받을 수 있도록 만든다.
그리고 이 sqlAdd를 활용해 add()에서 sql을 사용하도록 하고
public void add(User user){
this.jdbcTemplate.update(
this.sqlAdd,
user.getId(),
user.getName(),
user.getPassword(),
user.getLevel().getValue(),
user.getLogin(),
user.getRecommend(),
user.getEmail()
);
}
xml에 다음 쿼리를 주입해준다.
<bean id="userDao" class="seungkyu.UserDaoImpl">
<constructor-arg ref="dataSource" />
<constructor-arg value = "insert into users(id, name, password, email, level, login, recommend) values(?, ?, ?, ?, ?, ?, ?)"/>
</bean>
일단 이런 식으로 쿼리를 추출할 수 있을 것이다.
하지만 이 방법은 SQL이 많아질수록 DAO에 DI용 속성들을 하나하나 추가해주기가 부담일 것이다.
그러면 이번에는 SQL을 컬렉션으로 담아두는 방법을 사용해보자.
Map 자료구조를 사용한다.
주입을 Map으로 바꾸고
public class UserDaoImpl implements UserDao {
private final JdbcTemplate jdbcTemplate;
private final Map<String, String> sqlMap;
public UserDaoImpl(
DataSource dataSource,
Map<String, String> sqlMap) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.sqlMap = sqlMap;
}
}
주입을 위한 xml도 수정해준다.
<bean id="userDao" class="seungkyu.UserDaoImpl">
<constructor-arg ref="dataSource" />
<constructor-arg name="sqlMap">
<map>
<entry key="add" value="INSERT INTO USERS(id, name, password, email, level, login, recommend) values(?, ?, ?, ?, ?, ?, ?)"/>
</map>
</constructor-arg>
</bean>
우선 add 메서드만 작업해보았다.
SQL 제공 서비스
sql이 분리되기는 했지만, 현재 dao만 봐도 DI의 내용과 SQL이 혼합되어 있다.
sql을 코드랑은 분리했지만, DI의 내용과 섞인 것이다.
외부 파일에서 SQL을 가져오도록 하는 방법을 찾아보자.
우선 SQL 서비스의 인터페이스를 설계해보자.
어쨌든 이제는 분리하기 위해서는 인터페이스를 만들어야 한다는 것을 바로 알 수 있을 것이다.
public interface SqlService {
String getSql(String key);
}
메서드를 여러개 만들지 말고, 이렇게 검색해서 가져올 수 있도록 만들었다.
이거를 바탕으로 UserDao도 다시 모두 수정해주자.
public void add(User user){
this.jdbcTemplate.update(
this.sqlService.getSql("userAdd"),
user.getId(),
user.getName(),
user.getPassword(),
user.getLevel().getValue(),
user.getLogin(),
user.getRecommend(),
user.getEmail()
);
}
public User get(String id){
return jdbcTemplate.queryForObject(
this.sqlService.getSql("userGet"), userRowMapper, id);
}
public List<User> getAll() {
return this.jdbcTemplate.query(
this.sqlService.getSql("userGetAll"), userRowMapper);
}
public void update(User user) {
this.jdbcTemplate.update(
this.sqlService.getSql("userUpdate"),
user.getName(),
user.getPassword(),
user.getLevel().getValue(),
user.getLogin(),
user.getRecommend(),
user.getEmail(),
user.getId()
);
}
public void deleteAll(){
this.jdbcTemplate.update(this.sqlService.getSql("userDeleteAll"));
}
public int getCount() {
Integer result = jdbcTemplate.queryForObject(this.sqlService.getSql("userGetCount"), Integer.class);
return result == null ? 0 : result;
}
이렇게 SQL만 모두 수정해도 보기가 편해진다.
이제 SqlService를 구현해보자.
@RequiredArgsConstructor
public class SimpleSqlService implements SqlService {
private final Map<String, String> sqlMap;
@Override
public String getSql(String key) {
String sql = sqlMap.get(key);
if (sql == null)
throw new RuntimeException("sql not found: " + key);
return sql;
}
}
이제 이거도 빈으로 등록하고, sql들을 주입해줘야 한다.
<bean id="userDao" class="seungkyu.UserDaoImpl">
<constructor-arg ref="dataSource" />
<constructor-arg ref="sqlService"/>
</bean>
<bean id="sqlService" class="seungkyu.SimpleSqlService">
<constructor-arg name="sqlMap">
<map>
<entry key="userAdd" value="INSERT INTO USERS(id, name, password, email, level, login, recommend) values(?, ?, ?, ?, ?, ?, ?)"/>
<entry key="userGet" value="SELECT * FROM USERS WHERE Id = ?"/>
<entry key="userGetAll" value="SELECT * FROM USERS ORDER BY ID"/>
<entry key="userDeleteAll" value="DELETE FROM USERS"/>
<entry key="userGetCount" value="SELECT COUNT(*) FROM USERS"/>
<entry key="userUpdate" value="UPDATE USERS SET name = ?, password = ?, email = ?, level = ?, login = ?, recommend = ? where id = ?"/>
</map>
</constructor-arg>
</bean>
사실 대단한 기술이 추가된 것은 아니기에 성공하겠지만, 그럼에도 불구하고 테스트는 돌려보자.
솔직히 최근에도 쿼리 자체를 잘 만들지는 않지만, 만약 jdbc를 사용하게 된다면 이렇게 추출할 수는 있을 것 같다.
'Spring > 토비의 스프링' 카테고리의 다른 글
[토비의 스프링] 7.3 서비스 추상화 적용 (2) | 2025.07.21 |
---|---|
[토비의 스프링] 7.2 인터페이스의 분리와 자기참조 빈 (2) | 2025.07.11 |
[토비의 스프링] 6.8 트랜잭션 지원 테스트 (5) | 2025.07.07 |
[토비의 스프링] 6.7 어노테이션 트랜잭션 속성과 포인트컷 (1) | 2025.06.30 |
[토비의 스프링] 6.6 트랜잭션 속성 (2) | 2025.06.26 |