미니 프로젝트를 하면서 고민했던 부분, 공부했던 부분에 대해 정리해보고자 한다.
자바만 사용해서 swing과 jdbc, 그리고 MySQL로 2일만에 개발한 프로젝트였다. (기간이 짧았음. 사실상 24시간...?)
흔한 쇼핑몰보다는 그래도 어느정도 색다른 쇼핑몰을 하고 싶어서 "미니 스팀" 게임 쇼핑몰을 만들고자 하였다.
위와 같이 계층이 있는 카테고리는 카테고리 테이블에 모든 카테고리를 넣고 셀프 조인을 하며 부모 카테고리를 찾아가는 방법으로 쓴다고 한다.
여기서 나는 상위 카테고리, 하위 카테고리는 셀프 조인으로 계층이 있는데 어떻게 구현해야 하는건지 고민이었다.
그래서 찾아본 결과 계층이 있는 데이터를 처리할 때 WITH RECURSIVE와 UNION를 사용한다고 한다.
WITH RECURSIVE를 사용하는 계층형 쿼리 구조
WITH RECURSIVE 임시테이블명 AS ( 기저쿼리 UNION_ALL 임시 테이블을 활욯하는 쿼리 ) 최종쿼리
이를 프로젝트에 적용시키면 아래와 같은 구조를 갖게된다.
WITH RECURSIVE를 사용하는 계층형 쿼리
WITH RECURSIVE 임시테이블명 AS ( 입력한 카테고리 이름을 받아와서 카테고리 정보 추출하는 쿼리 UNION ALL -- 재귀가 이루어지는 곳 위에서 받아온 카테고리 아이디로 부모 카테고리를 찾는 쿼리 ) 부모와 자식 카테고리를 통해 상품 검색하는 쿼리
그래서 아래와 같이 먼저 상위 카테고리 목록을 불러오고, 상위 카테고리가 선택되면 하위 카테고리 목록을 불러오고, 하위 카테고리가 선택되면 상위, 하위 카테고리들을 사용하여 상품을 조회하는 계층 쿼리를 사용하게 된다.
public List<String> getTopCategories() {
List<String> categories = new ArrayList<>();
String sql = "SELECT name FROM category WHERE parent_categoryid IS NULL";
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = DBManager.getConnection();
preparedStatement = connection.prepareStatement(sql);
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
categories.add(resultSet.getString("name"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBManager.releaseConnection(resultSet, preparedStatement, connection);
}
return categories;
}
public List<String> getSubCategories(String parentCategory) {
List<String> subCategories = new ArrayList<>();
String sql = "SELECT c.name FROM category c JOIN category p ON c.parent_categoryid = p.categoryid WHERE p.name = ?; ";
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = DBManager.getConnection();
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, parentCategory);
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
subCategories.add(resultSet.getString("name"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBManager.releaseConnection(resultSet, preparedStatement, connection);
}
return subCategories;
}
// 카테고리로 제품을 검색하는 메서드
public List<Product> searchProductsCategory(String topCategory, String subCategory, String userId) {
List<Product> productList = new ArrayList<>();
String sql = "WITH RECURSIVE CategoryHierarchy AS (" +
" SELECT categoryid, name, parent_categoryid " + // 입력한 카테고리 이름 받아와서 조회
" FROM category " +
" WHERE name = ? " +
" UNION ALL " +
" SELECT c.categoryid, c.name, c.parent_categoryid " + // 재귀 - 부모 카테고리를 조회
" FROM category c " +
" JOIN CategoryHierarchy ch ON c.parent_categoryid = ch.categoryid " +
") " +
"SELECT p.*, b.brandname FROM product p " + // 부모와 자식 카테고리를 통해 상품 검색
"JOIN brand b ON p.brand_brandid = b.brandid " +
"JOIN CategoryHierarchy ch ON p.category_categoryid = ch.categoryid " +
"WHERE ch.name = ?";
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = DBManager.getConnection();
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, topCategory);
preparedStatement.setString(2, subCategory);
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
Product product = new Product();
product.setProductId(resultSet.getInt("productid"));
product.setPdname(resultSet.getString("pdname"));
product.setPrice(resultSet.getInt("price"));
product.setContent(resultSet.getString("content"));
product.setImage(resultSet.getString("image"));
product.setBrandName(resultSet.getString("brandname"));
boolean flag = checkPurchased(userId, product.getProductId());
if (flag) {
product.setHas("보유중");
} else {
product.setHas("구매가능");
}
productList.add(product);
}
} catch (
SQLException e) {
e.printStackTrace();
} finally {
DBManager.releaseConnection(resultSet, preparedStatement, connection);
}
return productList;
}
}
수업을 들었을 때 이러한 상태 같은 컬럼은 데이터를 추가하고 삭제하는 등의 경우를 고려하여 확장성 면에서 테이블을 분리하는게 좋다고 배워서 분리하였다.
role의 경우에도 다른 역할을 추가할 수도 있는 것이고, status도 새로운 상태를 추가하거나 기존 상태의 이름을 바꾼다면 위와 같이 하는 것이 좋다고 생각하였다.
하지만 개발을 진행하다보니 위와 같은 구조는 1:1 구조이기 때문에 불필요한 쿼리가 많아진다는 것을 느꼈다.
테이블을 나누지 않으면 select 쿼리를 한번만 써도 될 것을 2번 써야 됐기 때문이다.
또한 1:1 관계는 정말 필요한건지 고민해봐야 한다고 배웠기 때문에 코드 테이블을 나누는 것이 정말 좋은건지 고민이 되었다.
그래서 강사님께 여쭤보았다.
1:1 관계 테이블은 처음부터 하나로 만들어도 되지만
위와 같이 코드 테이블을 따로 나누는 건 메인, 서브 테이블 개념이라 일반 데이터는 메인에, 부가 데이터는 서브에 넣고 따로 관리하는 꼴이 된다.
이는 위 데이터가 모두 동시에 필요하면 부담이지만, 일반 데이터만 필요한 경우가 많다면 이득이 된다.
그래서 공통 코드 테이블은 분리해서 사용하는게 좋다는 것 같다.
기간이 짧았어서 UI 디자인은 신경쓰지 못했다.
프로젝트 목적이 jdbc 활용이라 jdbc 활용에 더 집중하였다.
아이디, 비밀번호로 로그인.
구매자 권한이면 상품 페이지로 넘어가고, 판매자 권한이면 상품 등록 페이지로 넘어간다.
평범한 회원가입 기능
상품 페이지
보유 여부는 고객 아이디와 상품 아이디에 맞는 주문 내역 테이블과 주문 상세 테이블을 조인해서 결과가 있는지, 주문한 내역이 있는지 확인하여 보유 여부를 결정하였다.
결제 처리에 관한 기능은 임시로 버튼 눌러 바꾸는게 아닌 이상 짧은 시간에 불가능하다 생각하여 주문 테이블을 사용하여 조회하였다.
상품명으로 검색할 수 있고, 상위, 하위 카테고리로 상품을 필터링할 수 있다.
상품 상세
상품에 대해 상세 버튼을 누르면 자세한 정보가 뜬다.
상품 테이블에 있는 정보와 좋아요 기능(추가, 삭제, 개수), 리뷰(추가, 삭제), 장바구니 추가 기능을 넣었다.
좋아요 부분은 이미 좋아요를 눌렀으면 까만 하트, 안눌렀으면 하얀 하트를 보이게 해놨으며 좋아요 버튼을 눌러 다시 변화가 생기면 하트 색도 변화가 생기게 하였다.
장바구니
장바구니에서는 수량을 조절하거나 장바구니에 담긴 상품을 삭제할 수 있다.
장바구니 수량을 바꾸면 아래에 총 가격도 바뀌게 된다.
결제
결제는 결제 유형 정도만 선택 가능하게 만들었고, 마찬가지로 총 가격을 띄워주었다.
여기서는 수량 조절이 불가능하다.
주문내역
사용자의 주문 내역을 볼 수 있다.
주문한 총 가격과 주문한 날짜, 결제 상태에 관해 볼 수 있다.
주문 상세
주문 내역 상세보기를 누르면 주문한 상품들의 정보를 볼 수 있다.
이렇게 하나의 주문에 여러가지 상품이 담길 수 있기 때문에 주문 내역 테이블과 주문 상품 테이블을 나누었다.
상품 등록
판매자로 로그인 시 들어갈 수 있는 화면이다.
판매자가 아닐 시 로그인 화면으로 넘어가게 된다.
위에서 이미지 선택 버튼은 JFileChooser를 사용하여 사용자의 파일에서 이미지를 고를 수 있게 하였고, product 테이블의 image에 해당 이미지의 경로가 저장되게 하였다.
너무 아쉽지만 1표 차이로 2등을 하게 되었다.😥
다들 노력 인정해주시고... 좋은 말 많이 해주셔서 넘 감사했다 ㅠㅠㅠㅠㅠ
그런 면에서 충분히 의미 있었던 활동이었던 것 같다.
이제 시작이라 생각하고 더욱 열심히 해야겠다!!!!!!!!!!!!!!!🔥🔥⚡⚡👊👊👊👊👊👊