스프링 부트 CKEditor
스프링 부트 CKEditor 15 - 게시글 수정 로직 코드
https://youtu.be/M5TTsIsPhE4?si=18IPzVfOHv5h7PiC
스프링 부트 CKEditor 15 - 게시글 수정 로직 코드
- 스프링 부트 CKEditor 15 - 게시글 수정 로직 코드
- Spring Boot + CKEditor 5 : 게시글 수정(Update) 기능 구현하기
- 1. 게시글 수정 전체 흐름
- 2. 수정 페이지 URL 설계
- 3. 수정 링크 추가하기
- 4. 수정 페이지 Controller 작성
- 5. 핵심 포인트
- 6. editor.html 수정하기
- 7. CKEditor와 textarea 관계 이해
- 8. 수정 저장 URL 구성
- 9. SaveController 수정
- 10. 수정 Service 작성
- 11. save()가 UPDATE 되는 이유
- 12. 실제 테스트
- 13. 현재 코드의 문제점 (실무 관점)
- 14. 문제점 1 : set 메소드 남용
- 15. 문제점 2 : 기존 데이터 유실 위험
- 16. 문제점 3 : XSS 보안
- 17. 지금 단계에서 중요한 것
- 18. 정리
Spring Boot + CKEditor 5 : 게시글 수정(Update) 기능 구현하기
이번 글에서는 CKEditor로 작성한 게시글을 다시 불러와 수정하고, 수정된 내용을 DB에 저장하는 기능을 구현한다.
CRUD 기준으로 보면:
- Create → 생성 ✅
- Read → 조회 ✅
- Update → 수정 ✅
- Delete → 삭제 ✅
이번 단계까지 완료하면 기본 게시판 CRUD 기능이 모두 완성된다.
핵심은 한 줄이다.
기존 게시글을 DB에서 조회하여 에디터에 출력한 뒤, 수정된 내용을 다시 save()로 덮어쓴다
1. 게시글 수정 전체 흐름
수정 기능은 다음 순서로 동작한다.
게시글 목록
↓
수정 버튼 클릭
↓
/edit/{id}
↓
DB에서 기존 게시글 조회
↓
CKEditor에 기존 데이터 출력
↓
사용자 수정
↓
/save/{id}
↓
DB 업데이트
2. 수정 페이지 URL 설계
수정할 게시글을 구분하기 위해 게시글 ID를 URL에 포함한다.
/edit/1
/edit/2
/edit/3
즉:
/edit/1
의 의미는:
“1번 게시글 수정 화면 열기”
3. 수정 링크 추가하기
먼저 게시글 상세 페이지에 수정 버튼을 만든다.
content.html
<a th:href="@{/edit/{id}(id=${content.id})}">
게시글 수정
</a>
실제 렌더링 결과:
<a href="/edit/1">
게시글 수정
</a>
4. 수정 페이지 Controller 작성
이제 /edit/{id} 요청을 처리한다.
@Controller
public class EditorController {
private final ContentsService contentsService;
public EditorController(ContentsService contentsService) {
this.contentsService = contentsService;
}
@GetMapping("/edit/{id}")
public String edit(@PathVariable Long id, Model model) {
ContentEntity content = contentsService.selectOneContent(id);
model.addAttribute("content", content);
return "editor";
}
}
5. 핵심 포인트
1) @PathVariable
@PathVariable Long id
URL의 ID 값을 가져온다.
예:
/edit/5
👉 id = 5
2) DB 조회
contentsService.selectOneContent(id);
기존에 만들었던 상세 조회 메소드를 그대로 재사용한다.
👉 코드 재사용성 증가
3) Model 전달
model.addAttribute("content", content);
조회한 게시글 데이터를 View로 전달한다.
6. editor.html 수정하기
이제 기존 데이터를 에디터에 출력해야 한다.
제목 출력
<input type="text"
name="title"
th:value="${content.title}">
내용 출력
<textarea id="content"
name="content">
[[${content.content}]]
</textarea>
7. CKEditor와 textarea 관계 이해
CKEditor는 독립 에디터가 아니다.
실제로는:
<textarea>
위에 플러그인 형태로 올라간다.
즉:
textarea ← 실제 데이터 저장소
CKEditor ← UI 편집기
8. 수정 저장 URL 구성
수정 저장 시에도 게시글 ID가 필요하다.
<form th:action="@{/save/{id}(id=${content.id})}"
method="post">
실제 요청:
/save/1
9. SaveController 수정
이제 수정 데이터를 받아야 한다.
@PostMapping("/save/{id}")
public String update(@PathVariable Long id,
SaveDTO saveDTO) {
contentsService.updateOneContent(saveDTO, id);
return "redirect:/contents/" + id;
}
10. 수정 Service 작성
이제 DB 업데이트 로직을 구현한다.
@Service
public class ContentsService {
private final ContentRepository contentRepository;
public ContentsService(ContentRepository contentRepository) {
this.contentRepository = contentRepository;
}
public void updateOneContent(SaveDTO saveDTO,
Long id) {
ContentEntity contentEntity = new ContentEntity();
contentEntity.setId(id);
contentEntity.setTitle(saveDTO.getTitle());
contentEntity.setContent(saveDTO.getContent());
contentRepository.save(contentEntity);
}
}
11. save()가 UPDATE 되는 이유
여기서 가장 중요한 핵심이다.
save() 메소드 동작 원리
contentRepository.save(entity);
JPA는 PK(Primary Key)를 기준으로 동작한다.
CASE 1. PK 없음
id = null
👉 INSERT
INSERT INTO ...
CASE 2. PK 존재
id = 1
👉 UPDATE
UPDATE ...
WHERE id = 1
즉:
contentEntity.setId(id);
이 부분 때문에 UPDATE가 된다.
12. 실제 테스트
1) 기존 게시글
제목: 안녕하세요
내용: 반갑습니다
2) 수정 버튼 클릭
/edit/1
3) 에디터에 기존 데이터 출력
제목: 안녕하세요
내용: 반갑습니다
4) 수정 후 저장
제목: 수정된 제목
내용: 수정된 내용
5) DB 결과
수정 완료
기존 데이터가 덮어쓰기 된다.
13. 현재 코드의 문제점 (실무 관점)
현재 구조는 학습용으로는 충분하지만 실무에서는 개선이 필요하다.
14. 문제점 1 : set 메소드 남용
현재 코드:
contentEntity.setTitle(...)
실무에서는 setter 남용을 지양한다.
이유
객체 상태가 언제든 변경 가능해진다.
실무 권장 방식
new ContentEntity(id, title, content)
생성자 기반 immutable 구조 권장
15. 문제점 2 : 기존 데이터 유실 위험
현재 방식은:
새 Entity 생성
후 save()
즉 기존 컬럼이 많아지면:
NULL 덮어쓰기 위험
존재
실무 권장 방식
기존 Entity 조회
↓
필드 수정
↓
save()
예시:
ContentEntity entity =
contentRepository.findById(id)
.orElseThrow();
entity.update(title, content);
16. 문제점 3 : XSS 보안
CKEditor는 HTML 저장 구조다.
즉 사용자가:
<script>alert('hack')</script>
입력 가능
실무에서는 반드시:
- HTML Sanitizing
- 허용 태그 제한
- XSS 필터링
필요
17. 지금 단계에서 중요한 것
이번 단계는 단순 수정이 아니다.
핵심 이해 포인트
- URL 기반 리소스 수정
- 기존 데이터 조회
- View 데이터 바인딩
- JPA save()의 UPDATE 동작 원리
- PK 기반 업데이트
18. 정리
이번 글의 핵심은 한 줄이다.
JPA save()는 PK 존재 여부에 따라 INSERT 또는 UPDATE를 자동 수행한다
핵심 요약
/edit/{id}로 기존 게시글 조회- Controller → Service → Repository 흐름
- Model로 View 데이터 전달
- CKEditor에 기존 데이터 출력
/save/{id}로 수정 요청- save() 메소드로 DB 업데이트