Spring Legacy 게시판 만들기 2편 - MVC 패턴 기반 CRUD 구현 (Controller, Service, Mapper)
📚 Spring Legacy 게시판 만들기 시리즈
- 1편: 프로젝트 세팅하기
- 2편: MVC 패턴으로 뼈대 만들기 (현재 글)
- 3편: 화면 만들기와 완성
1편에서 세팅을 마쳤으니, 이제 진짜 게시판의 뼈대를 만들어볼 차례예요! Spring에서는 코드를 역할별로 나누어서 관리하는데, 이것을 MVC 패턴이라고 해요.
🎭 MVC 패턴이란?
MVC는 Model, View, Controller의 약자예요. 마치 레스토랑처럼 각자 맡은 역할이 있어요!
graph LR
subgraph "🍽️ 레스토랑"
A["👨🍳 주방<br/>(Model)"]
B["🧑💻 웨이터<br/>(Controller)"]
C["📋 메뉴판<br/>(View)"]
end
D["👤 손님"] --> B
B --> A
A --> B
B --> C
C --> D
style A fill:#ffeb3b
style B fill:#4caf50,color:#fff
style C fill:#2196f3,color:#fff
| 역할 | Spring에서 | 하는 일 | 레스토랑 비유 |
|---|---|---|---|
| Controller | @Controller | 요청을 받고 응답을 보냄 | 웨이터 🧑💻 |
| Service | @Service | 실제 로직 처리 | 주방장 👨🍳 |
| Mapper | Interface | 데이터베이스와 대화 | 창고 담당자 📦 |
| DTO | Class | 데이터를 담아 나름 | 트레이 🍽️ |
| View | JSP | 화면에 보여줌 | 메뉴판/접시 📋 |
📦 Step 1: DTO 만들기 - 데이터를 담는 그릇
DTO (Data Transfer Object) 는 데이터를 담아서 전달하는 역할을 해요.
package org.zerock.dto;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import lombok.*;
@Getter // 📖 값을 읽는 메서드 자동 생성
@Setter // ✏️ 값을 쓰는 메서드 자동 생성
@ToString // 📝 내용을 문자열로 출력
@AllArgsConstructor // 🏗️ 모든 필드를 받는 생성자
@NoArgsConstructor // 🆕 빈 생성자
@Builder // 🧩 빌더 패턴 사용 가능
public class BoardDTO {
private Long bno; // 글 번호
private String title; // 제목
private String content; // 내용
private String writer; // 작성자
private LocalDateTime regDate; // 등록일
private LocalDateTime updateDate; // 수정일
private boolean delFlag; // 삭제 여부
// 날짜를 문자열로 변환 (yyyy-MM-dd 형식)
public String getCreatedDate() {
if (regDate == null) {
return "";
}
return regDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}
}
💡 Lombok이란?
@Getter,@Setter같은 어노테이션을 붙이면 getter/setter 메서드를 자동으로 만들어줘요. 코드가 훨씬 짧아지죠!
🗺️ Step 2: Mapper 만들기 - 데이터베이스와 대화하기
Mapper는 데이터베이스와 Java 코드 사이에서 통역사 역할을 해요.
Mapper 인터페이스 (Java)
package org.zerock.mapper;
import org.zerock.dto.BoardDTO;
import java.util.List;
public interface BoardMapper {
// 📝 글 등록
int insert(BoardDTO dto);
// 📖 글 한 개 조회
BoardDTO selectOne(long bno);
// 🗑️ 글 삭제 (실제로는 삭제 표시만)
int remove(long bno);
// ✏️ 글 수정
int update(BoardDTO dto);
// 📋 글 목록 조회
List<BoardDTO> list();
}
Mapper XML (SQL 정의)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.zerock.mapper.BoardMapper">
<!-- 📝 글 등록 -->
<insert id="insert">
<selectKey keyProperty="bno" resultType="long" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
INSERT INTO tbl_board (title, content, writer)
VALUES (#{title}, #{content}, #{writer})
</insert>
<!-- 📖 글 한 개 조회 -->
<select id="selectOne" resultType="org.zerock.dto.BoardDTO">
SELECT * FROM tbl_board WHERE bno = #{bno}
</select>
<!-- 🗑️ 글 삭제 (soft delete) -->
<update id="remove">
UPDATE tbl_board SET delflag = true WHERE bno = #{bno}
</update>
<!-- ✏️ 글 수정 -->
<update id="update">
UPDATE tbl_board
SET title = #{title}, content = #{content},
updatedate = NOW(), delflag = #{delFlag}
WHERE bno = #{bno}
</update>
<!-- 📋 글 목록 (삭제되지 않은 것만) -->
<select id="list" resultType="org.zerock.dto.BoardDTO">
SELECT bno, title, writer, content, regdate
FROM tbl_board
WHERE delflag = false
ORDER BY bno DESC
</select>
</mapper>
📝 참고:
#{ }안에 적은 이름은 DTO의 필드명과 일치해야 해요! MyBatis가 자동으로 값을 넣어줍니다.
🔧 Step 3: Service 만들기 - 비즈니스 로직 처리
Service는 실제로 일을 처리하는 핵심 일꾼이에요.
package org.zerock.service;
import java.util.List;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.zerock.dto.BoardDTO;
import org.zerock.mapper.BoardMapper;
@Service // 🏷️ "나는 서비스야!" 라고 Spring에게 알림
@Log4j2 // 📜 로그 기능 사용
@RequiredArgsConstructor // 🔗 생성자 자동 주입
public class BoardService {
private final BoardMapper boardMapper;
// 📋 글 목록 조회
public List<BoardDTO> getList() {
return boardMapper.list();
}
// 📝 글 등록
public Long register(BoardDTO dto) {
int insertCount = boardMapper.insert(dto);
log.info("insertCount : " + insertCount);
return dto.getBno(); // 새로 생성된 글 번호 반환
}
// 📖 글 한 개 읽기
public BoardDTO read(Long bno) {
return boardMapper.selectOne(bno);
}
// 🗑️ 글 삭제
public void remove(Long bno) {
boardMapper.remove(bno);
}
// ✏️ 글 수정
public void modify(BoardDTO dto) {
boardMapper.update(dto);
}
}
🎮 Step 4: Controller 만들기 - 요청 받고 응답 보내기
Controller는 사용자의 요청을 받는 첫 번째 관문이에요!
package org.zerock.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.zerock.dto.BoardDTO;
import org.zerock.service.BoardService;
import java.util.List;
@Controller // 🏷️ "나는 컨트롤러야!" 라고 Spring에게 알림
@Log4j2
@RequestMapping("/board") // 📍 /board로 시작하는 모든 요청 담당
@RequiredArgsConstructor
public class BoardController {
private final BoardService boardService;
// 📋 글 목록 페이지
// GET /board/list → list.jsp
@GetMapping("/list")
public void list(Model model) {
log.info("board list");
List<BoardDTO> boardDtoList = boardService.getList();
model.addAttribute("list", boardDtoList);
}
// ✏️ 글 쓰기 폼 페이지
// GET /board/register → register.jsp
@GetMapping("/register")
public void register() {
log.info("board register");
}
// 📝 글 등록 처리
// POST /board/register → redirect /board/list
@PostMapping("/register")
public String registerPost(BoardDTO dto, RedirectAttributes rttr) {
log.info("board register Post");
Long bno = boardService.register(dto);
rttr.addFlashAttribute("result", bno);
return "redirect:/board/list";
}
// 📖 글 상세 보기
// GET /board/read/1 → read.jsp
@GetMapping("/read/{bno}")
public String read(@PathVariable("bno") Long bno, Model model) {
log.info("board read");
BoardDTO dto = boardService.read(bno);
model.addAttribute("board", dto);
return "/board/read";
}
// 🔧 글 수정 폼 페이지
// GET /board/modify/1 → modify.jsp
@GetMapping("/modify/{bno}")
public String modify(@PathVariable("bno") Long bno, Model model) {
log.info("board modify");
BoardDTO dto = boardService.read(bno);
model.addAttribute("board", dto);
return "/board/modify";
}
// ✏️ 글 수정 처리
// POST /board/modify → redirect /board/read/번호
@PostMapping("/modify")
public String modifyPost(BoardDTO dto, RedirectAttributes rttr) {
log.info("board modify Post");
boardService.modify(dto);
rttr.addFlashAttribute("result", dto.getBno());
return "redirect:/board/read/" + dto.getBno();
}
// 🗑️ 글 삭제 처리
// POST /board/remove → redirect /board/list
@PostMapping("/remove")
public String remove(@RequestParam("bno") Long bno, RedirectAttributes rttr) {
log.info("board remove");
boardService.remove(bno);
rttr.addFlashAttribute("result", bno);
return "redirect:/board/list";
}
}
🔄 전체 흐름 이해하기
사용자가 글 목록을 볼 때 어떤 일이 일어나는지 살펴볼까요?
sequenceDiagram
participant 👤 as 사용자
participant 🎮 as Controller
participant 🔧 as Service
participant 🗺️ as Mapper
participant 💾 as Database
participant 📄 as JSP
👤->>🎮: /board/list 요청
🎮->>🔧: getList() 호출
🔧->>🗺️: list() 호출
🗺️->>💾: SELECT 쿼리 실행
💾-->>🗺️: 결과 데이터
🗺️-->>🔧: List<BoardDTO>
🔧-->>🎮: List<BoardDTO>
🎮->>📄: model에 데이터 담아서 전달
📄-->>👤: HTML 화면 응답
계층별 역할 정리
graph TB
subgraph "🎮 Controller Layer"
A["BoardController<br/>@Controller"]
end
subgraph "🔧 Service Layer"
B["BoardService<br/>@Service"]
end
subgraph "🗺️ Data Access Layer"
C["BoardMapper<br/>Interface"]
D["boardMapper.xml<br/>SQL"]
end
subgraph "📦 Domain"
E["BoardDTO<br/>Data Object"]
end
A -->|"호출"| B
B -->|"호출"| C
C -->|"매핑"| D
E -.->|"사용"| A
E -.->|"사용"| B
E -.->|"사용"| C
style A fill:#4caf50,color:#fff
style B fill:#ff9800,color:#fff
style C fill:#2196f3,color:#fff
style D fill:#9c27b0,color:#fff
style E fill:#607d8b,color:#fff
🎯 URL과 HTTP 메서드 정리
| 기능 | HTTP 메서드 | URL | Controller 메서드 | View |
|---|---|---|---|---|
| 목록 조회 | GET | /board/list | list() | list.jsp |
| 등록 폼 | GET | /board/register | register() | register.jsp |
| 등록 처리 | POST | /board/register | registerPost() | redirect |
| 상세 보기 | GET | /board/read/{bno} | read() | read.jsp |
| 수정 폼 | GET | /board/modify/{bno} | modify() | modify.jsp |
| 수정 처리 | POST | /board/modify | modifyPost() | redirect |
| 삭제 처리 | POST | /board/remove | remove() | redirect |
🏷️ 어노테이션 총정리
mindmap
root((Spring 어노테이션))
Controller
@Controller
@RequestMapping
@GetMapping
@PostMapping
@PathVariable
@RequestParam
Service
@Service
@RequiredArgsConstructor
Mapper
@Mapper 스캔
공통
@Log4j2
@Getter/@Setter
@ToString
| 어노테이션 | 위치 | 의미 |
|---|---|---|
@Controller | 클래스 | 웹 요청을 처리하는 컨트롤러 |
@Service | 클래스 | 비즈니스 로직을 처리하는 서비스 |
@RequestMapping | 클래스/메서드 | URL 경로 매핑 |
@GetMapping | 메서드 | GET 요청 처리 |
@PostMapping | 메서드 | POST 요청 처리 |
@PathVariable | 파라미터 | URL 경로에서 값 추출 |
@RequestParam | 파라미터 | 요청 파라미터 값 추출 |
@RequiredArgsConstructor | 클래스 | final 필드 생성자 자동 생성 |
✅ 2편 정리
오늘 우리가 한 일을 체크해볼까요?
- MVC 패턴 이해하기
- DTO 클래스 만들기 (데이터 담기)
- Mapper 인터페이스와 XML 만들기 (DB 연동)
- Service 클래스 만들기 (로직 처리)
- Controller 클래스 만들기 (요청 처리)
- 전체 흐름 이해하기
🔮 다음 편 예고
3편에서는 실제로 **눈에 보이는 화면(JSP)**을 만들어볼 거예요!
- 📋 글 목록 화면 만들기
- ✏️ 글 작성 폼 만들기
- 📖 글 상세 보기 화면 만들기
- 🔧 글 수정/삭제 기능 구현
Bootstrap을 사용해서 멋진 디자인의 게시판을 완성할 거예요! 🎨
⚠️ 다음 편을 위한 준비물
- 1편에서 세팅한 프로젝트가 정상 작동하는지 확인
- Tomcat 서버가 실행되는지 확인
- MySQL 데이터베이스에 테스트 데이터를 미리 넣어두면 좋아요!
INSERT INTO tbl_board (title, content, writer) VALUES ('첫 번째 글', '안녕하세요!', '홍길동');