https://extsdd.tistory.com/112
자 저번 시간까지 이클립스와 Maria DB를 MyBatis라는 친구로 연동을 시켰다. 아. 전에 MyBatis가 먼지 설명을 안했는데 간단하게 설명하자면 과거에 JAVA코드로 쿼리로 DB에 있는 데이터를 조회하려고 하면, 소스안에 자바코드도 껴져있고, SQL쿼리문도 껴져있고, 개판처럼 만들어 놓고 썻기때문에 관리도 안됐던 문제가 있었다. 사실 머 문제라기보단 조 불편하고 효율적이지 않았던거지!
MyBatis
하지만 쿼리문과 자바 구문들을 분리해놓으면서, 자바는 자바구문 끼리, 그리고 쿼리는 xml파일에 두고 namespace로 Mapping시켜 사용하게 하는 MyBatis방식이 나왔고 xml에 IF같은 태그가 가능해지면서 쿼리문도 동적으로 변화를 줄 수 있게 됐다. 그러면서 모든 Case의 쿼리를 안짜도 되고 이렇게 Flexible하게 코드를 짤 수 있으니 그야말로 효율을 극대화한 프레임워크라고 할 수 있다. 코드를 보면 머 iBatis도 있고 MyBatis도 있는데 iBatis는 아파치랑 붙어먹을때 쓰던 프레임워크고 구글로 넘어오면서 Mybatis를 쓰게 됐다고만 알고 있으면 될 것 같다.
잔말 그만하고, 일단 MyBatis로 이클립스랑 마리아DB랑 연동이 안 된 사람은 위 포스팅으로 가서 설정을 하고오자. 다 된 사람이라면 이제 본격적으로 시작해보자.
1. 데이터 조회 구조 파악하기.
자. 내가 웹 서비스에대해서 아무것도 모르고 회사에 와서 스프링 하세요! 했을때 완전 No Base인 상태에서 코드들을 따라갔을때 너무 복잡하게 느껴졌었다. 이게 왜 이렇게 되고, 이 코드가 어디랑 연관이 있고.. 거대한 숲에 혼자 떨어져서 내가 어디서 뭘하는지 모르는 그런 기분이었다.
이제 일을 하다보니 스프링 구조에대해서 어느정도 윤곽이 잡혔고, 이제는 하늘에서 숲을 내려다 보듯이 구조와 흐름이 보이기 시작했다. 처음부터 내가 이런 윤곽을 알고 했으면 더 수월했을텐데, 이렇게 초보자들이 볼 수 있게 정리된 글들이 없었다 ㅠㅠ.. 그래서 내가 정리했다.! 초보자 시점에서 전체 윤곽을 그린 모습을!
좀 어려울 수도 있는제 한번 흐름만 파악해보자.
와..! 엄청 어렵고 복잡하다. 난생 웹서비스를 처음해보는 나는 이런 전체의 모습이 아닌 코드를 한줄한줄 따라가면서 고대 문자 해석하듯이 했을때는 얼마나 어려웠을까 생각해보길 바란다..ㅠㅠ
어렵게 보이지만 스프링 웹 서비스의 구조는 딱 저렇다. 저기서 크게 벗어나지 않는다. 저기서 한개의 기능을 만들면 이제 복붙이고, 저 구조만 지켜지면 뭐든지 할 수 있다. 그러니까 포기하지 말고 따라오길 바란다.
1 . 자 1번 박스는 스프링 프로젝트라고 생각하면 된다. 저기 16번 박스에 있는 DB를 제외하면 다 스프링 프로젝트 내의 소스로 돌리는 것이다. 1번은 별거 없다. 제일 큰 구조부터 접근해보기 위해서 번호를 넣어봤다 ㅎㅎ
Controller / 컨트롤러
2. 아까까진 ice breaking이었고 여기서부터 진짜 시작이다.자 Controller 이름만 봐도 뭔가 파박! 떠오른다. 뭔가 조작하고 제어하고, 뭔가..! 뭔가를 수행하는넘! 맞다 Controller는 business(Biz) 적인 로직을 처리한다. Biz 로직이 머냐구!? 말그대로 사람이 이해할만한 "동작" 정도로 해석하면 될 꺼같은데 이런거다. 컴퓨터는 뭔가 데이터를 삽입, 삭제, 수정, 조회, 머 이런 원초적인 기능밖에 못하는데 컴퓨터가 아닌 우리 사람입장에서 기능이란, 뭔가 고객 데이터를 조회하거나, 게시판의 글 목록들을 불러오거나, 게시글을 클릭했을때 그 글을 보여줘! 처럼 사람이 이해할만한 "기능" 이라고 생각하면 되는 것이다. 좀 감이 왔나? 다시 정리하면 우리 인간들에게 기능다운 기능이란 삽입,삭제 뭐 이런 재미없는 것들을 말하는게 아니라, 그런 조회, 삽입, 삭제등 이런 컴퓨터의 기능들을 이용해 우리 인간세계게에서 통용될 수 있는 그런 기능을 작성하는 것이다. 예를들면 할머니한테 할머니! 제가 DB에서 Select 기능을 수행했어요! 하면 할머니는 뭔 개소리냐? 하고 반문할꺼지만, 할머니! 제가 어떤 예약이 있는지 조회했어요!! 한다면 할머니가 이해할 것이다. 차이점이 느껴지나..! 컴퓨터에는 "예약을 조회한다"는 기능이 없다! 예약정보가 어디있는지, 이걸 조회할지 삭제할지 이런건 다 인간이 정한거고 이 기능을 만들기 위한 로직을 Business 로직이라고 한다.
자 Controller의 결론을 말하자면, 기능을 작성하는 곳이다! 이정도로 이해하면 될 것 같고, 로직은 그냥 짜면 된다 if어쩌고~ 아니면 저쩌고~ 쭈르륵 작성하는데 DB에서 데이터를 가져와야할꺼 아냐? 그 데이터를 가지고 오기 위한 놈이 Service(서비스)라는 놈이다. 예전 포스팅에서도 이 서비스란 놈을 좀 설명했는데, 특정 기능들의 데이터를 서리하는 집합체라고 생각하면 된다. 더 raw하게 써논 설명을 보고 싶다면
위 포스팅 중간쯤에 서비스란 무엇인가 이걸 참조하면 된다. 아무튼 서비스가 실제로 데이터들을 불러오기 위한 객체인데, 우리 Controller에서 필요한 정보들을 조회하기 위한 서비스를 미리 호출해놓고 쓴다.
3. 자 3번 박스를 보면 그 컨트롤러의 실제 예시 코드를 캡쳐해 놓은 것이다. 구조를 보면 컨트롤러 fwdService를 미리 호출해놓고 5번 박스를 보면 그 객체로 8번 박스의 함수를 호출해서 데이터를 조회하는 것을 볼 수 있다. 조회된 데이터들은 17번에 있는 resultList에 담기고 그 밑으로 이 조회된 데이터를 기준으로 Biz 로직을 작성하면 하나의 기능이 되는 것이다.
4. 자 보면 컨트롤러에 fwdService가 선언되었다. 앞으로 이 Controller에선 fedService가 할 수 있는 기능을 이용할 수 있게된다. 필요한 서비스를 이렇게 불러놓으면 되는 것이다.
5. 4번에서 선언한 서비스를 실제로 로직에서 불러와 사용한 것이다.
6. 그 서비스는 service 패키지 경로안에 ~~~~Service.java 형식으로 파일이 있을 것이다. 4번 박스처럼 fwdService라면 FwdService.java 라는 파일로 만들어 놨을 것이다.
7. 이게 이 Service 파일의 구조인데, 보면 함수 이름정도만 선언되어있다.. 즉, 여기서 로직이 작성되진 않는다. 서비스에서 처리하는 로직은 ServiceImpl.java에서 구현된다. Impl 는 Implement정도로 해석하면되고, 말그대로 구현이다.
8. 보면 아까 7번 서비스에서 선언된 기능을 Biz 로직단에서 호출하는 것을 볼 수 있다. 아까 말했듯이 이 구현은 ServiceImple.java에서 구현된다고 했으니까 계속 알아보자.
9. 자 ServiceImpl.java로 오면 크게 구조가 DAO라는 친구가 선언되어 있다. 이건 나중에 알아보도록하고 실제 코드를 보자
10. 자 실제로 serviceImpl.java를 와보자.보면 아까 우리가 7,8번에서 언급한 함수가 여기도 선언되어있다. 맞다 Interface로 선언된 친구다. 그 실제 로직을 보자
11 .바로 이거다 DAO라는 친구를 이용해 selectList를 해오는 것이다. 보면 fwd.selectTargetUrl 이라는 쿼리를 수행하라고 되어있는데, fwd는 xml파일의 namespace를 말하는 것이고, 이 네임스페이스로가면 selectTargetUrl이라는 쿼리문이 있을것이다.
DAO (Data Access Object)
먼저 DAO라는 친구를 알고가야한다. 발음은 "다오"라고 부른다. 개귀엽다 ㅎㅎ.. 암튼 이친구가 뭐하는 역할이냐면,DB와 COnnection을 이용해 데이터를 가져와 주는 놈이다. 과거에 코딩을 좀 해본사람이라면 Java 소스안에서 Connection을 맽고 조회하고, Connection을 끝고,, 이런 불필요한 코드들이 아주 많았고, 한번 조회할때마다 이 커넥션을 게속 작성해야해서 너무 불편했다. 하지만 이제 DAO라는 친구에서 조회할꺼면 select, 삽입할꺼면 insert 등.. 함수만 치면 이런 커넥션 알아서 맺고 조회하면 알아서 끝고 다해주는 친구다. 세부 구현은 어떻게됐는지 몰라도 된다. 특급 개발자들이 다 잘 구현해 놓았을테니까 ㅎㅎ 우리는 사용만 하면된다.
12-1. 저 쿼리문을 MyBatis라는 저 fwd.selectTargetUrl이라는 문구를 보고 어디에 있는 파일에서 어떤 구문을 조회할지 Mapping해주는 역할을 해준다.
12-2. 그럼 이 마이바티스를 통해서 fwd네임스페이스니 fwdSql.xml이란 파일을 뒤져서 거기있는 selectTragerUrl이란 쿼리문을 찾아온다.
13. 그게바로 이 13번 박스에 있는 id로 매핑시켜 찾아오는것이다.
14. 자 그럼 마이바티스가 어떤 쿼리를 조회할지도 알아낸 것이다.
15. 이제 본젹적으로 아까 말했던 DB랑 Connection을 맺는 DAO라는 친구를 통해, 마이바티스에서 넘겨받은 쿼리 구문들을 본격적으로 조회한다.
16. DAO를 통해 DB에 접속한 단계이다. Connection이 맺어지면 아까 받은 쿼리들을 조회하고 그 결과를 DAO가 다시 가져온다. 그럼 역순으로 다시 11번 박스를 보면 그 조회 결과가 리턴되고, 이 리턴받은 데이터들은 8번 박스에 나온 저 함수에서 리턴되 결국 17번 박스에 있는 변수로 들어가게 되는것이다.
17. 결국 조회된 데이터가 resultList라는 변수에 들어가고 우린 그걸 사용하면 된다.
결론 : 기능은 ~Controller.java, ~Service.java, ~ServiceImpl.java, ~VO.java(필요하다면), ~Sql.xml 가 세트로 다닌다고 생각하면 된다. 이렇게 한사이클 돌았는데, 나머지도 보면 다~~똑같다, 이해가 안돼도 되니 느낌만 알고 가도록 하자
2. CommonDAO.java 추가
자 먼저 공통적으로 DB에서 커넥션을 맺어줄 DAO를 만들어보자!
자 cmmn 패키지에 오른쪽마우스를 눌러 NEW-Class를 눌러주고 패키지 경로를 하나 만들어주자.
자 패키지 경로명에 .dao를 추가해서 만들어주자.
만들어진 dao 패키지에 새 클래스를 만들어주자.
뚜둔~ 이름은 CommonDAO로 만들어주자!
보면 dao 패키지 밑에 CommonDAO.java 파일이 생겼다! 열어보자!
귀여운 파일이 생겼다. 소스를 넣어 생기를 불어넣어주자.
package cpservice.cmmn.dao;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Repository;
import egovframework.rte.psl.dataaccess.EgovAbstractMapper;
@Repository("CommonDAO")
public class CommonDAO extends EgovAbstractMapper {
/**
* 입력 처리 SQL mapping 을 실행한다.
*
* @param queryId - 입력 처리 SQL mapping 쿼리 ID
*
* @return DBMS가 지원하는 경우 insert 적용 결과 count
*/
public int insert(String queryId) {
return super.getSqlSession().insert(queryId);
}
/**
* 입력 처리 SQL mapping 을 실행한다.
*
* @param queryId - 입력 처리 SQL mapping 쿼리 ID
* @param parameterObject - 입력 처리 SQL mapping 입력 데이터를 세팅한 파라메터 객체(보통 VO 또는 Map)
*
* @return DBMS가 지원하는 경우 insert 적용 결과 count
*/
public int insert(String queryId, Object parameterObject) {
return super.getSqlSession().insert(queryId, parameterObject);
}
/**
* 수정 처리 SQL mapping 을 실행한다.
*
* @param queryId - 수정 처리 SQL mapping 쿼리 ID
*
* @return DBMS가 지원하는 경우 update 적용 결과 count
*/
public int update(String queryId) {
return super.getSqlSession().update(queryId);
}
/**
* 수정 처리 SQL mapping 을 실행한다.
*
* @param queryId - 수정 처리 SQL mapping 쿼리 ID
* @param parameterObject - 수정 처리 SQL mapping 입력 데이터(key 조건 및 변경 데이터)를 세팅한 파라메터 객체(보통 VO 또는 Map)
*
* @return DBMS가 지원하는 경우 update 적용 결과 count
*/
public int update(String queryId, Object parameterObject) {
return super.getSqlSession().update(queryId, parameterObject);
}
/**
* 삭제 처리 SQL mapping 을 실행한다.
*
* @param queryId - 삭제 처리 SQL mapping 쿼리 ID
*
* @return DBMS가 지원하는 경우 delete 적용 결과 count
*/
public int delete(String queryId) {
return super.getSqlSession().delete(queryId);
}
/**
* 삭제 처리 SQL mapping 을 실행한다.
*
* @param queryId - 삭제 처리 SQL mapping 쿼리 ID
* @param parameterObject - 삭제 처리 SQL mapping 입력 데이터(일반적으로 key 조건)를 세팅한 파라메터 객체(보통 VO 또는 Map)
*
* @return DBMS가 지원하는 경우 delete 적용 결과 count
*/
public int delete(String queryId, Object parameterObject) {
return super.getSqlSession().delete(queryId, parameterObject);
}
//CHECKSTYLE:OFF
/**
* 명명규칙에 맞춰 selectOne()로 변경한다.
* @deprecated select() 메소드로 대체
*
* @see EgovAbstractMapper.selectOne()
*/
//CHECKSTYLE:ON
@Deprecated
public Object selectByPk(String queryId, Object parameterObject) {
return super.getSqlSession().selectOne(queryId, parameterObject);
}
/**
* 단건조회 처리 SQL mapping 을 실행한다.
*
* @param queryId - 단건 조회 처리 SQL mapping 쿼리 ID
*
* @return 결과 객체 - SQL mapping 파일에서 지정한 resultType/resultMap 에 의한 단일 결과 객체(보통 VO 또는 Map)
*/
public <T> T selectOne(String queryId) {
return super.getSqlSession().selectOne(queryId);
}
/**
* 단건조회 처리 SQL mapping 을 실행한다.
*
* @param queryId - 단건 조회 처리 SQL mapping 쿼리 ID
* @param parameterObject - 단건 조회 처리 SQL mapping 입력 데이터(key)를 세팅한 파라메터 객체(보통 VO 또는 Map)
*
* @return 결과 객체 - SQL mapping 파일에서 지정한 resultType/resultMap 에 의한 단일 결과 객체(보통 VO 또는 Map)
*/
public <T> T selectOne(String queryId, Object parameterObject) {
return super.getSqlSession().selectOne(queryId, parameterObject);
}
/**
* 결과 목록을 Map 을 변환한다.
* 모든 구문이 파라미터를 필요로 하지는 않기 때문에, 파라미터 객체를 요구하지 않는 형태로 오버로드되었다.
*
* @param queryId - 단건 조회 처리 SQL mapping 쿼리 ID
* @param mapKey - 결과 객체의 프로퍼티 중 하나를 키로 사용
*
* @return 결과 객체 - SQL mapping 파일에서 지정한 resultType/resultMap 에 의한 단일 결과 객체(보통 VO 또는 Map)의 Map
*/
public <K, V> Map<K, V> selectMap(String queryId, String mapKey) {
return super.getSqlSession().selectMap(queryId, mapKey);
}
/**
* 결과 목록을 Map 을 변환한다.
* 모든 구문이 파라미터를 필요로 하지는 않기 때문에, 파라미터 객체를 요구하지 않는 형태로 오버로드되었다.
*
* @param queryId - 단건 조회 처리 SQL mapping 쿼리 ID
* @param parameterObject - 맵 조회 처리 SQL mapping 입력 데이터(조회 조건)를 세팅한 파라메터 객체(보통 VO 또는 Map)
* @param mapKey - 결과 객체의 프로퍼티 중 하나를 키로 사용
*
* @return 결과 객체 - SQL mapping 파일에서 지정한 resultType/resultMap 에 의한 단일 결과 객체(보통 VO 또는 Map)의 Map
*/
public <K, V> Map<K, V> selectMap(String queryId, Object parameterObject, String mapKey) {
return super.getSqlSession().selectMap(queryId, parameterObject, mapKey);
}
/**
* 결과 목록을 Map 을 변환한다.
* 모든 구문이 파라미터를 필요로 하지는 않기 때문에, 파라미터 객체를 요구하지 않는 형태로 오버로드되었다.
*
* @param queryId - 단건 조회 처리 SQL mapping 쿼리 ID
* @param parameterObject - 맵 조회 처리 SQL mapping 입력 데이터(조회 조건)를 세팅한 파라메터 객체(보통 VO 또는 Map)
* @param mapKey - 결과 객체의 프로퍼티 중 하나를 키로 사용
* @param rowBounds - 특정 개수 만큼의 레코드를 건너띄게 함
*
* @return 결과 객체 - SQL mapping 파일에서 지정한 resultType/resultMap 에 의한 단일 결과 객체(보통 VO 또는 Map)의 Map
*/
public <K, V> Map<K, V> selectMap(String queryId, Object parameterObject, String mapKey, RowBounds rowBounds) {
return super.getSqlSession().selectMap(queryId, parameterObject, mapKey, rowBounds);
}
//CHECKSTYLE:OFF
/**
* 명명규칙에 맞춰 selectList()로 변경한다.
*
* @see EgovAbstractMapper.selectList()
* @deprecated List<?> 메소드로 대체
*/
//CHECKSTYLE:ON
@Deprecated
public List<?> list(String queryId, Object parameterObject) {
return super.getSqlSession().selectList(queryId, parameterObject);
}
/**
* 리스트 조회 처리 SQL mapping 을 실행한다.
*
* @param queryId - 리스트 조회 처리 SQL mapping 쿼리 ID
*
* @return 결과 List 객체 - SQL mapping 파일에서 지정한 resultType/resultMap 에 의한 결과 객체(보통 VO 또는 Map)의 List
*/
public <E> List<E> selectList(String queryId) {
return super.getSqlSession().selectList(queryId);
}
/**
* 리스트 조회 처리 SQL mapping 을 실행한다.
*
* @param queryId - 리스트 조회 처리 SQL mapping 쿼리 ID
* @param parameterObject - 리스트 조회 처리 SQL mapping 입력 데이터(조회 조건)를 세팅한 파라메터 객체(보통 VO 또는 Map)
*
* @return 결과 List 객체 - SQL mapping 파일에서 지정한 resultType/resultMap 에 의한 결과 객체(보통 VO 또는 Map)의 List
*/
public <E> List<E> selectList(String queryId, Object parameterObject) {
return super.getSqlSession().selectList(queryId, parameterObject);
}
/**
* 리스트 조회 처리 SQL mapping 을 실행한다.
*
* @param queryId - 리스트 조회 처리 SQL mapping 쿼리 ID
* @param parameterObject - 리스트 조회 처리 SQL mapping 입력 데이터(조회 조건)를 세팅한 파라메터 객체(보통 VO 또는 Map)
* @param rowBounds - 특정 개수 만큼의 레코드를 건너띄게 함
*
* @return 결과 List 객체 - SQL mapping 파일에서 지정한 resultType/resultMap 에 의한 결과 객체(보통 VO 또는 Map)의 List
*/
public <E> List<E> selectList(String queryId, Object parameterObject, RowBounds rowBounds) {
return super.getSqlSession().selectList(queryId, parameterObject, rowBounds);
}
/**
* @description Map 으로 리스트를 조회한다. 단 페이징 로직을 수행한다.
*/
/*public <E>List<E> selectListWithPaging(String id ,CmMultiVO vo) {
MappedStatement ms = super.getSqlSession().getConfiguration().getMappedStatement(id);
//전체 ROW Counting을 위해 기존 SQLMap 객체를 카피하고 PaginationStatementInterceptor에서 excute하기전에 쿼리를 조작함
if(!super.getSqlSession().getConfiguration().hasStatement(id+"$$_TOTCNT___$$")){
MappedStatement.Builder msBuilder = new MappedStatement.Builder(
ms.getConfiguration(), id+"$$_TOTCNT___$$", ms.getSqlSource(),
ms.getSqlCommandType());
msBuilder.resource(ms.getResource());
msBuilder.fetchSize(ms.getFetchSize());
msBuilder.statementType(ms.getStatementType());
msBuilder.keyGenerator(ms.getKeyGenerator());
if (null != ms.getKeyProperties()) {
for (String keyProperty : ms.getKeyProperties()) {
msBuilder.keyProperty(keyProperty);
}
}
msBuilder.timeout(ms.getTimeout());
msBuilder.parameterMap(ms.getParameterMap());
ResultMap.Builder rmBuilder = new ResultMap.Builder(ms.getConfiguration(), ms.getId()+"$$_TOTCNTMAP___$$", Integer.class, new ArrayList<ResultMapping>());
ArrayList<ResultMap> rsList = new ArrayList<ResultMap>();
rsList.add(rmBuilder.build());
msBuilder.resultMaps(rsList);
msBuilder.cache(ms.getCache());
synchronized(super.getSqlSession().getConfiguration()) {
super.getSqlSession().getConfiguration().addMappedStatement(msBuilder.build());
}
}
//신규로 생성한 sql을 실행, 실행 시 PaginationStatementInterceptor sql을 조작함
int totCnt = super.getSqlSession().selectOne(id+"$$_TOTCNT___$$", vo);
RowBounds rowBounds = null;
if(vo.getPaginationInfo() != null){
vo.getPaginationInfo().setTotalRecordCount(totCnt);
rowBounds = new RowBounds(vo.getPaginationInfo().getFirstRecordIndex(), vo.getPaginationInfo().getRecordCountPerPage());
}else{
rowBounds = new RowBounds();
}
//실행 시 PaginationStatementInterceptor sql을 조작함
List<E> resultList = super.getSqlSession().selectList(id, vo, rowBounds);
return resultList;
}*/
/**
* 부분 범위 리스트 조회 처리 SQL mapping 을 실행한다.
* (부분 범위 - pageIndex 와 pageSize 기반으로 현재 부분 범위 조회를 위한 skipResults, maxResults 를 계산하여 ibatis 호출)
*
* @param queryId - 리스트 조회 처리 SQL mapping 쿼리 ID
* @param parameterObject - 리스트 조회 처리 SQL mapping 입력 데이터(조회 조건)를 세팅한 파라메터 객체(보통 VO 또는 Map)
* @param pageIndex - 현재 페이지 번호
* @param pageSize - 한 페이지 조회 수(pageSize)
*
* @return 부분 범위 결과 List 객체 - SQL mapping 파일에서 지정한 resultType/resultMap 에 의한 부분 범위 결과 객체(보통 VO 또는 Map) List
*/
public List<?> listWithPaging(String queryId, Object parameterObject, int pageIndex, int pageSize) {
int skipResults = pageIndex * pageSize;
//int maxResults = (pageIndex * pageSize) + pageSize;
RowBounds rowBounds = new RowBounds(skipResults, pageSize);
return super.getSqlSession().selectList(queryId, parameterObject, rowBounds);
}
/**
* SQL 조회 결과를 ResultHandler를 이용해서 출력한다.
* ResultHandler를 상속해 구현한 커스텀 핸들러의 handleResult() 메서드에 따라 실행된다.
*
* @param queryId - 리스트 조회 처리 SQL mapping 쿼리 ID
* @param handler - 조회 결과를 제어하기 위해 구현한 ResultHandler
* @return
*
* @return 결과 List 객체 - SQL mapping 파일에서 지정한 resultType/resultMap 에 의한 결과 객체(보통 VO 또는 Map)의 List
*/
public void listToOutUsingResultHandler(String queryId, ResultHandler handler) {
super.getSqlSession().select(queryId, handler);
}
}
저 소스덩어리를 이어서 CommonDAO.java에 넣어주자.
넣으면 이런식으로 쭉~ 코드들이 들어갈텐데 저장을 눌러주자. 여기 내용들은 머 이해할 필요까진 없다. 이미 Mapper 역할을 할 EgovAbstractMapper를 상속받아 만들어진 것이기 때문에 그냥 아~ 여기 데이터에 접근해 조회, 삭제, 삽입, 변경 하는 함수들이 들어가는 구나 ~ 정도만 알고있으면 된다.
3. Service 추가
자 이제 내 DB에서 데이터를 읽어올껀데 그렇게 하기위한 파일들을 작성해보자 먼저 service 부터 선언할껀데 위 service 패키지를 오른쪽마우스 눌러 New버튼 클릭 후 Class를 눌러주자. 그리고 앞으로 파일 생성할 일이 많으니 클래스를 만들라~ 하면 이정도는 외워두길 바란다.
자 이름은 FwdService로 정해주고 Finish를 눌러주장!
그럼 빈 파일이 생성된다.
아래 코드를 복붙하고 저장해주자.
package cpservice.fwd.service;
import java.util.List;
import egovframework.rte.psl.dataaccess.util.EgovMap;
public interface FwdService {
/**
* URL 조회
* @param FwdVO 검색조건
* @return List<?> URL 목록
*/
List<EgovMap> selectTargetUrl(FwdVO vo);
}
짜잔 서비스가 생겼다~ 이제 저 서비스에 있는 selectTargetUrl 기능을 구현해보자.
4. ServiceImpl 구현
자 ServiceImpl.java를 만들껀데 다른점이라면 Impl파일은 서비스 패키지가 아니라 impl 패키지에 추가한다는 점이다. 꼭.! 경로 헷살리지 않도록 하자, 만약 service 캐키지 밑에 impl패키지가 없으면 만들어라, 만드는거 New누르고 Package누르면 만들 수 있다. 암트 다시돌아와서 impl 패키지를 오른쪽마우스 누르고 New-Class를 눌러 만들어주자. 이제 파일 만드는 것은 설명안해두 되겠지..!?
파일 이름은 FwdServiceImpl로 만들어주자
짜잔..! 생겼다..!
개 귀여운 파일이 생겼다. 내용을 넣어주자
package cpservice.fwd.service.impl;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import cpservice.cmmn.dao.CommonDAO;
import cpservice.fwd.service.FwdService;
import cpservice.fwd.service.FwdVO;
import egovframework.rte.fdl.cmmn.EgovAbstractServiceImpl;
import egovframework.rte.psl.dataaccess.util.EgovMap;
@Service("fwdService")
public class FwdServiceImpl extends EgovAbstractServiceImpl implements FwdService {
/** CommonDAO */
// TODO mybatis 사용
@Resource(name = "CommonDAO")
private CommonDAO dao;
/**
* 요청에 맞는 URL을 조회하는 쿼리
*/
@Override
public List<EgovMap> selectTargetUrl(FwdVO vo) {
// TODO Auto-generated method stub
return dao.selectList("fwd.selectTargetUrl", vo);
}
}
코드 설명을 간단히 해보자면 이 서비스의 selectTargerUrl 함수를 돌리면 selectTargetUrl 쿼리를 조회해서 뱉어주는 역할을한다.
5. fwdSql.xml 작성
자 이제 쿼리문이 작성될 fwdSql.xml을 작성할 것이다.
sqlmap 폴더에 폴더를 하나 만들어주자.
폴더 이름은 fwd로 정해주자!
만들어진 fwd 폴더에 XML파일을 추가해주자. New - Other 클릭!
XML을 검색해주고 밑에 XML File을 눌러 NEXT!
파일명은 fwd.xml로 추가!
자 fwd폴더 밑에 fwd.xml이 생겼다 ㅎㅎ
파일을 열어보면 꼴랑 한줄 들어있다. 밑에 소스를 넣어주자!
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="fwd">
<select id="selectTargetUrl" parameterType="cpservice.fwd.service.FwdVO" resultType="egovMap">
/** selectTagetUrl (fwdSql.xml) 마스터코드 목록 조회*/
SELECT PM.SEQ_NO
,PM.URL_STR
,PM.DISC_STR
,PM.USE_YN
FROM prd_mst PM
WHERE PM.SEQ_NO = #{reqParam}
</select>
</mapper>
통째로 복붙하고 저장하자
1. 소스를 좀 살펴보자. 먼저 1번 박스가 네임스페이스다. 자바 소스에서 쿼리를 불러올때 fwd.selectTargetUrl 이라는쿼리를 불러올때 앞에 fwd가 이 네임스페이스다. 긍까 네임스페이스가 Fwd인 파일에있는 selectTargetUrl이란 쿼리를 찾아오겠다~ 이말이다.
2. 2번 박스를 보면 id="selectTargetUrl"로 되어있는데 1번에서 설명과 마찬가지로 해당 쿼리의 고유 ID다, 이 쿼리를 불러오기위해선 저 ID를 호출해야하는 것이다.
3. 이건 파라미터 타입이다. 이 쿼리를 실행하기전에 여기다 객체를 집어 넣을 수 있다. 넣어서 뭐하냐고?!! 아주 중요한 질문인데, 자 만약에 넣을 수 없다면, 저 쿼리를 봤을때 5번 박스가 있는 Where 절에 SEQ_NO를 1일때, 2일때, 3일때 똑같은 쿼리를 여러번 만들어 줘야하는데 #{reqParam} 이란 변수명이 보이는가!?, 저 파라미터로 가져온 FwdVO안에 있는 저 변수명을 가지고 올 수 있는 것이다. 이 쿼리를 요청할때 함수 구문에 VO를 넣어주는 것이 보일텐데, 여기서 그 VO에 넣어져 있는 값을 사용할 수 있는 것이다. 만약에 reqParam에 1이 들어있으면 WHERE PM.SEQ_NO = 1 로 쿼리가 설정되고, reqParam에 2가 들어오면 WHERE PM.SEQ_NO가 2로 셋팅되는식으로, 쿼리문은 하나인데 들어오는 값들에 의해서 쿼리가 동적으로 변화하면서 아주 유연한 쿼링이 가능케하는 것이다. 이게 바로 MyBatis의 강점이라고 할 수 있다.
4. 이건 조회하고나서 어떤 데이터 형으로 뱉어줄지다. 머 String이라고 하면 문자열로 나가는등, 원하는 타입으로 선언하면 된다. 하지만, DB특성사 조회하면 여러 컬럼의 형태로 조회되기 때문에 Map형태로 뽑아줘야한다. 이 전자정부프레임워크에는 egovMap이라는 데이터형이 만들어져 있는데 이 Map을 쓰는게 가장 사용하기 좋다.
5. #{변수명} 이라고 쓰면 3번 박스에서 선언된 VO를 읽어와 그 VO에 들어온 정보들을 쓸 수 있다.
6. Controller.java 수정
자 이제 데이터를 조회할 준비는 다됐고 실제 Biz 로직단에서 반영되도록 소스를 수정해주자!
자 저번까지 작성한 컨트롤러의 코드를 보면 그냥 하드코딩이 되어있다. 들어오는 파라미터가 1이면 네이버를, 2면 다음, 3이면 구글 이렇게 값자체가 소스에 박혀있어 나중에 사후 관리측면에서 문제가 되는 소스들이다.
이걸 이제 DB에서 불러오는걸로 바꿔보자 아예 통째로 소스를 부어 넣어주자.
package cpservice.fwd.web;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import cpservice.fwd.service.FwdService;
import cpservice.fwd.service.FwdVO;
import egovframework.rte.psl.dataaccess.util.EgovMap;
@Controller
public class FwdController {
/** 고객관리 서비스 */
@Resource(name = "fwdService")
private FwdService fwdService;
@RequestMapping(value = "/reqUrl.do")
public String selectReqUrl(@ModelAttribute("searchVO") FwdVO searchVO, ModelMap model) throws Exception {
List<EgovMap> resultList = fwdService.selectTargetUrl(searchVO);
String URL = (String) resultList.get(0).get("urlStr");
return "redirect:http://"+URL;
}
}
자 이소스로 복붙하고 저장하자.
소스를 살펴보자면
1. fwd 관련 쿼리를 사용할 것임으로 fwdService를 호출해주자.
2. 이 호출한 서비스에서 selectTargetUrl 함수를 돌려 쿼리를 실행하주고, 그걸 resultList에 넣어주자. 자 또하나 보자면 함수의 파라미터로 searchVO를 넣어줬는데. 이건 사용자가 이 reqUrl.do를 요청할때 넣어준 데이터들이 들어있는 객체다. 이 VO를 쿼리돌리는 저함수에 넣어주면 분명 저 FwdVO안에 있는 reqParam이라는 사용자가 요청한 값도 받아올 것이다.
3. 자 쿼리를 돌려 조회된 결과가 resultList인데 사실 이 쿼리는 여러행이 나오지 않는다. reqParam에 매칭되는 목적지 URL은 하나이기 때문에 단건 조회인데, 그럼 또 다른 함수를 타야하는데 보통 다건 조회가 많으니 그냥 List형태로 받아오도록했다.. 그래서 resultList.get(0)을 통해서 맨 첫번째 결과를 가져와 get("urlStr")을 통해서 조회한 쿼리에서 urlStr 칼럼을 가져오겠다는 것이다.
4. 마지막으로 redirect://http:// 와 URL을 붙혀 리턴해주면 DB를 기반으로 조회하는 포워딩 시스템이 완성된다!
7. 결과 확인
자 이제 서버를 한번 Clean뒤 Run을 해보면 서비스가 동작할텐데 오류없이 실행되면 잘 된거다. 다시 http://localhost:8080/reqUrl.do?reqParam=3이렇게 테스트로 조회를해보고 끝에 reqParam= 뒤에 값을 1,2,3등으로 변경하면서 엔터를 눌러봐라. 만약 각 DB에 넣은 정보에 맞는 페이지를 호출하면 잘 된거다.
혹시 오류가 나는사람들은 다음 포스팅에 할 Debug과정을 통해서 한번 값들이 어떻게 이동하는지 살펴보도록 하자.
#스프링 #전자정부프레임워크 #마리아DB #이클립스 #마이바티스 #컨트롤러 #DAO #원리 #구조 #MariaDB #ByBatis #Controller