{ 세미 발표하러 가는 내 모습 }
날이 따뜻해지고, 벚꽃이 지더니 세미 프로젝트가 내게 찾아왔다.
프로젝트 뭔가 산뜻하고, 열정 넘치고 온갖 갈등이 있을 것 같았지만 막상 겪은 건
“서류 지옥”이더라.
뭐 그리 작성할게 많던지 요구사항 정의서, 명세서, ERD, 와이어프레임, 발표PPT, 기획안 등등 혼자 만드느라 한 3일은 태운 것 같다.
무엇보다 ‘팀장’이 되어버렸다. “내가 왜 팀장이지?” 라는 생각을 했지만, 선택된 아이디어도 내꺼, 사람을 뽑은 것도 나, 나 아니면 다른 누군가한테 맡기기엔 분위기가 그러지 못했다.
그래도 좋은 팀원들 만나서 즐거웠다.
음....팀장이 되었다.
제일 어려웠던 것은 전체적인 상황을 보고 판단해야된다는 것?
우리 조 진행 방식은 각자 기능을 맡고, 그에 대한 프론트와 백엔드를 완성하는 것이었다.
그래서 1, 2일차에 내가 기본 디자인 프레임들과 백엔드 컨트롤러, DAO, DTO를 모두 만들어서 한 번에 Git으로 배포했다.
그리고 진행 일정에 맞춰 백엔드 구현에 들어갔는데, 초반에는 각자 어떻게 진행되는지 알기 어려웠다. 비대면이라 모이기도 어려웠고, Git 풀리퀘스트를 해줘야만 진행 상황을 판단할 수 있었기 때문이다.
그래도 기획, 설계, 구현 모두 내 머리에서 나온 거였고, 밤을 새우면서 보완하고 수정해야 했다. 또 꾸준히 어디까지 진행되고, 어떤 점을 보완해야 하는지 공지도 해줘야 했다. 그러다 보니 팀원분들도 잘 따라오셔서, 의견 제의도 많이 해주시고 점점 합이 맞춰졌다.
그렇게 3주를 거의 프로젝트에 바쳤다.
또, '검색 기능'을 구현하기로 했는데 로직을 짜는게 힘들었다. 사실상 동적쿼리없이 if문만 30개를 넣어서 검색 기능을 만드는 것이니 말이다.
실상 문제는 뭐였냐면, 쿼리문을 자동화하는 거였다. 어떻게 하면 선택 조건에 따라 동적 쿼리를 구현할 수 있을까 고민을 참 많이 했다. 그리고 결국 해냈지...(제가 무식하게 한 겁니다)
[ Controller ]
if (uri.equals("/search.search")) {
request.setCharacterEncoding("utf-8");
// 검색 조건
boolean[] searchType = new boolean[] {false, false, false, false, false};
// 검색 객체
String[] alcholArr = new String[] {"null", "null", "null", "null", "null"}; // 5개
String[] areaArr = new String[] {"null", "null", "null", "null", "null", "null"}; // 6개
String[] gradeString;
String product_name;
float abv;
int grade = 0;
String sql;
String search_text = request.getParameter("search_text");
String search_avb = request.getParameter("avb");
// 검색 조건 분류
if(!(search_text.isEmpty())) { // 입력어가 있다면
product_name = sDAO.getWord(search_text); // 파싱하고
searchType[0] = true; // 입력 검색을 활성화하라
} else {
product_name = null;
}
if(!(search_avb.isEmpty() || search_avb == null)) { // 술 도수를 입력했으면
abv = Float.parseFloat(search_avb); // float로 바꾸고
searchType[4] = true;
} else {
abv = 0;
}
if(!(request.getParameterValues("alchol") == null)) { // 술 종류를 택했다면
searchType[1] = true; // 검색 조건을 활성화하고
for(int i=0; i<request.getParameterValues("alchol").length; i++) {
alcholArr[i] = request.getParameterValues("alchol")[i];
}
} else {
alcholArr = null;
}
if(!(request.getParameterValues("area") == null)) {
searchType[2] = true; // 검색 조건을 활성화
for(int i=0; i<request.getParameterValues("area").length; i++) {
areaArr[i] = request.getParameterValues("area")[i];
}
} else {
areaArr = null;
}
if(!(request.getParameterValues("grade") == null)) {
gradeString = request.getParameterValues("grade");
grade = Integer.parseInt(gradeString[0]);
searchType[3] = true;
} else {
gradeString = null;
}
// 일반 검색
if ((product_name) != null && areaArr == null && gradeString == null && abv == 0 && alcholArr == null ) {
List<SearchDTO> list = sDAO.SearchByText(product_name);
int count = list.size();
request.setAttribute("count", count);
request.setAttribute("list", list);
request.getRequestDispatcher("/Search/searchResult.jsp").forward(request, response);
// 상세 검색
} else if ((product_name) == null && (areaArr != null || gradeString != null || abv != 0 || alcholArr != null || grade != 0)) { // 입력 텍스트 안 보냄
sql = searchApp.getSql(searchType);
List<SearchDTO> list = sDAO.searchDetail(searchType, alcholArr, areaArr, grade, abv, sql);
int count = list.size();
request.setAttribute("count", count);
request.setAttribute("list", list);
request.getRequestDispatcher("/Search/searchResult.jsp").forward(request, response);
} else if ((product_name) != null && (areaArr != null || gradeString != null || abv != 0 || alcholArr != null || grade != 0)) { // 입력 텍스트 보냄
sql = searchApp.getSql(searchType);
List<SearchDTO> list = sDAO.searchDetailHasPname(searchType, product_name, alcholArr, areaArr, grade, abv, sql);
int count = list.size();
request.setAttribute("count", count);
request.setAttribute("list", list);
request.getRequestDispatcher("/Search/searchResult.jsp").forward(request, response);
} else {
List<SearchDTO> list = sDAO.SearchAll();
int count = list.size();
request.setAttribute("count", count);
request.setAttribute("list", list);
request.getRequestDispatcher("/Search/searchResult.jsp").forward(request, response);
}
}
[ SearchAPP ] : 쿼리문 뽑아내는 용도
package SearchApp;
public class Search {
// 쿼리문 변수
private String defaultSql = "select row_number() over(order by product_name desc) num, product_name, seq, kind, price, abv, grade, smry, ori_name, sys_name from product_info where"; // index추가
private String pname = " product_name like '%'||?||'%' ";
private String achType = " (kind like ? or kind like ? or kind like ? or kind like ? or kind like ?) ";
private String area = " (product_area like ? or product_area like ? or product_area like ? or product_area like ? or product_area like ? or product_area like ?) ";
// String area = "product_area like '경기도' or product_area like '경상도' or product_area like '강원도' or product_area like '전라도' or product_area like '충청도' or product_area like '제주도'";
private String gradeStr = " (grade between ? and ? + 0.9) ";
private String abvStr = " (abv >= ?)";
// 대치 쿼리문
private String[] sqlArr = new String[] {pname, achType, area, gradeStr, abvStr};
// 쿼리문 반환
public String getSql(boolean[] searchType) {
// 쿼리문 공장 돌리기
for (int i=0; i<searchType.length; i++) {
if(searchType[i]) {
defaultSql += sqlArr[i];
if(i != 4) {
if(searchType[i+1]) {
defaultSql += "and";
}
}
} else {
if(i != 0 && i != 4) {
if(searchType[i+1]) {
defaultSql += "and";
}
}
}
}
if (defaultSql.contains("whereand")) {
String sql = defaultSql.replace("whereand", "where");
return sql;
}
return defaultSql;
}
}
일단 저는 개발하러 왔는데, 왜 기획자가 되어 있죠?
처음부터 끝까지, 각 기능 종합까지 내가 하다보니 개발을 하는 건지 기획을 하는 건지 헷갈렸다.
솔직히 이런 부분까지도 내가 해야돼? 라고 불만도 있었지만, 돌아보면 내가 해야될 것이었다. 그러면서 느낀 거는 ‘기획자가 개발을 알아야 한다’라는 것이다.
곧, 팀 리더가 실무를 알아야 한다는 것을 깊이 깨달았다. 그래야 팀원이 뭘 말하는지 이해도 되고 어떤 방향으로 나가야 하는지 지시해줄 수 있었다. 물론 나같이 동네 학원 세미 프로젝트가 어디서 실무에 비비냐 하겠지마는 역으로 생각해보자. 동네 학원도 이 정도인데, 실무에서 리더의 무게가 얼마나 무겁겠냐
우리 팀은 ‘전통주’를 가지고 프로젝트를 진행했다. 발표도 성공적이었고, 무엇보다 강사님이 좀 더 보완해서 포트폴리오용으로 쓰면 될 정도라고 칭찬해주셨다.
물론 내 입장에선 더 고급 기술을 쓰지도 않고, 기본만 가지고 만들었기 때문에 아쉬움이 남았다. 그래도 함께 해준 팀원들 모두 각자의 몫을 성실히 해주셔서 성공적인 프로젝트가 될 수 있던 것 같다.
팀장 : 프로젝트 설계 및 지휘, DB설계 및 관리, 전체 디자인 조율
Git 관리자, 관리자 페이지 구현(관리자 권한 부여, 신고 게시글 삭제, 상품 등록)
검색 기능 구현(검색어 기반 검색, 선택 조건 기반 검색)
[ 세부 내용 ]