스프링 부트 CKEditor

스프링 부트 CKEditor 15 - 게시글 수정 로직 코드

https://youtu.be/M5TTsIsPhE4?si=18IPzVfOHv5h7PiC

스프링 부트 CKEditor 15 - 게시글 수정 로직 코드


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 업데이트


© 2020. All rights reserved.

SIKSIK