表单提交至数据库时Title与Content未存储问题排查
问题描述
点击「게시글 작성(帖子发布)」按钮后页面重定向至首页,但数据库中仅id、author、publishedDateTime字段有值,表单填写的title和content为null,无法定位问题。
相关代码
Post 实体类
@Entity @Getter @EqualsAndHashCode(of = "id") @Builder @AllArgsConstructor @NoArgsConstructor public class Post { @Id @GeneratedValue private Long id; @ManyToOne(fetch = FetchType.LAZY) private Account author; private String title; @Lob @Basic(fetch = FetchType.EAGER) private String content; private LocalDateTime publishedDateTime; private LocalDateTime modifiedDateTime; public void addAuthor(Account account) { this.publishedDateTime = LocalDateTime.now(); this.author = account; } }
PostForm 表单类
@Data public class PostForm { @NotBlank @Length(max = 100) private String title; @NotBlank private String content; }
PostController 控制器
@Controller @RequiredArgsConstructor public class PostController { private final PostService postService; private final PostRepository postRepository; private final ModelMapper modelMapper; @GetMapping("/announcement/write") public String newAnnouncementForm(@CurrentAccount Account account, Model model) { model.addAttribute(account); model.addAttribute(new PostForm()); return "post/form"; } @PostMapping("/announcement/write") public String writeNewAnnouncement(@CurrentAccount Account account, @Valid PostForm postForm, Errors errors, Model model) { if (errors.hasErrors()) { model.addAttribute(account); return "post/form"; } Post newPost = postService.createNewPost(modelMapper.map(postForm, Post.class), account); return "redirect:/"; } }
PostService 业务层
@Service @Transactional @RequiredArgsConstructor public class PostService { private final PostRepository postRepository; public Post createNewPost(Post post, Account account) { Post newPost = postRepository.save(post); newPost.addAuthor(account); return newPost; } }
PostRepository 数据访问层
@Transactional(readOnly = true) public interface PostRepository extends JpaRepository<Post, Long> { }
前端表单页面
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head th:replace="fragments.html :: head"></head> <body> <div th:replace="fragments.html :: main-nav"></div> <div class="container"> <div class="py-5 text-center"> <h2>게시글 작성</h2> </div> <div class="row justify-content-center"> <form class="needs-validation col-sm-8" th:action="@{/announcement/write}" th:object="${postForm}" method="post" novalidate> <div class="form-group"> <label for="title">제목</label> <input id="title" type="text" th:field="*{title}" class="form-control" placeholder="제목" aria-describedby="titleHelp" required max="100"> <small class="invalid-feedback">제목을 입력하세요.</small> <small class="form-text text-danger" th:if="${#fields.hasErrors('title')}" th:errors="*{title}">Title Error</small> </div> <div class="form-group"> <label for="content">내용</label> <textarea id="content" type="textarea" th:field="*{content}" class="form-control" placeholder="내용을 작성해주세요." aria-describedby="contentHelp" required></textarea> <small class="invalid-feedback">내용를 입력하세요.</small> <small class="form-text text-danger" th:if="${#fields.hasErrors('content')}" th:errors="*{content}">Content Error</small> </div> <div class="form-group"> <button class="btn btn-primary btn-block" type="submit" aria-describedby="submitHelp">게시글 작성</button> </div> </form> </div> </div> <div th:replace="fragments.html :: footer"></div> <script th:replace="fragments.html :: form-validation"></script> <script src="/node_modules/summernote/dist/summernote-bs4.js"></script> <script type="application/javascript"> $(function () { $('#content').summernote({ fontNames: ['Arial', 'Arial Black', 'Comic Sans MS', 'Courier New', 'Noto Sans KR', 'Merriweather'], placeholder: '내용을 입력해주세요.', tabsize: 2, height: 600 }); }); </script> </body> </html>
问题修复方案
1. 补充Post实体的Setter方法
Post实体仅标注了@Getter,没有提供Setter方法,导致ModelMapper无法将PostForm中的title和content映射到Post对象。
修复代码:
给Post实体添加@Setter注解:
@Entity @Getter @Setter // 添加Setter @EqualsAndHashCode(of = "id") @Builder @AllArgsConstructor @NoArgsConstructor public class Post { // 原有代码不变 }
或者手动为title和content添加Setter方法:
public void setTitle(String title) { this.title = title; } public void setContent(String content) { this.content = content; }
2. 调整Service层的保存顺序
当前Service先保存空属性的Post,再设置作者信息,虽然JPA会自动同步持久化对象的修改,但更合理的方式是先完成所有属性赋值再执行保存操作,避免潜在的持久化上下文问题。
修复代码:
@Service @Transactional @RequiredArgsConstructor public class PostService { private final PostRepository postRepository; public Post createNewPost(Post post, Account account) { post.addAuthor(account); // 先设置作者和发布时间 return postRepository.save(post); // 再执行保存 } }
3. 同步Summernote编辑器内容到textarea
前端使用Summernote时,编辑器会替换原生textarea,默认情况下表单提交时不会自动同步编辑器内容到textarea,导致content字段为空。
修复代码:
在表单提交前添加同步逻辑:
$(function () { $('#content').summernote({ fontNames: ['Arial', 'Arial Black', 'Comic Sans MS', 'Courier New', 'Noto Sans KR', 'Merriweather'], placeholder: '내용을 입력해주세요.', tabsize: 2, height: 600 }); // 表单提交前同步编辑器内容 $('form').submit(function() { $('#content').val($('#content').summernote('code')); }); });
内容的提问来源于stack exchange,提问作者Joshep
相关产品推荐
相关产品推荐

