프로젝트

SpringBoot로 그룹캘린더 만들기 시즌2 - DTO <-> Entity 변환 방법, Controller, Service 역할 구분짓기

duburani 2023. 7. 11. 17:39
 

SpringBoot로 그룹캘린더 만들기 시즌2 - 스프링 데이터 JPA를 활용

기존의 프로젝트는 순수 jpa와 QueryDSL을 사용하여 레파지토리를 구현했다. 그러다보니 간단한 CRUD도 직접 구현해야하는데..! 영한쌤의 스프링 데이터 JPA 강의를 완독한 후 아 ..! 바꿔야겠다 싶었

duburani.tistory.com

이전 포스팅에서 작성했듯이 컨트롤러와 서비스 계층 간의 구분을 확실히 해두어야겠다고 생각이 들었다.

나는 그동안 비즈니스 로직을 컨트롤러에서 주로 처리해왔는데 비즈니스 로직은 사실 서비스에서 처리하는게 맞다고 하며 ..! 그렇기 때문에 엔티티 변환도 서비스에서 이루어지는게 <<웹계층 전달>> 만을 하는 컨트롤러의 역할이라고 생각되었다.

 

데이터 가공하고..~ 어쩌꼬~ 저쩌고~는 서비스에게 맡겨보자!

 

 

 

Controller, Service 역할 구분짓기
MainController.java
@Controller
@Slf4j
@RequiredArgsConstructor
public class MainController {
    private final HttpSession session;
    private final GroupService groupService;

    @GetMapping("/")
    public String login(Model model) {
        String returnUrl = "main";

        Object objUser = session.getAttribute("user");
        if(objUser != null){
            SessionUser sessionUser = (SessionUser) objUser;
            String userId = sessionUser.getUser_id();
            List<UserGroupResponse> groupList = groupService.findGroupByUserId(userId);

            if(groupList.size() < 1){
                returnUrl = "redirect:/userGroup";
            }else{
                returnUrl = "redirect:/calendars";
            }

            model.addAttribute("userInfo", sessionUser);
            model.addAttribute("groupInfo", groupList);
        }

        return returnUrl;
    }
}​

사실 기존에 메인, 유저, 캘린더 컨트롤러에서 회원 유무, 그룹 유무를 따져서 리턴 시켜주는 부분이 세 컨트롤러 다 중복으로 나타나고 보기 싫은 코드였다.

그래서 리팩토링을 시작하며 제일 처음 건든 부분이 바로 로그인 한 뒤 ~! 파트 였다.

 

이거를 어느 서비스에서 담당하기에도 애매하다 생각해서 각 컨트롤러에서 똑같은 코드를 중복해서 사용한 거였는데 이런 로직을 서비스에서 담당해도 된다는 걸 보고 ..! 그리고 그렇게 하는게 훨씬 깔끔했다 코드를 수정했다.

 

MainController.java
@Controller
@Slf4j
@RequiredArgsConstructor
public class MainController {
    private final MainService mainService;


    @GetMapping("/")
    public String login(Model model, @LoginUser SessionUser sessionUser) {
        String returnUrl = mainService.returnUrlByUserGroup(sessionUser);
        return "main".equals(returnUrl) ? returnUrl : "redirect:/" + returnUrl;
    }
}​


MainService.java

@Service
public class MainService {
    @Autowired
    private UserService userService;

    public String returnUrlByUserGroup(SessionUser sessionUser) {
        if (sessionUser == null)
            return "main";

        User user = userService.findUser(sessionUser.getUser_id());

        if (user == null)
            return "main";

        if (user.getGroup() == null) {
            return "userGroup";
        } else {
            return "calendars";
        }
    }
}

세션 유저가 없거나 회원이 아닐 때 main 으로

그룹이 없는 경우 커플을 맺어야 하기 때문에 userGroup 으로

그룹이 있으면 캘린더 작성할 수 있으니 calendars로

 

서비스 안에서 또 다른 서비스를 참조해도 상관없으므로 respository가 아닌 유저서비스를 넣어줬다.

코드가 많이 줄어서 너무 뿌듯하고.. ^^...

 

아맞다 @LoginUser 를 사용하면 OAuth2로 로그인 후 세션에 담아준 값을 저렇게 가져올 수 있게 된다!!

개이득~~ 이건 구선생님이 많이 알려주시니 따라해보길 추천한다.

httpSession.get ~~  if( == null) ~~~~ 귀찮은 코드가 한결 덜어져서 너무 좋음

 

 

 

아 그리고 비즈니스 로직을 서비스단으로 옮겼다.

기존에 컨트롤러에서 화면에 보여질 데이터를 Json으로 변환하는 작업을 했었는데 그 부분을 서비스에서 하는 걸로 수정했다.

기존 CalController
@Controller
@RequiredArgsConstructor
public class CalController {
    private final CalendarService calendarService;

    /**
     * 달력 얼정 상세 등록, 수정, 상세 화면
     * @param calDtlId
     * @param model
     * @return
     */
    @GetMapping("/calendars/detail/new")
    public String calendarDetailForm(@RequestParam(required = false, defaultValue = "0") int calDtlId
                                    , @RequestParam(required = false) String dateStr
                                    , @RequestParam int groupId
                                    , Model model){
        CalendarDetail calendarDetail = null;
        if(calDtlId != 0){
            calendarDetail = calendarService.findCalendarDetail(calDtlId);
            int cal_id = calendarDetail.getCalendar().getCal_id();
            model.addAttribute("cal_id", cal_id);
        }
        List<CalendarResponse> calendarList = calendarService.findCalendarList(groupId);

//        model.addAttribute("resultData", calendarDetail);
        model.addAttribute("dateStr", dateStr);
        model.addAttribute("groupId", groupId);
        model.addAttribute("calendarList", calendarList);
        model.addAttribute("calendarDetailForm", calDtlId == 0 ? new CalendarDetailForm() : calendarDetail);

        return "cal/calendarDetailForm";
    }
}​



변경 후 CalendarService
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class CalendarService {
    private final CalendarRepository calendarRepository;
    private final CalendarDetailRepository calendarDetailRepository;

    // 달력 일정 상세 목록 조회
    public JSONArray findCalendarDetailList(CalendarRequestDto calendarRequestDto) throws ParseException {
        List<Calendar> calendarDetailList = calendarJpaRepository.findCalendarDetailList(calendarRequestDto);
        JSONArray jsonArr = new JSONArray();
        HashMap<String, Object> hash = new HashMap<>();
        for(Calendar calendar : calendarDetailList){
            hash.put("color", ColorCode.valueOf(calendar.getColor().toUpperCase()).getRgb_color());
            hash.put("cal_title", calendar.getCal_title());
            for(CalendarDetail calendarDetail : calendar.getCalendars()){
                hash.put("id", calendarDetail.getCal_dtl_id());
                hash.put("title", calendarDetail.getTitle());
                hash.put("start", calendarDetail.getStart_date() + "T" + calendarDetail.getStart_time());
                hash.put("end", getDate(calendarDetail.getEnd_date(), calendarDetail.getAllday_yn()) + "T" + calendarDetail.getEnd_time());
                hash.put("allDay", "Y".equals(calendarDetail.getAllday_yn()) ? true : false);

                JSONObject jsonObj = new JSONObject(hash);
                jsonArr.add(jsonObj);
            }
        }
        return jsonArr;
    }
}​

 

 

 

 

 

 

DTO <-> Entity 변환 방법

디티오 엔티티는 서비스에서 변환하기로 정했는데~~ 이를 어떻게 변환해야 하느냐

 

Calendar Entity
@Entity
@Getter
@Table(name = "Calendar")
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@DynamicInsert
public class Calendar extends BaseEntity{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long cal_id;

    private String cal_title;
    private String color;
    private String memorial_yn;
    private String del_yn;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "group_id")
    private Group group;

    @OneToMany(mappedBy = "calendar")
    private List<CalendarDetail> calendars = new ArrayList<>();

    public void updateCal(CalendarForm calendarForm){
        this.cal_title = calendarForm.getCal_title();
        this.color = calendarForm.getColor();
        this.memorial_yn = calendarForm.getMemorial_yn();
    }

    public void updateDelYn(String del_yn){
        this.del_yn = del_yn;
    }
}​

CalendarResponseDto

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@Getter
public class CalendarResponseDto {
    private Long calId;
    private String calTitle;
    private String color;
    private String memorialYn;
    private Long calCnt;
    private Long groupId;
    private List<CalendarDetail> calendarDetailList = new ArrayList<>();

    public CalendarResponseDto(Calendar calendar) {
        this.calId = calendar.getCal_id();
        this.calTitle = calendar.getCal_title();
        this.color = calendar.getColor();
        this.memorialYn = calendar.getMemorial_yn();
        this.groupId = calendar.getGroup().getGroup_id();
        this.calendarDetailList = calendar.getCalendars();
    }
}


CalendarService

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class CalendarService {
    private final GroupService groupService;
    private final CalendarRepository calendarRepository;
    private final CalendarDetailRepository calendarDetailRepository;

    // 달력 생성
    @Transactional
    public void craete(CalendarForm calendarForm){
        Group group = groupService.findGroup(calendarForm.getGroup_id());
        Calendar calendar = Calendar.builder()
                .cal_title(calendarForm.getCal_title())
                .color(calendarForm.getColor())
                .memorial_yn(calendarForm.getMemorial_yn())
                .group(group)
                .build();
        calendarRepository.save(calendar);
    }

    // 달력 단건 조회
    public CalendarResponseDto findCalendar(Long cal_id){
        Calendar calendar = calendarRepository.findById(cal_id).orElse(null);
        return new CalendarResponseDto(calendar);
    }

    // 달력 조회
    public List<CalendarResponseDto> findCalendarList(Long groupId){
        Group group = groupService.findGroup(groupId);
        List<Calendar> calendarByGroup = calendarRepository.findCalendarByGroup(group);
        return calendarByGroup.stream().map(CalendarResponseDto::new).collect(Collectors.toList());
    }

    // 달력 삭제
    @Transactional
    public void deleteCalendar(Long calId) {
        Calendar calendar = calendarRepository.findById(calId).orElse(null);
        if(calendar != null)
            calendar.updateDelYn("Y");
    }

    // 달력 수정
    @Transactional
    public void update(CalendarForm calendarForm){
        Calendar calendar = calendarRepository.findById(calendarForm.getCal_id()).orElse(null);
        if(calendar != null)
            calendar.updateCal(calendarForm);
    }
}

전체적인 코드이고 서비스에서 Dto -> Entity / Entity -> Dto 변환해주는 모습이다.

 

// 달력 단건 조회
public CalendarResponseDto findCalendar(Long cal_id){
    Calendar calendar = calendarRepository.findById(cal_id).orElse(null);
    return new CalendarResponseDto(calendar);
}

단건 조회의 경우 new 생성자에서 캘린더 엔티티 통으로 전달해주면 된다.

뭐.. 빌더로 서비스단에서 만들어줄 수도 있고 방법은 여러가지이며 나는 이후에 list에서도 쓰일 new 를 위해 통일했다

 

 

// 달력 조회
public List<CalendarResponseDto> findCalendarList(Long groupId){
    Group group = groupService.findGroup(groupId);
    List<Calendar> calendarByGroup = calendarRepository.findCalendarByGroup(group);
    return calendarByGroup.stream().map(CalendarResponseDto::new).collect(Collectors.toList());
}

달력 목록 조회하는 경우~

java 8 이상 람다를 사용해 한줄로 표현했다.

포문 돌려서 안박아줘도 되니 넘 편해 개꿀탱