'์คํ๋ง๋ถํธ(SpringBoot)๋ก ํ์บ๋ฆฐ๋(FullCalendar) ๊ตฌํํ๊ธฐ - 1' ๊ฒ์๊ธ์ ํตํด์ ๊ธฐ๋ณธ์ ์ธ ์บ๋ฆฐ๋๋ฅผ ๊ตฌ์ฑํ๋ ๋ฐฉ๋ฒ์ ๋ํด์ ์์๋ณด์์ต๋๋ค. ํ์บ๋ฆฐ๋ ๊ตฌํํ๊ธฐ ๋ ๋ฒ์งธ ๊ฒ์๊ธ์์๋ ์ฒซ ๋ฒ์งธ ๊ฒ์๊ธ์์ ๋ง์ง๋ง ๋ฐฉ์์ ํด๋นํ๋ '์ผ์ ๋ชฉ๋ก์ ์์ฒญํ URL๋ก ํ์บ๋ฆฐ๋์ ์ผ์ ์ถ๊ฐํ๊ธฐ'๋ฅผ ๊ตฌ์ฒด์ ์ธ ๋ฐฉ๋ฒ๊ณผ ํ๋ฆ์ผ๋ก ํ์ธํด ๋ณด๊ฒ ์ต๋๋ค.
ํ์บ๋ฆฐ๋ ์ค์ ํ๊ธฐ
์ ์ฒด ์์ค
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.14/index.global.min.js"></script>
</head>
<body>
<div id="calendar"></div>
</body>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function() {
let calendarEl = document.getElementById('calendar');
let headerToolbar = {
left: 'prevYear,prev,next,nextYear today',
center: 'title',
right: 'dayGridMonth,dayGridWeek,timeGridDay'
}
let calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth',
headerToolbar: headerToolbar,
events: "/scheduleList.do"
});
calendar.render();
});
</script>
</html>
์ ์ฉ ํ๋ฉด
๊ฐ์ฅ ๋จผ์ , 'events' ์์ฑ์ ์ค์ ๋ ์ผ์ ๋ชฉ๋ก์ ์กฐํํ๊ธฐ ์ํ ์์ฒญ '/scheduleList.do'๋ฅผ ์ปจํธ๋กค๋ฌ์์ ์ง์ ๊ตฌ์ฑํ List ๋ฐ์ดํฐ๋ฅผ ์๋ต์ผ๋ก ๋ฐ์์ค๋ ํ์์ผ๋ก ๋จผ์ ํ ์คํธํด๋ด ๋๋ค.
์๋ฒ ์ค์ ํ๊ธฐ
1. ์ง์ ๊ตฌ์ฑํ List ๋ฐ์ดํฐ ์๋ต์ผ๋ก ๋ฐ๊ธฐ
์ปจํธ๋กค๋ฌ์์ ์์ฒญ('/scheduleList.do')์ ๋ฐ์ผ๋ฉด ์ง์ ๊ตฌ์ฑํ List ํ์ ์ ๋ฐ์ดํฐ๋ฅผ ์๋ต์ผ๋ก ๋ฆฌํดํฉ๋๋ค. ๋ฆฌํด๋ ์๋ต ๋ฐ์ดํฐ๋ 'List<Map<String, Object>>' ํ์ ์ผ๋ก ๊ตฌ์ฑ๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ์ผ์ ์ ์ถ๋ ฅํฉ๋๋ค.
์ปจํธ๋กค๋ฌ ๊ตฌ์ฑ
@Controller
public class ScheduleController {
@GetMapping("/schedule.do")
public String schedulePage() {
return "schedule";
}
@ResponseBody
@GetMapping("/scheduleList.do")
public ResponseEntity<List<Map<String, Object>>> scheduleList() {
// ์ผ์ Map์ ๋ด์ List ์ ์ธ
List<Map<String, Object>> scheduleList = new ArrayList<>();
// ์ผ์ ์ฒซ๋ฒ์งธ๋ฅผ ๊ตฌ์ฑํ Map ์ ์ธ
Map<String, Object> schedule01 = new HashMap<>();
schedule01.put("id", "sch01"); // ์ฒซ๋ฒ์งธ ์ผ์ ์์ด๋
schedule01.put("title", "์ผ์ 01"); // ์ฒซ๋ฒ์ฌ ์ผ์ ์ ๋ชฉ
schedule01.put("start", "2025-05-13"); // ์ฒซ๋ฒ์งธ ์ผ์ ์์ ์ผ์
schedule01.put("end", "2025-05-13"); // ์ฒซ๋ฒ์งธ ์ผ์ ๋ ์ผ์
schedule01.put("allDay", true); // ์ฒซ๋ฒ์ฌ ์ผ์ ํ๋ฃจ์ข
์ผ flag
// ์ผ์ ๋๋ฒ์งธ๋ฅผ ๊ตฌ์ฑํ Map ์ ์ธ
Map<String, Object> schedule02 = new HashMap<>();
schedule02.put("id", "sch02"); // ๋๋ฒ์งธ ์ผ์ ์์ด๋
schedule02.put("title", "์ผ์ 02"); // ๋๋ฒ์งธ ์ผ์ ์ ๋ชฉ
schedule02.put("start", "2025-05-14T09:00:00"); // ๋๋ฒ์งธ ์ผ์ ์์ ์ผ์
schedule02.put("end", "2025-05-16T09:00:00"); // ๋๋ฒ์ฌ ์ผ์ ๋ ์ผ์
schedule02.put("allDay", false); // ๋๋ฒ์ฌ ์ผ์ ํ๋ฃจ์ข
์ผ flag
scheduleList.add(schedule01); // ์ฒซ๋ฒ์ฌ ์ผ์ List์ ์ถ๊ฐ
scheduleList.add(schedule02); // ๋๋ฒ์ฌ ์ผ์ List์ ์ถ๊ฐ
return new ResponseEntity<List<Map<String, Object>>>(scheduleList, HttpStatus.OK);
}
}
์ปจํธ๋กค๋ฌ ๊ตฌ์ฑ์ ํ์ธํด ๋ณด๋ฉด schedulePage() ๋ฉ์๋๋ ์บ๋ฆฐ๋ ํ๋ฉด์ ์ถ๋ ฅํ๊ธฐ ์ํด์ @GetMapping ์ด๋ ธํ ์ด์ ์ผ๋ก ์ค์ ๋ ๋ชฉ์ ์ง์ด๊ณ , scheduleList() ๋ฉ์๋๋ ์บ๋ฆฐ๋ ์ด๋ฒคํธ์์ events ์์ฑ์ ๋ช ์๋ ์์ฒญ์ ์ฒ๋ฆฌํ๊ธฐ ์ํด์ @PostMapping ์ด๋ ธํ ์ด์ ์ผ๋ก ์ค์ ๋ ๋ชฉ์ ์ง๊ฐ ๋ฉ๋๋ค. ์์ฒญ์ ๋ฐ๋ฅธ ์๋ต์ ํ์ด์ง ์ ๋ณด๊ฐ ์๋ ๋ฐ์ดํฐ๋ฅผ ์๋ต์ผ๋ก ๋ฆฌํดํ๊ธฐ ์ํด์ ResponseEntity ์๋ต ๊ฐ์ฒด ํ์ ์ผ๋ก ์ค์ ํ์์ต๋๋ค. ResponseEntity ์๋ต ๊ฐ์ฒด๋ ํค๋์ ๋ณด, ๋ฐ์ดํฐ๋ฅผ ๋ด์ ์ ์๋ Body, ์๋ต์ ๋ฐ๋ฅธ ์ํ ์ฝ๋์ธ HttpStatus์ ์ ๋ณด๋ฅผ ๊ฐ์ง๊ณ ์๊ธฐ ๋๋ฌธ์ @ResponseBody์ ๊ฐ์ ์ด๋ ธํ ์ด์ ์ผ๋ก ์๋ต์ผ๋ก ๋ด๋ณด๋ผ ์ ๋ณด๊ฐ 'ํ์ด์ง ์ ๋ณด๊ฐ ์๋ ๋ฐ์ดํฐ์ ๋๋ค.'๋ฅผ ๋ช ์ํ๊ณ ์์ต๋๋ค. ๊ทธ๋ ์ง๋ง @ResponseBody ์ด๋ ธํ ์ด์ ์ด ๊ผญ ํ์ํ์ง ์์ง๋ง ๋ถ์ฌ์ค ์ด์ ๋ ์ง๊ด์ฑ ๋๋ฌธ์ ํด๋น ๋ฉ์๋์ ๋ถ์ฌ๋์์ต๋๋ค.
์ ์ฉ ํ๋ฉด
์ ์ฉ ํ๋ฉด์ ํตํด์ ํ์ธ๋ ๊ฒ์ฒ๋ผ ์ผ์ ์ Map ํํ๋ก ๋ง๋ค๊ณ ๋ง๋ค์ด์ง ์ผ์ ์ List์ ์ถ๊ฐ ํ ์๋ต ๋ฐ์ดํฐ๋ก ๋ฆฌํดํ๋ ํ์บ๋ฆฐ๋ ํ๋ฉด์ ์ผ์ ๋ ๊ฐ๊ฐ ์ถ๊ฐ๋ ๊ฑธ ํ์ธํ ์ ์์ต๋๋ค. ๊ทธ๋ ๋ค๋ฉด ์ ํํ๋ฅผ ๊ทธ๋๋ก DB๋ก ์ฎ๊ฒจ ๋์ผํ ๊ฐ๋ค์ ๊ฐ์ง ์ ์๋๋ก ์ค์ ํด ๋ณด๊ฒ ์ต๋๋ค.
2. DB ์ฐ๋ ํ, ์ผ์ ๋ชฉ๋ก ๋ฐ์ดํฐ ๋ฐ๊ธฐ
์ปจํธ๋กค๋ฌ ๊ตฌ์ฑ
@Controller
public class ScheduleController {
@Autowired
private IScheduleService service;
@GetMapping("/schedule.do")
public String schedulePage() {
return "schedule";
}
@ResponseBody
@GetMapping("/scheduleList.do")
public ResponseEntity<List<ScheduleVO>> scheduleList() {
List<ScheduleVO> scheduleList = service.selectScheduleList();
return new ResponseEntity<List<ScheduleVO>>(scheduleList, HttpStatus.OK);
}
}
์๋น์ค ๊ตฌ์ฑ
public interface IScheduleService {
public List<ScheduleVO> selectScheduleList();
}
@Service
public class ScheduleServiceImpl implements IScheduleService {
@Autowired
private IScheduleMapper mapper;
@Override
public List<ScheduleVO> selectScheduleList() {
List<ScheduleVO> list = mapper.selectScheduleList();
changeAllDayStatus(list); // ์กฐํ๋ ์ผ์ ๋ชฉ๋ก์ allDay ์ํ ๊ฐ ์ฒ๋ฆฌ
return list;
}
// allDayStatus ๊ฐ์ ๋ฐ๋ผ allDay true/false ์ฒ๋ฆฌ
public void changeAllDayStatus(List<ScheduleVO> list) {
for(int i = 0; i < list.size(); i++) {
ScheduleVO schedule = list.get(i); // ์ผ์ ๋ชฉ๋ก์์ ์ผ์ ๊บผ๋ด๊ธฐ
String status = schedule.getAllDayStatus(); // ํ๋ฃจ์ข
์ผ ์ํ ๊ฐ ๊บผ๋ด๊ธฐ(TRUE, FALSE)
if(status.equals("FALSE")) { // ์ํ ๊ฐ FALSE ์ผ ๋
schedule.setAllDay(false); // bool ํ์
์ false ์ค์
continue;
}
schedule.setAllDay(true); // ์ํ ๊ฐ TRUE ์ผ ๋, bool ํ์
์ true ์ค์
}
}
}
Mapper ๊ตฌ์ฑ
@Mapper
public interface IScheduleMapper {
public List<ScheduleVO> selectScheduleList();
}
Mapper XML ๊ตฌ์ฑ
<?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="kr.or.ddit.mapper.IScheduleMapper">
<select id="selectScheduleList" resultType="kr.or.ddit.vo.ScheduleVO">
select
sch_no as "no",
sch_id as "id",
sch_title as "title",
sch_start as "start",
sch_end as "end",
sch_backgroundcolor as "backgroundColor",
sch_textcolor as "textColor",
sch_allday as "allDayStatus"
from schedule
</select>
</mapper>
๋ฐ์ดํฐ๋ฒ ์ด์ค ํ ์ด๋ธ ๊ตฌ์ฑ
VO ํด๋์ค ๊ตฌ์ฑ
@Data
public class ScheduleVO {
private int no;
private String id;
private String title;
private String start;
private String end;
private String backgroundColor;
private String textColor;
private String allDayStatus;
private boolean allDay;
}
์ ์ฉ ํ๋ฉด
DB๋ฅผ ํตํด์ ์กฐํ๋ ์ผ์ ๋ชฉ๋ก ๋ฐ์ดํฐ๋ฅผ 'Controller - Service - Mapper - DB' ์์๋ก ๊ฐ์ ธ์ต๋๋ค. ๊ฐ์ ธ์จ ๋ฐ์ดํฐ๋ฅผ ์์ฒญ์ ํธ์ถํ callback์ผ๋ก ์๋ตํฉ๋๋ค. ์๋ต๋ ์ผ์ ๋ชฉ๋ก ๋ฐ์ดํฐ๋ events ์์ฑ์ ์ํด์ ํ์บ๋ฆฐ๋ ํ๋ฉด์ ์ผ์ ์ ์ถ๋ ฅํฉ๋๋ค.
@Junesker