Search code examples
javamysqlspringthymeleaf

Thymeleaf + spring save new object instead of update


I am asking for a hint. I searched the internet for a long time for a solution to the problem, but I'm already desperate. I would like the update operation to update a record in the table instead of writing a new one. Sorry for my code i am a beginner.

It is my code:

  @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;
  • getters setters and constructors

service layer:

@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);
    }

controller methods :

@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";
    }

and view layer:

<html xmlns:th="https://www.thymeleaf.org">
<head>
<link rel="stylesheet"
    href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" />
<script
    src="https://cdn.jsdelivr.net/npm/[email protected]/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>

New error :

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]

Solution

  • One way to do this is by first fetching the job that matches your job argument from the repository and then applying the necessary changes to it before saving (updating) it to the database.

    @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 
        }
       
    }
    

    UPDATE: That error is thrown by your method getJobById. You can, of course, change that method so that it returns null whenever a record is not found in the repository (rather than throwing an exception as you're doing now). Note that the job id in this case is 0 and there exists no record in the repository with that id (0). If you're updating a record then you have to provide a unique id that already exists in the database (whether it's id or something else). I'm guessing you haven't set Job.id explicitly to 0 but rather id is initialized to its default value, which is 0.