[Spring] JSTL로 화면에 D-day 보여주기

우롱밀크티당도70·2023년 8월 15일
0

Spring

목록 보기
3/4
post-thumbnail
post-custom-banner

1. 배경

친구랑 토이 프로젝트를 진행하는데 주제로 <투두 리스트 웹 사이트>를 만들기로 했다. 각자 잘 사용 하고 있는 앱인 타임블럭스(TimeBlocks)와 투두메이트(todomate)를 참고로 했다.

이런 화면이 나오도록 화면 설계서를 작성했고 이 글에서는 왼쪽 상단의
D-day 구현기를 적어보도록 하겠다...


2. 개발환경

  • Java 1.8
  • SpringFramework
  • Oracle Database
  • MyBatis

3. 구현하기

3-1. 테이블 설계

회원 가입을 한 사용자가 할 일 리스트를 적을 카테고리를 생성하고 그 카테고리에 일정을 추가할 수 있도록 테이블을 설계했다.

3-2. UI 구현하기

  • 부트스트랩을 사용.
  • html 코드는 D-day 구현에 관련된 부분만 작성한다...

<div class="input-group custom-search-form">
  <legend style="border-bottom: none;"><a style="text-decoration: none; font-weight: bold;">D-day</a></legend>
  <ul class="nav">
    <li style="border-bottom: none;">
    	<a>내용</a>
    </li>
  </ul>
</div>

3-3. DB에서 D-day 목록 읽어오기

앞서 일정(Schedule)테이블을 설계할 때 디데이(Dday) 컬럼이 있었다. 사용자가 일정을 등록할 때 디데이가 아니면 0, 디데이면 1, 기념일이면 2가 입력되게 했다.

3-3-1. VO 작성하기(DdayVO.java)

/**
 * 일정(SCHEDULE) + 반복(REPEAT)
 * 
 */

@Data
public class DdayVO {
	
	private ScheduleVO schedule;	/* ScheduleVO */
	private RepeatVO repeat;		/* RepeatVO */
}

Schedule(일정) 테이블

Repeat(반복) 테이블

3-3-2. Mapper 작성하기(DdayMapper.java)

두 개 이상의 파라미터를 넘겨주기 위해 @Param 어노테이션을 사용한다.

public interface DdayMapper {
	
	public List<DdayVO> selectDdayList(@Param("member_no") int member_no);

}

3-3-3. Service 작성하기(DdayService.java, DdayServiceImpl.java)

(DdayService.java)

public interface DdayService {
	
	public List<DdayVO> selectDdayList(@Param("member_no") int member_no);

}

(DdayServiceImpl.java)

@Log4j
@Service
@AllArgsConstructor
public class DdayServiceImpl implements DdayService {

	@Autowired
	private DdayMapper ddayMapper;
	
	@Override
	public List<DdayVO> selectDdayList(int member_no) {
		log.info("Service :: selectDdayList..................");
		return ddayMapper.selectDdayList(member_no);
	}
}

3-3-4. MyBatis 쿼리 작성하기(DdayMapper.xml)

selectDdayList의 return 타입은 DdayVO인데 DdayVO는 ScheduleVO와 RepeatVO를 사용하기 때문에 resultMap으로 ScheduleVO와 RepeatVO의 프로퍼티와 컬럼명을 매핑한다.

<?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="com.todocalendar.mapper.DdayMapper">
	
	<resultMap type="com.todocalendar.model.ScheduleVO" id="scheduleMap">
		<id property="schedule_no" column="schedule_no" />
		<id property="member_no" column="member_no" />
		<id property="category_no" column="category_no" />
		<id property="content" column="content" />
		<id property="plan_date" column="plan_date" />
		<id property="dday" column="dday" />
		<id property="dday_nm" column="dday_nm" />
		<id property="dday_cnt" column="dday_cnt" />
		<id property="complete" column="complete" />
	</resultMap>
	
	<resultMap type="com.todocalendar.model.RepeatVO" id="repeatMap">
		<id property="schedule_no" column="schedule_no" />
		<id property="schedule_detail_no" column="schedule_detail_no" />
		<id property="start_date" column="start_date" />
		<id property="end_date" column="end_date" />
		<id property="re_devision" column="re_devision" />
		<id property="re_date" column="re_date" />
	</resultMap>
	
	<resultMap type="com.todocalendar.model.DdayVO" id="DdayVO">
		<collection property="schedule" resultMap="scheduleMap" />
		<collection property="repeat" resultMap="repeatMap" />
	</resultMap>
	
	<select id="selectDdayList" resultMap="DdayVO">
		SELECT *
		  FROM SCHEDULE, REPEAT
		 WHERE SCHEDULE.SCHEDULE_NO = REPEAT.SCHEDULE_NO(+)
		   AND SCHEDULE.MEMBER_NO = #{member_no}
	</select>

</mapper>

3-4. Controller 작성하기

  • 로그인한 사용자의 정보가 페이지에 존재한다는 가정 하에 작성한다.
  • 디데이 목록을 읽어오는 메소드 selectDdayList는 파라미터로 member_no(회원번호)를 필요로 한다.
  • 메소드를 호출하여 읽어온 목록을 List형 변수 ddayList에 초기화 하여 model.addAttribute() 메소드로 View에 데이터를 전달한다.
@Controller
@Log4j
@AllArgsConstructor
public class HomeController {

	@Autowired
	MemberService memberService;

	@Autowired
	CategoryService categoryService;

	@Autowired
	ScheduleService scheduleService;

	@Autowired
	DdayService ddayService;
	
	@Autowired
	PaletteService paletteService;
    
    @GetMapping("/home")
	public void mainPage(Model model, HttpServletRequest request, HttpSession session) {
		log.info("main page.........");

		MemberVO member = (MemberVO)session.getAttribute("member");

		List<CategoryVO> categoryList = categoryService.selectCategoryList(member.getMember_no());
		List<ScheduleVO> scheduleList = scheduleService.selectScheduleListAll(member.getMember_no());

		model.addAttribute("categoryList", categoryList);
		model.addAttribute("scheduleList", scheduleList);
		
		List<DdayVO> ddayList = ddayService.selectDdayList(member.getMember_no());
		log.info("member_no : " + member.getMember_no() + ", ddayList : " + ddayList);
		
		model.addAttribute("ddayList", ddayList);
		
		CountVO scheduleCount = scheduleService.selectScheduleListByCount(member.getMember_no());
		log.info("count : " + scheduleCount);
		
		//scheduleCount의 내용이 null이 아닐때에 아래의 내용을 수행
		if(scheduleCount != null) {
			model.addAttribute("y_count", scheduleCount.getY_count());
			model.addAttribute("n_count", scheduleCount.getN_count());
			model.addAttribute("all_count", scheduleCount.getAll_count());
			
			List<PaletteVO> paletteList = paletteService.selectPaletteListAll();
			model.addAttribute("paletteList", paletteList);
		}
		
	}

}

3-5. JSTL <c:forEach>, <c:if>, <c:set> 태그 사용하기

3-5-1. 일정의 내용을 표시하기

  • <c:forEach> 태그는 List나 배열 목록을 반복해서 출력할 때 사용한다.
  • <c:if> 태그로 Dday 컬럼의 값이 1인 일정의 내용을 표시한다.
  • model.addAttribute로 전달 받은 데이터 ddayList의 이름을 dday로 지정했고 ddayList의 타입은 DdayVO인데 DdayVO는 ScheduleVO와 RepeatVO로 구성되어 있기 때문에 ScheduleVO의 content를 가져오려면 dday.schedule.cotent로 접근한다.

<div class="input-group custom-search-form">
  <legend style="border-bottom: none;"><a style="text-decoration: none; font-weight: bold;">D-day</a></legend>
  <ul class="nav">
    <li style="border-bottom: none;">
		<c:forEach var="dday" items="${ddayList }" varStatus="status">
          <!-- DDAY 컬럼의 값이 1(D-day)인 것만 보여준다. -->
          <c:if test="${dday.schedule.dday == 1 }">
          	<a>${dday.schedule.content }</a>
          </c:if>
        </c:forEach>
    </li>
  </ul>
</div>

3-5-2. 일정의 내용과 날짜 표시하기

<div class="input-group custom-search-form">
  <legend style="border-bottom: none;"><a style="text-decoration: none; font-weight: bold;">D-day</a></legend>
  <ul class="nav">
    <li style="border-bottom: none;">
		<c:forEach var="dday" items="${ddayList }" varStatus="status">
          <!-- DDAY 컬럼의 값이 1(D-day)인 것만 보여준다. -->
          <c:if test="${dday.schedule.dday == 1 }">
          	<a>${dday.schedule.content } ${dday.schedule.plan_date }</a>
          </c:if>
        </c:forEach>
    </li>
  </ul>
</div>

3-5-3. 현재 날짜와 특정 날짜 사이의 일수 계산하기

plan_date만 표시했더니 데이터베이스에 23/08/31로 들어간 데이터가 Thu Aug 31 00:00:00 KST 2023으로 나왔다.
원하는건 D-(일수) 형식의 표기이기 때문에 현재 날짜와 plan_date 사이의 일수를 계산해야 한다.

  • 먼저 JSTL로 Date 객체를 사용하여 현재 날짜를 알기 위해 상단에 Bean을 선언.
  • 날짜 표기를 지정한 형식으로 보여주기 위해 fmt 태그를 사용해야 한다.
<jsp:useBean id="now" class="java.util.Date" />
<fmt:formatDate var="now_FD" value="${now }" pattern="yyyy-MM-dd"/>
  • formatDate는 날짜 및 시간 값을 지정한 형식으로 변경해준다.
  • parseDate는 String타입으로 표시된 날짜 및 시간 값을 Date타입으로 파싱해준다.
  • parseNumber는 숫자로 파싱해준다.
<fmt:formatDate var="planDate_FD"  value="${dday.schedule.plan_date }" pattern="yyyy-MM-dd"/>

<fmt:parseDate var="now_PD" value="${now_FD }" pattern="yyyy-MM-dd" />
<fmt:parseDate var="planDate_PD" value="${planDate_FD }" pattern="yyyy-MM-dd" />

<fmt:parseNumber var="now_PN" value="${now_PD.time/(1000*60*60*24) }" integerOnly="true" />
<fmt:parseNumber var="planDate_PN" value="${planDate_PD.time/(1000*60*60*24) }" integerOnly="true" />
  • 지정 날짜에서 현재 날짜의 일 수 차이를 구하고 그 값이 (-)가 아닐 때에만 D-day와 내용을 보여준다.
    이 글을 작성하는 날짜 8월 15일로부터 합격 발표까지 16일 남았다고 한다...
<c:set var="d_day" value="${planDate_PN-now_PN }" />
<c:if test="${d_day > 0 }">
	<a>· ${dday.schedule.content } D-${d_day }</a>
</c:if>


4. 결과

최종 화면.
Dday 컬럼이 2인 것만 읽어와서 현재 날짜 - 지정 날짜의 일 수 차이(과거의 날짜에서 현재 날짜까지 얼마나 지나왔는지)를 구하고 그 값이 (-)가 아닐 때에만 내용과 일 수를 보여준다면 D+기념일도 보여줄 수 있다.


5. 생각해볼 점, 개선이 필요한 부분

  1. JSTL로 fmt 태그를 너무 남발하지 않았는지? 솔직히 지저분해 보인다...
  2. 디데이와 기념일 내용을 클릭하면 modal로 내용을 수정할 수 있는 기능도 필요하다...
  3. 현재 날짜를 가져와서 계산한건데 컴퓨터 시간이나 다른 나라면 어떻게 작동 되는지...
profile
안뇽하세용
post-custom-banner

0개의 댓글