Reputation: 361
In my Spring boot batch application, I am calling a JPA repository class from Tasklet. The JPA call retrieves a particular value (Entity object) from DB. The problem is, If I update some value in the entity object, once the control goes out of Tasklet, it automatically updates to DB even though I am not calling any save operation. How to prevent this? Default JPA implementation is Hibernate.
Tasklet class
Employee employee = employeeRepository.fetchEmployee(employeeName);
List<Address> addressList = employee.getAddress();
addressList.forEach(e -> e.setStatus(Status.INVALID.toString()));
Repository
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
@Query("select em from Employee em where em.employeeName = :employeeName")
public Employee fetchEmployee(@Param("employeeName") Long employeeName);
}
Entity class
@Entity
@Table(name = "Employee")
public class Employee implements java.io.Serializable {
private static final long serialVersionUID = -3769636546619492649L;
private Long id;
private List<Address> address;
private String employeeName;
// Getters and setters
// @OneToMany mapping to Address
}
Even though I am not calling a .save() operation, it automatically updates Address table Status to "INVALID"
Upvotes: 0
Views: 3051
Reputation: 1046
This happen because the entity is not in detached state. In EJB we can do this in the following way.
EJB solution
@Query(value = "select * from Employee WHERE EmployeeName = ?1", nativeQuery = true)
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public List<Employee> fetchEmployee(String employeeName);
This will make the transaction closed. Changes you make to entity will not get saved in DB
Spring JPA
After a bit of research i found JPA doesn't provide the detach functionality out of the box.
Refer : https://github.com/spring-projects/spring-data-jpa/issues/641
To make it work we can have a custom JPA repository which overrides detach method. An example is given in this link.
https://www.javaer101.com/en/article/1428895.html
Upvotes: 3
Reputation: 1491
Use Deep cloning to solve your issue.
First override the clone method inside your Address class like below.
Note : Please customize the implementation of clone()
method by adding your class attributes.Since you didn't mention the structure of the class Address , I have implemented the solution with my own defined class attributes.
Address class
public class Address {
private String country;
private String city;
private String district;
private String addressValue;
public Address() {
super();
}
public Address(String country, String city, String district, String addressValue) {
super();
this.country = country;
this.city = city;
this.district = district;
this.addressValue = addressValue;
}
//Getters and Setters
@Override
protected Object clone() {
try {
return (Address) super.clone();
} catch (CloneNotSupportedException e) {
return new Address(this.getCountry(), this.getCity(), this.getDistrict(),this.getAddressValue());
}
}
}
Then re construct your class Tasket like below.
Tasket Class
Employee employee = employeeRepository.fetchEmployee(employeeName);
List<Address> addressList = employee.getAddress();
List<Address> clonedAddressList = new ArrayList<>();
addressList.forEach(address -> clonedAddressList.add((Address)address.clone()) );
clonedAddressList.forEach(address -> address.setStatus(Status.INVALID.toString()));
Upvotes: 1