250107 MongoDB (웹 데이터 저장/읽기)

250107 에이콘 아카데미 수업에 기반하여 작성되었음을 알립니다.


MongoDB - Spring Boot (빅데이터)

build.gradle

... 생략

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
	
	// JSON 라이브러리 추가
    implementation 'org.json:json:20210307'
	
}

tasks.named('test') {
	useJUnitPlatform()
}

application.properties

spring.application.name=mongo5_boot_bigdata

# MongoDB 설정
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017

KaData

@Data
@Document(collection = "katalkfiles")
public class KaData {
	@Id
	private String id;
	
	private String req;
	private String res;
}

KaDataController

@Controller
public class KaDataController {
    @Autowired
    private MongoClient mongoClient;
    
    @GetMapping("/")
    public String sijak() {
        return "index"; 
    }

    // /show 경로에 요청이 오면 show.html을 반환
    @GetMapping("/show")
    public String showChatData(Model model) {
        List<KaData> kaDataList = new ArrayList<>(); // 데이터를 저장할 리스트

        // MongoDB의 GridFSBucket에서 파일을 가져온다.
        GridFSBucket gridFSBucket = GridFSBuckets.create(mongoClient.getDatabase("katalkdb"), "katalkfiles");

        try {
            // GridFS에서 저장된 파일을 하나씩 가져온다.
            for (GridFSFile gridFSFile : gridFSBucket.find()) {
                ObjectId fileId = gridFSFile.getObjectId();

                // 파일 다운로드
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                gridFSBucket.downloadToStream(fileId, outputStream);
                String fileContent = new String(outputStream.toByteArray());

                // JSON 파싱
                if (fileContent.trim().startsWith("[")) {
                    JSONArray jsonArray = new JSONArray(fileContent);
                    for (int i = 0; i < jsonArray.length(); i++) {
                        JSONObject jsonObject = jsonArray.getJSONObject(i);
                        KaData kaData = new KaData();
                        kaData.setReq(jsonObject.getString("req"));
                        kaData.setRes(jsonObject.getString("res"));
                        kaDataList.add(kaData);
                    }
                } else {
                    JSONObject jsonObject = new JSONObject(fileContent);
                    KaData kaData = new KaData();
                    kaData.setReq(jsonObject.getString("req"));
                    kaData.setRes(jsonObject.getString("res"));
                    kaDataList.add(kaData);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        model.addAttribute("dataList", kaDataList);     // 데이터를 모델에 추가
        return "show";     // show.html 파일을 반환
    }
}

index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<a href="/show">몽고 데이터 보여 줘</a>
</body>
</html>

show.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<head>
<meta charset="UTF-8">
<title>Kakao Data</title>
<style>
    table {
        width: 100%;
        border-collapse: collapse;
    }
    th, td {
        border: 1px solid black;
        padding: 8px;
        text-align: left;
    }
    th {
        background-color: #f2f2f2;
    }
</style>
</head>
<body>
<h1>카카오 데이터</h1>
<table>
    <thead>
        <tr><th>질문</th><th>답변</th></tr>
    </thead>
    <tbody>
        <tr th:each="kdata : ${dataList}">
            <td th:text="${kdata.req}"></td>
            <td th:text="${kdata.res}"></td>
        </tr>
    </tbody>
</table>
</body>
</html>


MongoDB - Spring Boot (Schedule CRUD)

application.properties

spring.application.name=mongo06_boot_schedule

spring.data.mongodb.uri=mongodb://localhost:27017/scheduledb
spring.data.mongodb.database=scheduledb

Schedule

@Data
@Document(collection = "schedules")
public class Schedule {
	
	@Id
	private String id;
	
	private String title;
	private String description;
	private LocalDateTime startTime;
	private LocalDateTime endTime;
}

ScheduleRepository

public interface ScheduleRepository extends MongoRepository<Schedule, String> {

}

ScheduleService

@Service
public class ScheduleService {
	
	private final ScheduleRepository scheduleRepository;
	
	public ScheduleService(ScheduleRepository scheduleRepository) {
		this.scheduleRepository = scheduleRepository;
	}
	
	public List<Schedule> getAllSchedules() {
		return scheduleRepository.findAll();
	}
	
	public Optional<Schedule> getScheduleById(String id) {
		return scheduleRepository.findById(id);
	}
	
	public Schedule createSchedule(Schedule schedule) {
		return scheduleRepository.save(schedule);
	}
	
	public Schedule updateSchedule(String id, Schedule schedule) {
		schedule.setId(id);
		return scheduleRepository.save(schedule);
	}
	
	public void deleteSchedule(String id) {
		scheduleRepository.deleteById(id);
	}
}

WebMvcConfig

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
	
	// custom formatter나 변환기를 등록할 수 있다.
	@Override
	public void addFormatters(FormatterRegistry registry) {
		// DateTimeFormatterRegistrar : 날짜와 시간에 대한 형식 설정을 관리
		DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
		
		// 날짜와 시간을 "YYYY-MM-DD'T HH:MM:SS 형식으로 변환하기 위한 ISO 표준값 중 하나
		// 예) 2025-01-07T10:09:15
		registrar.setDateTimeFormatter(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
		
		registrar.registerFormatters(registry);
	}
}

MainController

@Controller
public class MainController {
	
	@GetMapping("/")
	public String main() {
		return "redirect:/schedules";
	}
}

ScheculeController (읽기)

@Controller
@RequestMapping("/schedules")
public class ScheculeController {
	
	private final ScheduleService scheduleService;
	
	public ScheculeController(ScheduleService scheduleService) {
		this.scheduleService = scheduleService;
	}
	
	@GetMapping
	public String getAllSchedules(Model model) {
		List<Schedule> schedules = scheduleService.getAllSchedules();
		
		model.addAttribute("schedules", schedules);
		return "schedule/list";
	}
	
	@GetMapping("/new")
	public String createform(Model model) {
		model.addAttribute("schedule", new Schedule());
		return "schedule/create"; // create.html에서 Schedule 객체 접근 가능
	}
	
	// @ModelAttribute : 다른 어노테이션보다 먼저 수행된다.
	@PostMapping
	public String createSchedule(@ModelAttribute("schedule") Schedule schedule) {
		scheduleService.createSchedule(schedule);
		return "redirect:/schedules";
	}
}

list.html

<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>스케쥴 관리</title>
<link rel="stylesheet" th:href="@{/css/mystyles.css}">
<script th:inline="javascript">
function confirmDelete(event, id) {
	event.preventDefault();
	if(confirm("정말 삭제?")) {
		window.location.href = "/schedules/delete/" + id;
	}
}
</script>
</head>
<body>
	<h2>일정 목록</h2>
	<a href="/schedules/new" class="button primary">새 일정 추가</a>
	<table class="styled-table">
		<thead>
			<tr>
				<th>제목</th><th>설명</th><th>시작시간</th><th>종료시간</th><th>작업</th>
			</tr>
		</thead>
		<tbody>
			<tr th:each="schedule:${schedules}">
				<td th:text="${schedule.title}">제목</td>
				<td th:text="${schedule.description}">설명</td>
				<td th:text="${#temporals.format(schedule.startTime, 'yyyy-MM-dd HH:mm')}">시작시간</td>
				<td th:text="${#temporals.format(schedule.endTime, 'yyyy-MM-dd HH:mm')}">종료시간</td>
				<td>
					<a th:href="@{'/schedules/edit/' + ${schedule.id}}" class="button secondary">수정</a>
					<a href="#" class="button danger" th:data-id="${schedule.id}"
						onclick="confirmDelete(event, this.getAttribute('data-id'))">삭제</a>
				</td>
			</tr>
		</tbody>
	</table>
</body>
</html>

ScheculeController (추가)

@Controller
@RequestMapping("/schedules")
public class ScheculeController {
	... 생략
	
	@GetMapping("/new")
	public String createform(Model model) {
		model.addAttribute("schedule", new Schedule());
		return "schedule/create"; // create.html에서 Schedule 객체 접근 가능
	}
	
	// @ModelAttribute : 다른 어노테이션보다 먼저 수행된다.
	@PostMapping
	public String createSchedule(@ModelAttribute("schedule") Schedule schedule) {
		scheduleService.createSchedule(schedule);
		return "redirect:/schedules";
	}
}

create.html

<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>새 일정 추가</title>
<link rel="stylesheet" th:href="@{/css/mystyles.css}">
</head>
<body>
	<h2>새 일정 추가</h2>
	<form th:action="@{/schedules}" th:object="${schedule}" method="post">
		제목 : <input type="text" th:field="*{title}" /><br />
		내용 : <textarea th:field="*{description}"></textarea><br />
		시작 시간 : <input type="datetime-local" th:field="*{startTime}" /><br />
		종료 시간 : <input type="datetime-local" th:field="*{endTime}" /><br />
		<button type="submit">저장</button>
	</form>
	<br/>
	<a href="/schedules">일정 목록</a>
</body>
</html>

ScheculeController (수정)

@Controller
@RequestMapping("/schedules")
public class ScheculeController {
	
	... 생략
    
	@GetMapping("/edit/{id}")
	public String editform(@PathVariable(name = "id")String id, Model model) {
		Schedule schedule = scheduleService.getScheduleById(id)
				.orElseThrow(() -> new IllegalArgumentException("invalid id : " + id));
		model.addAttribute("schedule", schedule);
		return "schedule/edit";
	}
	
	@PostMapping("/update/{id}")
	public String editschedule(@PathVariable(name = "id")String id, @ModelAttribute("schedule") Schedule schedule) {
		scheduleService.updateSchedule(id, schedule);
		return "redirect:/schedules";
	}
}

edit.html

<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>일정 수정</title>
<link rel="stylesheet" th:href="@{/css/mystyles.css}">
</head>
<body>
	<h2>일정 수정</h2>
	<form th:action="@{'/schedules/update/' + ${schedule.id}}" th:object="${schedule}" method="post">
		제목 : <input type="text" th:field="*{title}" /><br />
		내용 : <textarea th:field="*{description}"></textarea><br /> 
		시작 시간 : <input type="datetime-local" th:field="*{startTime}" /><br />
		종료 시간 : <input type="datetime-local" th:field="*{endTime}" /><br />
		<button type="submit">저장</button>
	</form>
	<br/>
	<a href="/schedules">일정 목록</a>
</body>
</html>

ScheculeController (삭제)

@Controller
@RequestMapping("/schedules")
public class ScheculeController {
	
	... 생략
	
	@GetMapping("/delete/{id}")
	public String deleteschedule(@PathVariable(name = "id")String id) {
		scheduleService.deleteSchedule(id);
		return "redirect:/schedules";
	}
}

 

mystyles.css

@charset "UTF-8";
body {
    font-family: Arial, sans-serif;
    margin: 20px;
}

h1 {
    color: #333;
}

form {
    max-width: 600px;
}

label {
    display: block;
    margin-top: 15px;
    font-weight: bold;
}

input[type="text"],
input[type="datetime-local"],
textarea {
    width: 100%;
    padding: 8px;
    box-sizing: border-box;
}

textarea {
    height: 150px;
    resize: vertical;
}

button {
    margin-top: 20px;
    padding: 10px 20px;
    background-color: #4CAF50;
    color: #fff;
    border: none;
    cursor: pointer;
}

button:hover {
    background-color: #45a049;
}

a.button {
    display: inline-block;
    margin-bottom: 15px;
    padding: 10px 20px;
    background-color: #007BFF;
    color: #fff;
    text-decoration: none;
    border-radius: 5px;
    font-weight: bold;
}

a.button.primary {
    background-color: #28a745;
}

a.button.secondary {
    background-color: #007BFF;
}

a.button.danger {
    background-color: #dc3545;
}

a {
    display: inline-block;
    margin-top: 20px;
    color: #007BFF;
    text-decoration: none;
}

a:hover {
    text-decoration: underline;
}

table.styled-table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 15px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

table.styled-table thead tr {
    background-color: #009879;
    color: white;
    text-align: left;
    font-weight: bold;
}

table.styled-table th,
table.styled-table td {
    padding: 12px 15px;
}

table.styled-table tbody tr {
    border-bottom: 1px solid #dddddd;
}

table.styled-table tbody tr:nth-of-type(even) {
    background-color: #f3f3f3;
}

table.styled-table tbody tr:last-of-type {
    border-bottom: 2px solid #009879;
}