Thymeleaf + spring 保存新对象而不是更新

Thymeleaf + spring save new object instead of update

我正在寻求提示。我在互联网上搜索了很长时间以解决问题,但我已经绝望了。我希望更新操作更新 table 中的记录,而不是写入新记录。对不起我的代码,我是初学者。

这是我的代码:

  @Entity
public class Job {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @NotEmpty(message = "You must enter the title.")
    @Size(min=2, max = 30, message = "You must add a description with length beetwen 2 and 30 letters.")
    @Column(name = "title")
    private String title;

    @NotEmpty(message = "You must enter the description.")
    @Size(min=10, max = 150, message = "You must add a description with length beetwen 10 and 150 letters.")
    @Column(name = "description")
    private String description;

    @NotEmpty(message = "You must select profession")
    @Column(name = "profession")
    private String profession;

    @NotEmpty(message = "You must select status")
    @Column(name = "status")
    private String status;

    @NotEmpty(message = "You must select location")
    @Column(name = "location")
    private String location;

    @CreationTimestamp
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "publishDate")
    private Date publishDate;

服务层:

@Service
public class JobServiceImpl implements JobService {

    @Autowired
    private JobRepository repository;

    @Override
    public Job saveJob(Job job) {

        return repository.save(job);
    }


    @Override
    public Job getJobById(Long id) throws JobNotFoundException {

        Optional<Job> optional = repository.findById(id);

        if (optional.isPresent()) {
            return optional.get();
        } else {
            throw new JobNotFoundException("Job with id: " + id + " not found ");
        }

    }

    @Override
    public void deleteJobById(Long id) throws JobNotFoundException {
        repository.delete(getJobById(id));

    }

    @Override
    public void updateJob(Job job) {

        repository.save(job);
    }

控制器方法:

@GetMapping("/edit")
    public String getEditPage(Model model, RedirectAttributes attributes, @RequestParam Long id) {
        String page = null;
        initModel(model);
        try {
            Job job = service.getJobById(id);
            model.addAttribute("job", job);
            page = "editJobPage";
        } catch (JobNotFoundException e) {
            e.printStackTrace();
            attributes.addAttribute("message", e.getMessage());
            page = "redirect:getAllJobs";
        }
        return page;
    }

    @PostMapping("/edit")
    public String updateJob(@ModelAttribute Job job, Model model, RedirectAttributes attributes) {
        initModel(model);
        service.updateJob(job);
        Long id = job.getId();
        attributes.addAttribute("message", "Job advertisement with id: '" + id + "' is updated successfully !");
        return "redirect:getAllJobs";
    }

和视图层:

<html xmlns:th="https://www.thymeleaf.org">
<head>
<link rel="stylesheet"
    href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" />
<script
    src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js"></script>
<link rel="stylesheet"
    href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css" />
</head>
<body>
    <div class="col-md-12">
        <div class="container">

            <div class="card">
                <div class="card-header text-center text-black">
                    <h3>Edit advertisement</h3>
                </div>
                <div class="card-body">
                    <form action="/job/edit" th:action="@{/job/edit}" th:object="${job}"
                        method="POST" id="editJobPage">
                        <div class="row">
                            <div class="col-2">
                                <label for="title"><b>Title</b></label>
                                
                            </div>
                            <div class="col-6">
                                <input type="text" class="form-control"
                                    name="title" th:field="*{title}"><br />
                            
                            </div>
                        </div>
                        <br />
                        <div class="row">
                            <div class="col-2">
                                <label for="location"><b>Location</b></label>
                            </div>
                            <select id="location" name="location">
                                <option value="" disabled selected>Select location</option>
                                <option th:each="location : ${locationsList}"
                                    th:text="${location}" />
                            </select> 
                            

                        </div>
                        <br />
                        <div class="row">
                            <div class="col-2">
                                <label for="description"><b>Description</b></label>
                            </div>
                            <div class="col-6">
                                <textarea class="form-control" rows="3" 
                                     name="description"
                                    th:field="*{description}"></textarea>
                                <br />
                    
                            </div>
                        </div>
                        <br />
                        <div class="row">
                            <div class="col-2">
                                <label for="profession"><b>Profession</b></label>
                            </div>
                            <select name="profession">
                                <option value="" disabled selected>Select profession</option>
                                <option th:each="profession : ${professionsList}"
                                    th:text="${profession}" />

                            </select>
                    

                        </div>
                        <br />
                        <div class="row">
                            <div class="col-2">
                                <label for="status"><b>Status</b></label>
                            </div>
                            <select name="status">
                                <option value="" disabled selected>Select status</option>
                                <option th:each="status : ${statusList}" th:text="${status}" />

                            </select>
                    

                        </div>
                        <br />

                        <button type="submit" class="btn btn-success">
                            Update Advertisement <i class="fa fa-plus-square"
                                aria-hidden="true"></i>
                        </button>
                        
                    </form>
                </div>
                <div th:if="${message!=null}" class="card-footer bg-white text-info">
                    <span th:text="${message}"></span>
                </div>
            </div>
        </div>
    </div>
</body>
</html>

新错误:

2022-05-07 15:51:48.027 DEBUG 29284 --- [nio-8080-exec-7] org.hibernate.SQL                        : select job0_.id as id1_0_0_, job0_.description as descript2_0_0_, job0_.location as location3_0_0_, job0_.profession as professi4_0_0_, job0_.publish_date as publish_5_0_0_, job0_.status as status6_0_0_, job0_.title as title7_0_0_ from job job0_ where job0_.id=?
2022-05-07 15:51:48.027 TRACE 29284 --- [nio-8080-exec-7] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [0]
com.example.job.advertisements.web.app.exceptionHandler.JobNotFoundException: Job with id: 0 not found 
    at com.example.job.advertisements.web.app.service.JobServiceImpl.getJobById(JobServiceImpl.java:39)
    at com.example.job.advertisements.web.app.service.JobServiceImpl.updateJob(JobServiceImpl.java:53)
    at com.example.job.advertisements.web.app.controller.JobController.updateJob(JobController.java:132)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:681)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.web.servlet.resource.ResourceUrlEncodingFilter.doFilter(ResourceUrlEncodingFilter.java:67)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:895)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1732)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.base/java.lang.Thread.run(Thread.java:829)
2022-05-07 15:51:48.049 DEBUG 29284 --- [nio-8080-exec-8] org.hibernate.SQL                        : select job0_.id as id1_0_, job0_.description as descript2_0_, job0_.location as location3_0_, job0_.profession as professi4_0_, job0_.publish_date as publish_5_0_, job0_.status as status6_0_, job0_.title as title7_0_ from job job0_
2022-05-07 15:51:48.050 TRACE 29284 --- [nio-8080-exec-8] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_] : [BIGINT]) - [1]

您看到这种情况的原因是因为您的持久性提供程序(大概是休眠)不知道您要更新哪个版本,所以它将它视为一个新对象。

在您的模型中,只需添加

@Version
private Long version;

你那里的东西应该有用。该版本现在将成为对象的一部分,当您的更新请求返回时,如果填充了版本字段,hibernate 将知道它需要更新哪一行。

此外,您还需要将 id 和版本添加到您的视图层(它们可以是隐藏字段),它们在您的视图中将如下所示

<input type="hidden" name="id" th:field="*{id}">
<input type="hidden" name="version" th:field="*{version}">

一种方法是首先从存储库中获取与您的作业参数匹配的作业,然后在将其保存(更新)到数据库之前对其应用必要的更改。

@Override
public void updateJob(Job job) {
    //I assume  job.getId() returns an integer here, otherwise use another field that uniquely identifies a job
    Job dbJob = getJobById(job.getId());
    if(dbJob != null){
      
       dbJob.setStatus(job.getStatus());
       dbJob.setTitle(job.getTitle());
       //set all the other fields except id
       
     repository.save(dbJob);//this updates an existing job record
    }else{
      repository.save(job);//this inserts a new job 
    }
   
}

更新:该错误是由您的方法 getJobById 引发的。当然,您可以更改该方法,以便每当在存储库中找不到记录时它 returns null(而不是像您现在所做的那样抛出异常)。请注意,本例中的作业 ID 为 0,并且存储库中不存在具有该 ID (0) 的记录。如果要更新记录,则必须提供数据库中已存在的唯一 ID(无论是 ID 还是其他)。我猜你没有将 Job.id 显式设置为 0,而是将 id 初始化为其默认值,即 0.