Issue
I'm learning hibernate and Spring Boot (also Thymeleaf) and encountered the following problem:
Circumstances: I have a form that handles both creating and updating data based on ID parameter in GET method, if ID is equal to a row in database then update, otherwise save. ID is stored inside a hidden input tag and inserted into an Entity object along with form data. This object is then sent to a service which calls a DAO to save or update data using saveOrUpdate() method.
Summary of the problem: In my service I have a method annotated with @Transactional that calls my DAO, which in turn opens a session and executes saveOrUpdate(). The problem is that this code correctly saves new data to my database but it doesn't update existing data, in logs saveOrUpdate() only selects but doesn't insert.
What I tried: I found out that if I open a new transaction inside the DAO and then commit the transaction everything works correctly but when I don't do so and just rely on @Transactional annotation above the service method I can only save new data but nothing is updated.
My HTML form with Thymeleaf
<form action="#" th:action="@{saveCustomer}" th:object="${newCustomer}" method="post">
<input type="hidden" th:field="*{id}" />
<table>
<tr>
<td><label>First Name:</label></td>
<td><input type="text" th:field="*{firstName}"/></td>
</tr>
<tr>
<td><label>Last Name:</label></td>
<td><input type="text" th:field="*{lastName}"/></td>
</tr>
<tr>
<td><label>Email:</label></td>
<td><input type="text" th:field="*{email}"/></td>
</tr>
<tr>
<td><label></label></td>
<td><input type="submit" value="Submit" class="save"/></td>
</tr>
<tr>
<td><label></label></td>
<td><input type="reset" value="Reset" class="save"/></td>
</tr>
</table>
</form>
Controller method
@PostMapping("/saveCustomer")
public String saveCustomer(@ModelAttribute("newCustomer") Customer newCustomer) {
customerService.saveCustomer(newCustomer);
return "redirect:/list";
}
Service method
import javax.transaction.Transactional;
@Override
@Transactional
public void saveCustomer(Customer newCustomer) {
customerDAO.saveCustomer(newCustomer);
}
DAO method
@Autowired
private EntityManagerFactory entityManagerFactory;
@Override
public void saveCustomer(Customer newCustomer) {
try (Session session = entityManagerFactory.unwrap(SessionFactory.class).openSession()) {
// If I use transaction like this everything works correctly!
// Transaction transaction = session.beginTransaction();
session.saveOrUpdate(newCustomer);
// transaction.commit();
} catch (Exception e) {
e.printStackTrace();
}
}
My Customer Entity class
import javax.persistence.*;
@Entity
@Table(name = "customer")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private int id;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
@Column(name = "email")
private String email;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "Customer{" +
"id=" + id +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
'}';
}
}
Hibernate logs when this occurs
Hibernate: select customer0_.id as id1_0_, customer0_.email as email2_0_, customer0_.first_name as first_na3_0_, customer0_.last_name as last_nam4_0_ from customer customer0_ order by customer0_.last_name
Hibernate: select customer0_.id as id1_0_0_, customer0_.email as email2_0_0_, customer0_.first_name as first_na3_0_0_, customer0_.last_name as last_nam4_0_0_ from customer customer0_ where customer0_.id=?
Hibernate: select customer0_.id as id1_0_, customer0_.email as email2_0_, customer0_.first_name as first_na3_0_, customer0_.last_name as last_nam4_0_ from customer customer0_ order by customer0_.last_name
I omitted most imports to declutter code as I only included parts of my objects that are relevant to the question.
Solution
You are importing wrong @Transactional
annotation. It should be from package org.springframework.transaction.annotation
. Here is the API Doc of this annotation - https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/Transactional.html
Also you can use Spring Data JPA, and ease the database operations with JPA repositories, as they have many easy to use, and handy methods. With Spring Data JPA, you just need to create repository interface (as below), call its method/s to save the data:
@Repository
public interface CustomerRepository extends JpaRepository<Customer, Integer> {}
And in service, autowire this repository, and call its save method:
@Autowired
private CustomerRepository customerRepository;
@org.springframework.transaction.annotation.Transactional
public Customer saveOrUpdate(Customer customer) {
return customerRepository.save(customer); // this will save or update (if id is there)
}
Answered By - Jignesh M. Khatri