[신한 DS SW아카데미 / 1차 프로젝트] [BE] User bookmark 리팩터링 하기
[신한 DS SW아카데미 / 1차 프로젝트] [BE] User bookmark 리팩터링 하기
⭐ 해당 프로젝트의 경우, 존재하는 레퍼런스에서 약간 변형하여 팀 프로젝트로 제작 중인 신한DS 부트캠프 1차 미니 프로젝트입니다.
🤔 Intro
- main과 마찬가지로 내가 맡은 bookmark 도 리팩터링을 진행해보자!
😀 Start!
목표
- 기존의 코드에서 리팩토링을 진행해서 더 나은 코드를 만들어보자!
화면명세
- 별을 이용해서 북마크 추가 삭제가 가능하다.
- 마감, 오픈 여부가 보인다.
- 카테고리와 대표메뉴가 나와있다.
- 마감이면 빈칸, 오픈이면 픽업 시간이 나와있다.
- 판매가와 원가가 나와있다.
- 가게 대표 이미지가 존재한다.
- 다음과 같이 별을 눌러서 북마크가 삭제될 경우, 흐려진다.
- 별을 다시 누르면 북마크를 다시 추가할 수 있다.
DTO & Mapper
기존 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* [DTO] Bookmark
*
* - user가 가진 bookmark DTO
* - Service로직을 사용하기 위해 MainStore를 상속받도록 구성한다.
*/
@Data
@NoArgsConstructor
public class Bookmark extends MainStore{
//Bookmark table에서 가져오는 정보들
private Integer bookmark_no;
private String user_id;
}
- 마찬가지로 데이터 의존적으로 설계되어 있을 뿐 아니라,
- 기존 main에서 사용하는 서비스 로직을 사용하기 위해 main의 객체를 상속받는 식으로 설계되어 불필요한 정보가 너무 많이 포함된다는 치명적인 단점이 존재한다.
개선 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import lombok.*;
import java.util.List;
/**
* [DTO] BookmarkDTO
*
* - Bookmark에 나오는 가게 정보 카드를 사용하기 위한 DTO 클래스
* - 카드 안에 들어가는 모든 정보들을 한 번에 저장해서 사용한다.
* - @Data로 불필요한 애노테이션을 늘리는 것 보다는, 필요한 애노테이션만 더해주었다.
*/
@Getter
@Setter
@ToString
@Builder
@AllArgsConstructor //Builder와 테스트용 생성자
@NoArgsConstructor // MyBatis 매핑용 기본 생성자
public class BookmarkDTO {
//Store table에서 가져오는 정보들
private Store store;
//Product table에서 가져오는 정보들
private Product product;
//Image table에서 가져오는 정보들
private Image image;
//Bookmark에서 가져오는 정보들
private Bookmark bookmark;
//[추가 정보] DB에는 없는 추가 정보
private PickupStatus pickup_status; //오늘픽업인지 내일 픽업인지 마감인지를 저장
private List<String> category_list; //카테고리 리스트 저장
private List<String> mainmenu_list; //store_menu -> 메인메뉴 리스트 저장
}
- 이를 통해 해당 DTO는 어떤 객체를 참조하는지 등을 좀 더 명확하게 알 수 있다.
- 마찬가지로, 이렇게 객체를 참조하는 방식으로 DTO를 구성할 경우, resultMap을 이용해야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<?xml version="1.0" encoding="UTF-8"?>
<!-- [refactor] bookmark 위한 전용 mapper -->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http:/mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="kr.co.ohgoodfood.dao.UserBookmarkMapper">
<resultMap id="BookmarkDTO" type="kr.co.ohgoodfood.dto.BookmarkDTO">
<!-- store 연관관계 매핑 -->
<association property="store" javaType="kr.co.ohgoodfood.dto.Store">
<id column="store_id" property="store_id"/>
<result column="store_name" property="store_name"/>
<result column="store_status" property="store_status"/>
<result column="store_menu" property="store_menu"/>
<result column="category_bakery" property="category_bakery"/>
<result column="category_fruit" property="category_fruit"/>
<result column="category_salad" property="category_salad"/>
<result column="category_others" property="category_others"/>
<result column="closed_at" property="closed_at"/>
<result column="confirmed" property="confirmed"/>
</association>
<!-- product 연관관계 매핑 -->
<association property="product" javaType="kr.co.ohgoodfood.dto.Product">
<id column="product_no" property="product_no"/>
<result column="pickup_start" property="pickup_start"/>
<result column="pickup_end" property="pickup_end"/>
<result column="origin_price" property="origin_price"/>
<result column="sale_price" property="sale_price"/>
<result column="reservation_end" property="reservation_end"/>
<result column="amount" property="amount"/>
<result column="product_explain" property="product_explain"/>
</association>
<!-- Image 연관관계 매핑 -->
<association property="image" javaType="kr.co.ohgoodfood.dto.Image">
<result column="store_img" property="store_img"/>
</association>
<!-- Bookmark 연관관계 매핑 -->
<association property="bookmark" javaType="kr.co.ohgoodfood.dto.Bookmark">
<id column="bookmark_no" property="bookmark_no"/>
<result column="user_id" property="user_id"/>
</association>
</resultMap>
<!-- selectAllBookmark mapper, bookmark에서 이용한다. -->
<select id="selectAllBookmark" parameterType="map" resultMap="BookmarkDTO">
SELECT
s.store_id AS store_id, s.store_name AS store_name, s.store_menu AS store_menu, s.store_status AS store_status, s.category_bakery AS category_bakery,s.category_fruit AS category_fruit, s.category_salad AS category_salad, s.category_others AS category_others, s.closed_at AS closed_at,
pf.product_no AS product_no, pf.pickup_start AS pickup_start,pf.pickup_end AS pickup_end, pf.origin_price AS origin_price, pf.sale_price AS sale_price, pf.product_explain AS product_explain, pf.reservation_end AS reservation_end, pf.amount AS amount,
img.first_img AS store_img
FROM Bookmark b
JOIN Store s ON s.store_id = b.store_id
LEFT JOIN Product pf
ON pf.store_id = s.store_id
AND DATE(pf.reservation_end) = CURDATE()
LEFT JOIN (
SELECT
store_id,
MIN(store_img) AS first_img
FROM Image
GROUP BY store_id
) img ON img.store_id = s.store_id
<where>
s.confirmed = 'Y'
AND b.user_id = #{user_id}
<!-- ORDER BY 순서 : 오픈, 매진, 마감순 정렬 -->
</where>
ORDER BY
CASE
WHEN s.store_status = 'Y' AND amount > 0 THEN 1
WHEN s.store_status = 'Y' AND amount = 0 THEN 2
ELSE 3
END;
</select>
<!-- deleteBookmark mapper -->
<delete id="deleteBookmark" parameterType="map">
DELETE FROM Bookmark
WHERE store_id = #{store_id}
AND user_id = #{user_id};
</delete>
<!-- insertBookmark mapper -->
<delete id="insertBookmark" parameterType="map">
INSERT INTO Bookmark (user_id, store_id)
VALUES (#{user_id}, #{store_id});
</delete>
</mapper>
- main에서 리팩토링 했던 것과 같은 방식으로, resultMap을 이용해서 객체 지향적인 설계에 가깝도록 구성한다.
- 쿼리는 기존과 동일하게 사용한다.
- 마찬가지로, bookmark 전용 mapper를 분리한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import kr.co.ohgoodfood.dto.BookmarkDTO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* UserBookmarkMapper
*
* 사용자 bookmark 페이지에서 사용하는 mapper interface
*/
@Mapper
public interface UserBookmarkMapper {
/**
* 사용자 북마크 화면에서 표시할 가게 목록을 조회
*
* @param user_id 조회 대상 user_id
* @return 특정 user가 가진 BookmarkDTO 리스트
*/
List<BookmarkDTO> selectAllBookmark(String user_id);
/**
* 사용자의 특정 북마크를 삭제 처리
*
* @param user_id 조회 대상 user_id
* @param store_id user_id + store_id 조합으로 삭제
* @return 영향받은 행(row) 수
*/
int deleteBookmark(@Param("user_id") String user_id,
@Param("store_id") String store_id);
/**
* 사용자 특정 북마크를 추가 처리
*
* @param user_id 조회 대상 user_id
* @param store_id 북마크에 추가할 store 정보
* @return 영향받은 행(row) 수
*/
int insertBookmark(@Param("user_id") String user_id,
@Param("store_id") String store_id);
}
- mapper interface는 다음과 같이 구성한다.
- 각 매퍼에 id=”selectOneStoreByStoreId” 아이디값으로 정의한 것이 여기서는 method의 이름이다.
- 주석에는 각 DTO안에 무엇이 들어가고, 무엇을 기준으로 어떤 return 값이 나오는지를 좀 더 명확히 볼 수 있도록 구성했다.
[추가] FilterDTO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import jakarta.validation.constraints.NotNull;
import lombok.*;
/**
* [DTO] BookmarkFilter
*
* - user가 가진 bookmark를 삭제 & 추가하기 위해 필요한 정보 모음
* - 유효성 검증을 위해 DTO로 따로 분리하였다.
* - @Data로 불필요한 애노테이션을 늘리는 것 보다는, 필요한 애노테이션만 더해주었다.
*/
@Getter
@Setter
@ToString
@Builder
@AllArgsConstructor //Builder와 테스트용 생성자
@NoArgsConstructor // MyBatis 매핑용 기본 생성자
public class BookmarkFilter {
private int bookmark_no;
private String user_id;
@NotNull(message = "store_id는 필수값입니다")
private String store_id; //insert 기능을 위해 추가.
}
- main에서처럼 마찬가지로 유효성 검사 값들을 달아준다.
- insert, delete시 id 값들은 무조건!!!! 있어야 하기 때문에 NotNull Anotation 처리한다.
- 그러나 지금 내 구조에서, user_id는 controller에서 session단에서 빼서 주입하기 때문에 여기서는 binding 애노테이션을 제거해야 한다
Service
- 다음으로는 서비스 로직을 고쳐보자.
- main에서 그랬듯이, 서비스 로직도 맞춰서 일부 변경하였다.
기존 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/**
* UsersServiceImpl.java - UsersService interface 구현체
*
* @see UsersService - 세부 기능은 해당 클래스인 UsersServiceImpl에 구현한다.
* 의존성 주입은 생성자 주입으로 구성한다.
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class UserServiceImpl implements UsersService{
private final UserMapper userMapper;
private final AwsS3Config awsS3Config;
/**
* 사용자가 가진 북마크 리스트를 가져오는 method
*
* @param user_id : 현재 세션에 접속한 사용자 id
* @return : bookmarkList (Bookmark DTO의 리스트 객체)
*/
@Override
public List<Bookmark> getBookmarkList(String user_id){
List<Bookmark> bookmarkList = userMapper.selectAllBookmark(user_id);
// 여기에 카테고리 이름과 pickup 상태를 저장
for(Bookmark bookmark : bookmarkList){
bookmark.setPickup_status(getPickupDateStatus(bookmark));
bookmark.setCategory_list(getCategoryList(bookmark));
bookmark.setMainmenu_list(StringSplitUtils.splitMenu(bookmark.getStore_menu(), "/"));
}
return bookmarkList;
}
/**
* 북마크를 삭제하기 위한 기능이다.
*
* @param bookmarkFilter : Bookmark 삭제시 필요한 정보값이 담긴 DTO
* @return : 실행 결과 행 수에 따라 Boolean
*/
@Override
public boolean deleteUserBookMark(BookmarkFilter bookmarkFilter) {
String user_id = bookmarkFilter.getUser_id();
String store_id = bookmarkFilter.getStore_id();
int cnt = userMapper.deleteBookmark(user_id, store_id);
if (cnt == 1) {
return true;
}
return false; //delete 실패!
}
/**
* 북마크를 추가하기 위한 기능이다.
*
* @param bookmarkFilter : Bookmark 삭제시 필요한 정보값이 담긴 DTO
* @return : 결과 행 수에 따라 Boolean
*/
@Override
public boolean insertUserBookMark(BookmarkFilter bookmarkFilter) {
String user_id = bookmarkFilter.getUser_id();
String store_id = bookmarkFilter.getStore_id();
int cnt = userMapper.insertBookmark(user_id, store_id);
if (cnt == 1) {
return true;
}
return false; //insert 실패!
}
//......
}
- main에서 존재했던 문제점과 비슷하게, 예외처리가 안 되어 있고 하나의 userService안에 모든 코드가 다 들어가 있다는 단점이 있다.
- 전체적인 리팩터링 방식은 main에서 진행했던 것과 동일한 방식으로 진행하였다.
개선 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import kr.co.ohgoodfood.dto.BookmarkDTO;
import kr.co.ohgoodfood.dto.BookmarkFilter;
import java.util.List;
/**
* UserBookmarkService interface
* - UserBookmarkService 기능 틀 interface
* - 유지 보수 및 확장 편의성을 위해 interface로 구성한다.
*/
public interface UserBookmarkService {
//[Controller 로직] 북마크 Controller 연결 로직
List<BookmarkDTO> getBookmarkList(String user_id);
//[Controller 로직] 북마크 삭제 Controller 연결 로직
boolean deleteUserBookMark(BookmarkFilter bookmarkFilter);
//[Controller 로직] 북마크 추가 Controller 연결 로직
boolean insertUserBookMark(BookmarkFilter bookmarkFilter);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import kr.co.ohgoodfood.dao.UserBookmarkMapper;
import kr.co.ohgoodfood.dto.BookmarkDTO;
import kr.co.ohgoodfood.dto.BookmarkFilter;
import kr.co.ohgoodfood.dto.PickupStatus;
import kr.co.ohgoodfood.exception.InvalidPickupDataException;
import kr.co.ohgoodfood.util.StringSplitUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* UserBookmarkServiceImpl.java - UserBookmarkService interface 구현체
*
* @see UserBookmarkService - 세부 기능은 해당 클래스인 UserBookmarkServiceImpl에 구현한다.
* - 의존성 주입은 생성자 주입으로 구성한다.
* - 스프링은 기본 빈 주입이 싱글톤이기 때문에, 따로 싱글톤 처리 없이 @Service로 해결한다.
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class UserBookmarkServiceImpl implements UserBookmarkService {
private final UserBookmarkMapper userBookmarkMapper;
private final UserCommonService userCommonService;
/**
* 사용자가 가진 북마크 리스트를 가져오는 method
*
* @param user_id : 현재 세션에 접속한 사용자 id
* @return : bookmarkList (Bookmark DTO의 리스트 객체)
*/
@Override
public List<BookmarkDTO> getBookmarkList(String user_id){
List<BookmarkDTO> bookmarkList = userBookmarkMapper.selectAllBookmark(user_id);
// 여기에 카테고리 이름과 pickup 상태를 저장
for(BookmarkDTO bookmark : bookmarkList){
PickupStatus pickup_status;
try{
//product가 없음, 마감 상태 getProduct().으로 접근할때 nullpointerException을 막기 위함이다.
if(bookmark.getProduct() == null){
pickup_status = PickupStatus.CLOSED;
}else{
pickup_status = userCommonService.getPickupDateStatus(
bookmark.getStore().getStore_status(),
bookmark.getProduct().getPickup_start(),
bookmark.getProduct().getAmount()
);
}
} catch (InvalidPickupDataException e){
log.info("픽업 상태 계산 실패(storeId={}): {}",
bookmark.getStore().getStore_id(), e.getMessage());
//이상 데이터 값의 경우, continue로 숨김처리 및 pickup_status 계산 안함
continue;
}
bookmark.setPickup_status(pickup_status);
bookmark.setCategory_list(userCommonService.getCategoryList(bookmark.getStore()));
bookmark.setMainmenu_list(StringSplitUtils.splitMenu(bookmark.getStore().getStore_menu(), "\\s*\\|\\s*"));
}
return bookmarkList;
}
/**
* 북마크를 삭제하기 위한 기능이다.
*
* @param bookmarkFilter : Bookmark 삭제시 필요한 정보값이 담긴 DTO
* @return : 실행 결과 행 수에 따라 Boolean
*/
@Override
public boolean deleteUserBookMark(BookmarkFilter bookmarkFilter) {
String user_id = bookmarkFilter.getUser_id();
String store_id = bookmarkFilter.getStore_id();
int cnt = userBookmarkMapper.deleteBookmark(user_id, store_id);
if (cnt == 1) {
return true;
}
return false; //delete 실패!
}
/**
* 북마크를 추가하기 위한 기능이다.
*
* @param bookmarkFilter : Bookmark 삭제시 필요한 정보값이 담긴 DTO
* @return : 결과 행 수에 따라 Boolean
*/
@Override
public boolean insertUserBookMark(BookmarkFilter bookmarkFilter) {
String user_id = bookmarkFilter.getUser_id();
String store_id = bookmarkFilter.getStore_id();
int cnt = userBookmarkMapper.insertBookmark(user_id, store_id);
if (cnt == 1) {
return true;
}
return false; //insert 실패!
}
}
- main에서 그랬던 것처럼, 마찬가지로 interface와 그 구현체로 구성한다.
- 또한, 기존에 리팩토링에서 판별 로직들을 전부 userMainService에 넣었었는데, bookmark에서도 같은 로직을 사용하기 때문에 이를 UserCommonService로 분리하였다.
- commonService로 공통 로직을 분리하였으므로, userCommonService.getPickupDateStatus 이렇게 class에 method로 접근해서 사용하면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import kr.co.ohgoodfood.dto.PickupStatus;
import kr.co.ohgoodfood.dto.Store;
import java.sql.Timestamp;
import java.util.List;
/**
* UserCommonService interface
* - UserCommonService 기능 틀 interface
* - 유지 보수 편의성 및, DIP 원칙을 준수하기 위해 interface로 구성한다.
* - bookmark와 main에서 동시에 사용하는 판별 로직을 common으로 분리
*/
public interface UserCommonService {
//[판별 로직] 오늘 픽업, 내일 픽업, 마감 판별 연결 로직
public PickupStatus getPickupDateStatus(String store_status, Timestamp pickup_start, Integer amount);
//[판별 로직] 카테고리 List<String> 저장 로직
public List<String> getCategoryList(Store store);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import kr.co.ohgoodfood.dto.PickupStatus;
import kr.co.ohgoodfood.dto.Store;
import kr.co.ohgoodfood.dto.StoreCategory;
import kr.co.ohgoodfood.exception.InvalidPickupDataException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
/**
* UserCommonServiceImpl.java - UserCommonService interface 구현체
*
* @see UserCommonService - 세부 기능은 해당 클래스인 UserCommonServiceImpl에 구현한다.
* - 공통적으로 사용하는 판별 로직(오늘 픽업 & 내일픽업 여부, categoryList 구현 등..)을 commonservice로 분리한다.
* - 의존성 주입은 생성자 주입으로 구성한다.
* - 스프링은 기본 빈 주입이 싱글톤이기 때문에, 따로 싱글톤 처리 없이 @Service로 해결한다.
*/
@Slf4j
@Service
public class UserCommonServiceImpl implements UserCommonService {
/**
* LocalDate.now()로 오늘픽업, 내일픽업, 매진, 마감 상태를 판별합니다.
*
* @param store_status : store가 현재 오픈 상태인지 판단
* @param pickup_start : store가 오늘 픽업인지, 내일 픽업인지를 판단
* @param amount : store가 매진인지 판단, nullable 이므로 컬렉션 객체로 만든다.
*
* @return : PickupStatus ENUM 객체
*/
@Override
public PickupStatus getPickupDateStatus(String store_status, Timestamp pickup_start, Integer amount) {
//parameter가 null인경우, nullpointerException이 발생하므로, Integer로 감싼다.
LocalDate today = LocalDate.now();
// [마감] - store_status = N
if(store_status.equals("N")){
return PickupStatus.CLOSED;
}else{
LocalDate pickupDate;
if (pickup_start == null) {
throw new InvalidPickupDataException(store_status, pickup_start, amount);
}
//한번 null 위험 처리를 하고나면, 그 다음부터는 time_stamp 형식 예외 등의 로직만 잡으므로, 더 안전하게 체크가 가능하다.
try{
pickupDate = pickup_start.toLocalDateTime().toLocalDate();
} catch (Exception e){
//에러 로그를 보존한채 넘긴다.
throw new InvalidPickupDataException(store_status, pickup_start, amount, e);
}
// [매진] - amount = 0
if(amount == null || amount == 0){
return PickupStatus.SOLD_OUT;
}else{
// [오늘픽업] 현재 날짜와 같음
if (pickupDate.isEqual(today)) {
return PickupStatus.TODAY;
} else if (pickupDate.isEqual(today.plusDays(1))) { //[내일픽업] 현재 날짜 + 1과 같음
return PickupStatus.TOMORROW;
}
}
}
throw new InvalidPickupDataException(store_status, pickup_start, amount); //custom exception 던지기
}
/**
* |(구분자) 구분은 확장성을 위해 프론트 단에 위임
* 서버에서는 리스트에 담아서 보내도록 한다.
*
* @param store : store dto 내부에 있는 cateogory_ 값에 따라 list를 구현하기 위함이다.
* @return : 카테고리 이름이 담긴 List
*/
@Override
public List<String> getCategoryList(Store store) {
List<String> category_list = new ArrayList<>();
if(store.getCategory_bakery().equals("Y")){
category_list.add(StoreCategory.BAKERY.getDisplayName());
}
if(store.getCategory_fruit().equals("Y")){
category_list.add(StoreCategory.FRUIT.getDisplayName());
}
if(store.getCategory_salad().equals("Y")){
category_list.add(StoreCategory.SALAD.getDisplayName());
}
if(store.getCategory_others().equals("Y")){
category_list.add(StoreCategory.ETC.getDisplayName());
}
return category_list;
}
}
- 이를 통해 bookmark에서도 이를 깔끔하게 공용으로 사용할 수 있다.
- 그외 나머지 예외처리 로직은 main에서 처리했던 것과 동일하게 처리한다.
Controller
기존 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
/**
* UsersController
*
* 사용자 페이지 전용 기능을 처리하는 컨트롤러입니다.
* - POST /user/signup : 사용자 회원가입 페이지
* - GET /user/main : 사용자 메인 화면 조회
* - POST /user/filter/store : AJAX 기반 가게 목록 필터링
* - GET /user/bookmark : 해당 user_id가 가진 bookmark 목록 조회
* - POST /user/bookmark/delete : 해당하는 bookmark 삭제
* - POST /user/bookmark/insert : 해당하는 bookmark 추가
* - GET /user/main/orderList : 유저가 가진 orderList 목록 조회
* - POST /user/filter/order : AJAX 기반 오더 목록 필터링
* - POST /user/order/cancel : 유저가 선택한 오더 주문 취소
* - GET /user/map/pin : AJAX 기반 핀으로 선택한 스토어 fragment 조회
* - GET /user/mypage : 유저 mypage 이동
* - GET /user/reviewList : 하단 메뉴바 Review탭 이동시 전체 리뷰 목록 조회
*
*/
@Controller
@RequestMapping("/user")
@Slf4j
@RequiredArgsConstructor
public class UsersController {
private final UsersService usersService;
private final CommonService commonService;
private final PayService payService;
// 지도 사용을 위한 앱키
@Value("${kakao.map.appKey}")
private String kakaoMapAppKey;
//.......
/**
* 해당 user가 가진 북마크 리스트를 조회한다.
*
* @param userMainFilter 요청 파라미터와 바인딩되어 뷰로 전달되는 DTO
* @param model 뷰에 전달할 데이터(Model)
* @param session 현재 HTTP 세션(로그인된 사용자 정보)
* @return users/userBookmark.jsp로 포워딩
*/
@GetMapping("/bookmark")
public String userBookmark(@ModelAttribute UserMainFilter userMainFilter,
Model model,
HttpSession session){
//세션에서 받아오는 로직
Account loginUser = (Account) session.getAttribute("user");
String user_id = loginUser.getUser_id();
List<Bookmark> bookmarkList = usersService.getBookmarkList(user_id);
model.addAttribute("bookmarkList", bookmarkList);
return "users/userBookmark"; // /WEB-INF/views/users/userBookmark.jsp로 forwarding
}
/**
* 해당 user가 가진 북마크 리스트 중, 특정 북마크를 삭제한다.
*
* @param bookmarkFilter bookMark delete에 필요한 필드 정보가 담긴 DTO
* @param model 뷰에 전달할 데이터(Model)
* @param session 현재 HTTP 세션(로그인된 사용자 정보)
* @return json 응답, 성공시 {"code" : 200} / 실패시 {"code" : 500}
*/
@PostMapping("/bookmark/delete")
@ResponseBody //json으로 code응답을 주기 위함이다.
public Map<String,Integer> userBookmarkDelete(@RequestBody BookmarkFilter bookmarkFilter,
Model model,
HttpSession session){
//세션에서 받아오는 로직
Account loginUser = (Account) session.getAttribute("user");
String user_id = loginUser.getUser_id();
//bookmark를 위해 user_id 세팅
bookmarkFilter.setUser_id(user_id);
//delete bookmark 실행
boolean result = usersService.deleteUserBookMark(bookmarkFilter);
return Collections.singletonMap("code", result ? 200 : 500);
}
/**
* 해당 user가 가진 북마클 리스트에서 삭제 된 것을 살리기 위함이다.
*
* @param bookmarkFilter bookMark delete에 필요한 필드 정보가 담긴 DTO
* @param model 뷰에 전달할 데이터(Model)
* @param session 현재 HTTP 세션(로그인된 사용자 정보)
* @return json 응답, 성공시 {"code" : 200} / 실패시 {"code" : 500}
*/
@PostMapping("/bookmark/insert")
@ResponseBody //json으로 code응답을 주기 위함이다.
public Map<String,Integer> userBookmarkInsert(@RequestBody BookmarkFilter bookmarkFilter,
Model model,
HttpSession session){
//세션에서 받아오는 로직
Account loginUser = (Account) session.getAttribute("user");
String user_id = loginUser.getUser_id();
//bookmark를 위해 user_id 세팅
bookmarkFilter.setUser_id(user_id);
//delete bookmark 실행
boolean result = usersService.insertUserBookMark(bookmarkFilter);
return Collections.singletonMap("code", result ? 200 : 500);
}
//.....
}
- Controller의 문제점 역시 main에서 봤던 것과 동일하다.
- 한 클래스가 너무 많은 책임을 지지 않도록 Bookmark 전용 컨트롤러를 분리하고, BindingResult를 추가하는 방향으로 리팩터링을 진행했다.
개선 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import jakarta.validation.Valid;
import kr.co.ohgoodfood.dto.*;
import kr.co.ohgoodfood.service.users.UserBookmarkService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpSession;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* UserBookmarkController
*
* 사용자 bookmark 페이지 전용 기능을 처리하는 컨트롤러입니다.
* - GET /user/bookmark : 해당 user_id가 가진 bookmark 목록 조회
* - POST /user/bookmark/delete : 해당하는 bookmark 삭제
* - POST /user/bookmark/insert : 해당하는 bookmark 추가
*/
@Controller
@RequestMapping("/user")
@Slf4j
@RequiredArgsConstructor
public class UserBookmarkController {
private final UserBookmarkService userBookmarkService;
/**
* 해당 user가 가진 북마크 리스트를 조회한다.
*
* @param model 뷰에 전달할 데이터(Model)
* @param session 현재 HTTP 세션(로그인된 사용자 정보)
* @return users/userBookmark.jsp로 포워딩
*/
@GetMapping("/bookmark")
public String userBookmark(Model model,
HttpSession session) {
//세션에서 받아오는 로직
Account loginUser = (Account) session.getAttribute("user");
String user_id = loginUser.getUser_id();
List<BookmarkDTO> bookmarkList = userBookmarkService.getBookmarkList(user_id);
model.addAttribute("bookmarkList", bookmarkList);
return "users/userBookmark"; // /WEB-INF/views/users/userBookmark.jsp로 forwarding
}
/**
* 해당 user가 가진 북마크 리스트 중, 특정 북마크를 삭제한다.
*
* @param bookmarkFilter bookMark delete에 필요한 필드 정보가 담긴 DTO (store_id, user_id)
* @param session 현재 HTTP 세션(로그인된 사용자 정보)
* @return json 응답, 성공시 {"code" : 200} / 실패시 {"code" : 500} / 유효성 검사 실패시 {"code" : 400}
*/
@PostMapping("/bookmark/delete")
@ResponseBody //json으로 code응답을 주기 위함이다.
public Map<String, Integer> userBookmarkDelete(@Valid @RequestBody BookmarkFilter bookmarkFilter,
BindingResult br,
HttpSession session) {
//세션에서 받아오는 로직
Account loginUser = (Account) session.getAttribute("user");
String user_id = loginUser.getUser_id();
//bookmark를 위해 user_id 세팅
bookmarkFilter.setUser_id(user_id);
//유효성 검사 필드가 하나 이므로, 단일 구성
if (br.hasFieldErrors("store_id")) {
return Collections.singletonMap("code", 400);
}
//delete bookmark 실행
boolean result = userBookmarkService.deleteUserBookMark(bookmarkFilter);
return Collections.singletonMap("code", result ? 200 : 500);
}
/**
* 해당 user가 가진 북마클 리스트에서 삭제 된 것을 다시 추가하기 위함이다.
*
* @param bookmarkFilter bookMark delete에 필요한 필드 정보가 담긴 DTO (store_id, user_id)
* @param session 현재 HTTP 세션(로그인된 사용자 정보)
* @return json 응답, 성공시 {"code" : 200} / 실패시 {"code" : 500} / 유효성 검사 실패시 {"code" : 400}
*/
@PostMapping("/bookmark/insert")
@ResponseBody //json으로 code응답을 주기 위함이다.
public Map<String, Integer> userBookmarkInsert (@Valid @RequestBody BookmarkFilter bookmarkFilter,
BindingResult br,
HttpSession session){
//세션에서 받아오는 로직
Account loginUser = (Account) session.getAttribute("user");
String user_id = loginUser.getUser_id();
//bookmark를 위해 user_id 세팅
bookmarkFilter.setUser_id(user_id);
//유효성 검사 필드가 하나 이므로, 단일 구성
if (br.hasFieldErrors("store_id")) {
return Collections.singletonMap("code", 400);
}
//delete bookmark 실행
boolean result = userBookmarkService.insertUserBookMark(bookmarkFilter);
return Collections.singletonMap("code", result ? 200 : 500);
}
}
- /bookmark
- main에서 그랬던것처럼, 여기서도 불필요한 modelAttrbute를 삭제한다.
- /bookmark/delete
- filterDTO에 유효성 검사 애노테이션이 추가 되었으므로 유효성 검사가 필요하다.
- store_id가 없을 경우, 삭제 로직을 진행할 수 없으므로 ajax에 400 code를 전달한다.
- 화면 모달로 처리할 예정이다.
- /bookmark/insert
- /bookmark/delete와 로직 흐름은 같다. 여기도 store_id 없으면 insert가 진행될 수 없으므로 마찬가지로 400에러 처리한다.
This post is licensed under CC BY 4.0 by the author.