How do I update an entity using spring-data-jpa? - java

Well the question pretty much says everything. Using JPARepository how do I update an entity?
JPARepository has only a save method, which does not tell me if it's create or update actually. For example, I insert a simple Object to the database User, which has three fields: firstname, lastname and age:
#Entity
public class User {
private String firstname;
private String lastname;
//Setters and getters for age omitted, but they are the same as with firstname and lastname.
private int age;
#Column
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
#Column
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
private long userId;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
public long getUserId(){
return this.userId;
}
public void setUserId(long userId){
this.userId = userId;
}
}
Then I simply call save(), which at this point is actually an insert into database:
User user1 = new User();
user1.setFirstname("john"); user1.setLastname("dew");
user1.setAge(16);
userService.saveUser(user1);// This call is actually using the JPARepository: userRepository.save(user);
So far so good. Now I want to update this user, say change his age. For this purpose I could use a Query, either QueryDSL or NamedQuery, whatever. But, considering I just want to use spring-data-jpa and the JPARepository, how do I tell it that instead of an insert I want to do an update?
Specifically, how do I tell spring-data-jpa that users with the same username and firstname are actually EQUAL and that the existing entity supposed to be updated? Overriding equals did not solve this problem.

Identity of entities is defined by their primary keys. Since firstname and lastname are not parts of the primary key, you cannot tell JPA to treat Users with the same firstnames and lastnames as equal if they have different userIds.
So, if you want to update a User identified by its firstname and lastname, you need to find that User by a query, and then change appropriate fields of the object your found. These changes will be flushed to the database automatically at the end of transaction, so that you don't need to do anything to save these changes explicitly.
EDIT:
Perhaps I should elaborate on overall semantics of JPA. There are two main approaches to design of persistence APIs:
insert/update approach. When you need to modify the database you should call methods of persistence API explicitly: you call insert to insert an object, or update to save new state of the object to the database.
Unit of Work approach. In this case you have a set of objects managed by persistence library. All changes you make to these objects will be flushed to the database automatically at the end of Unit of Work (i.e. at the end of the current transaction in typical case). When you need to insert new record to the database, you make the corresponding object managed. Managed objects are identified by their primary keys, so that if you make an object with predefined primary key managed, it will be associated with the database record of the same id, and state of this object will be propagated to that record automatically.
JPA follows the latter approach. save() in Spring Data JPA is backed by merge() in plain JPA, therefore it makes your entity managed as described above. It means that calling save() on an object with predefined id will update the corresponding database record rather than insert a new one, and also explains why save() is not called create().

Since the answer by #axtavt focuses on JPA not spring-data-jpa
To update an entity by querying then saving is not efficient because it requires two queries and possibly the query can be quite expensive since it may join other tables and load any collections that have fetchType=FetchType.EAGER
Spring-data-jpa supports update operation.
You have to define the method in Repository interface.and annotated it with #Query and #Modifying.
#Modifying
#Query("update User u set u.firstname = ?1, u.lastname = ?2 where u.id = ?3")
void setUserInfoById(String firstname, String lastname, Integer userId);
#Query is for defining custom query and #Modifying is for telling spring-data-jpa that this query is an update operation and it requires executeUpdate() not executeQuery().
You can specify the return type as int, having the number of records being updated.
Note: Run this code in a Transaction.

You can simply use this function with save() JPAfunction, but the object sent as parameter must contain an existing id in the database otherwise it will not work, because save() when we send an object without id, it adds directly a row in database, but if we send an object with an existing id, it changes the columns already found in the database.
public void updateUser(Userinfos u) {
User userFromDb = userRepository.findById(u.getid());
// crush the variables of the object found
userFromDb.setFirstname("john");
userFromDb.setLastname("dew");
userFromDb.setAge(16);
userRepository.save(userFromDb);
}

As what has already mentioned by others, the save() itself contains both create and update operation.
I just want to add supplement about what behind the save() method.
Firstly, let's see the extend/implement hierarchy of the CrudRepository<T,ID>,
Ok, let's check the save() implementation at SimpleJpaRepository<T, ID>,
#Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
As you can see, it will check whether the ID is existed or not firstly, if the entity is already there, only update will happen by merge(entity) method and if else, a new record is inserted by persist(entity) method.

spring data save() method will help you to perform both: adding new item and updating an existed item.
Just call the save() and enjoy the life :))

Using spring-data-jpa save(), I was having same problem as #DtechNet. I mean every save() was creating new object instead of update. To solve this I had to add version field to entity and related table.

This is how I solved the problem:
User inbound = ...
User existing = userRepository.findByFirstname(inbound.getFirstname());
if(existing != null) inbound.setId(existing.getId());
userRepository.save(inbound);

With java 8 you can use repository's findById in UserService
#Service
public class UserServiceImpl {
private final UserRepository repository;
public UserServiceImpl(UserRepository repository) {
this.repository = repository;
}
#Transactional
public void update(User user) {
repository
.findById(user.getId()) // returns Optional<User>
.ifPresent(user1 -> {
user1.setFirstname(user.getFirstname);
user1.setLastname(user.getLastname);
repository.save(user1);
});
}
}

public void updateLaserDataByHumanId(String replacement, String humanId) {
List<LaserData> laserDataByHumanId = laserDataRepository.findByHumanId(humanId);
laserDataByHumanId.stream()
.map(en -> en.setHumanId(replacement))
.collect(Collectors.toList())
.forEach(en -> laserDataRepository.save(en));
}

Specifically how do I tell spring-data-jpa that users that have the
same username and firstname are actually EQUAL and that it is supposed
to update the entity. Overriding equals did not work.
For this particular purpose one can introduce a composite key like this:
CREATE TABLE IF NOT EXISTS `test`.`user` (
`username` VARCHAR(45) NOT NULL,
`firstname` VARCHAR(45) NOT NULL,
`description` VARCHAR(45) NOT NULL,
PRIMARY KEY (`username`, `firstname`))
Mapping:
#Embeddable
public class UserKey implements Serializable {
protected String username;
protected String firstname;
public UserKey() {}
public UserKey(String username, String firstname) {
this.username = username;
this.firstname = firstname;
}
// equals, hashCode
}
Here is how to use it:
#Entity
public class UserEntity implements Serializable {
#EmbeddedId
private UserKey primaryKey;
private String description;
//...
}
JpaRepository would look like this:
public interface UserEntityRepository extends JpaRepository<UserEntity, UserKey>
Then, you could use the following idiom: accept DTO with user info, extract name and firstname and create UserKey, then create a UserEntity with this composite key and then invoke Spring Data save() which should sort everything out for you.

As mentioned by others answer, method save() is dual function. It can both do save or update, it's automatically update if you provide the id.
for update method in controller class I suggested to use #PatchMapping. below is the example.
#Save method POST
{
"username": "jhon.doe",
"displayName": "Jhon",
"password": "xxxyyyzzz",
"email": "jhon.doe#mail.com"
}
#PostMapping("/user")
public void setUser(#RequestBody User user) {
userService.save(user);
}
#Update method PATCH
{
"id": 1, // this is important. Widly important
"username": "jhon.doe",
"displayName": "Jhon",
"password": "xxxyyyzzz",
"email": "jhon.doe#mail.com"
}
#PatchMapping("/user")
public void patchUser(#RequestBody User user) {
userService.save(user);
}
Maybe you're wondering where the id's come from. It comes from the database of course, you want to update the existing data right?

If your primary key is autoincrement then, you have to set the value for the primary key.
for the save(); method to work as a update().else it will create a new record in db.
if you are using jsp form then use hidden filed to set primary key.
Jsp:
<form:input type="hidden" path="id" value="${user.id}"/>
Java:
#PostMapping("/update")
public String updateUser(#ModelAttribute User user) {
repo.save(user);
return "redirect:userlist";
}
also look at this:
#Override
#Transactional
public Customer save(Customer customer) {
// Is new?
if (customer.getId() == null) {
em.persist(customer);
return customer;
} else {
return em.merge(customer);
}
}

Use #DynamicUpdate annotation. it is cleaner and you don't have to deal with querying the database in order to get the saved values.

You can see the example below:
private void updateDeliveryStatusOfEvent(Integer eventId, int deliveryStatus) {
try {
LOGGER.info("NOTIFICATION_EVENT updating with event id:{}", eventId);
Optional<Event> eventOptional = eventRepository.findById(eventId);
if (!eventOptional.isPresent()) {
LOGGER.info("Didn't find any updatable notification event with this eventId:{}", eventId);
}
Event event = eventOptional.get();
event.setDeliveryStatus(deliveryStatus);
event = eventRepository.save(event);
if (!Objects.isNull(event)) {
LOGGER.info("NOTIFICATION_EVENT Successfully Updated with this id:{}", eventId);
}
} catch (Exception e) {
LOGGER.error("Error :{} while updating NOTIFICATION_EVENT of event Id:{}", e, eventId);
}
}
Or Update Using Native Query:
public interface YourRepositoryName extends JpaRepository<Event,Integer>{
#Transactional
#Modifying
#Query(value="update Event u set u.deliveryStatus = :deliveryStatus where u.eventId = :eventId", nativeQuery = true)
void setUserInfoById(#Param("deliveryStatus")String deliveryStatus, #Param("eventId")Integer eventId);
}

I did this for my Entity UserModel:
In the Controller:
#PutMapping("/{id}")
public Optional<UserModel> update(#RequestBody UserModel user, #PathVariable Long id) {
return this.userService.update(user, id);
}
And in the Service:
public Optional<UserModel> update(UserModel req, Long id){
Optional<UserModel> user = userRepository.findById(id);
if (user != null) {
userRepository.save(req);
}
return user;
}
Example with postman:
Postman method PUT example

Related

JPA-Repository save: Proceed saving after insert Constraint violation

i'm using JPA repository to save simple data objects to the database. To avoid duplicates i created a unique constraint on multiple fields. If now a duplicate according to the unique fields/constraint should be saved i want to catch the exception, log the object and the application should proceed and saves the next object. But here i always get this exception: "org.hibernate.AssertionFailure: null id in de.test.PeopleDBO entry (don't flush the Session after an exception occurs)".
In general i understand what hibernate is doing, but how i can revert the session or start a new session to proceed with saving of the next data objects. Please have a look to the code below:
PeopleDBO.java
#Entity
#Data
#Table(
name = "PEOPLE",
uniqueConstraints = {#UniqueConstraint(columnNames = {"firstname", "lastname"}})
public class PeopleDBO {
public PeopleDBO(String firstname, String lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstname;
private String lastname;
}
The Test:
public void should_save_people_and_ignore_constraint_violation(){
final List<PeopleDBO> peopleList = Arrays.asList(
new PeopleDBO("Georg","Smith"),
new PeopleDBO("Georg","Smith"),
new PeopleDBO("Paul","Smith")
);
peopleList.forEach(p -> {
try {
peopleRepository.save(p);
} catch (DataIntegrityViolationException e) {
log.error("Could not save due to constraint violation: {}",p);
}
}
Assertions.assertThat(peopleRepository.count()).isEqualTo(2);
}
The problem is, that with saving of the second people the unique constraint gets violated. The error log happens, and with the next call of peopleRepository.save() the mentioned exception above is thrown:
"org.hibernate.AssertionFailure: null id in de.test.PeopleDBO entry (don't flush the Session after an exception occurs)"
How i can avoid this behaviour? How i can clean the session or start a new session?
Thanks a lot in advance
d.
--------- Edit / new idea ------
I just tried some things and have seen that i could implement a PeopleRepositoryImpl, like this:
#Service
public class PeopleRepositoryImpl {
final private PeopleRepository peopleRepository;
public PeopleRepositoryImpl(PeopleRepository peopleRepository) {
this.peopleRepository = peopleRepository;
}
#Transactional
public PeopleDBO save(PeopleDBO people){
return peopleRepository.save(people);
}
}
This is working pretty fine in my tests. ... what do you think?
One single transaction
The reason is that all inserts occur in one transaction. As this transaction is atomic, it either succeeds entirely or fails, there is nothing in-between.
The most clean solution is to check if a People exists before trying to insert it:
public interface PeopleRespository {
boolean existsByLastnameAndFirstname(String lastname, String firstname);
}
and then:
if (!peopleRepository.existsByLastnameAndFirstname(p.getLastname, p.getFirstname)) {
peopleRepository.save(p);
}
One transaction per people
An alternative is indeed to start a new transaction for each person. But I am not sure it will be more efficient, because there is an extra cost to create transaction.

Spring JPA selecting from where clause

I am using Spring JPA to perform all database operations. However I don't know how to select specific rows (connected by simple WHERE clause) from a table in Spring JPA?
For example:
SELECT * FROM user where name=agrawalo AND email=abc#example.com
User Class:
#Entity
Class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private String email;
// Getters and Setters
}
Repository:
public interface UserRepository extends JpaRepository<User, Integer> {
}
You don't need to write queries for such simple things if you are using spring-data-jpa. you can write a method name and spring-data will formulate a query based on your method name and get the results.
public interface UserRepository extends JpaRepository<User, Integer> {
Optional<User> findByNameAndEmail(String name, String email)
}
Create a method like above and call the method with the required arguments.
If you don't want(not advisable) to use Optional, you can just use User as return type. In such case, if there are no entries matching your arguments then you would have null returned.
public interface UserRepository extends JpaRepository<User, Integer> {
public User findUserByNameAndEmail(String name,String email);
}
Implementation will be created on the fly.
I know I am very very late to this but I still want to provide another solution that one would like to use. This is particularly useful when you think the queries generated by method names do not serve the purpose that you want and you really want to write the native queries. To do that, you can actually use the #Query annotation in your repository and put your query inside it like below:
#Query(value = "SELECT * FROM user where name = ?1 AND email = ?2", nativeQuery = true)
List<User> getUserByNameAndEmail(String name, String email);
Here the #Query tells that the query provided in the value attribute needs to be executed and the nativeQuery attribute tells that it is a native sql query.
Notice that values for name and email are provided as ?1 and ?2 respectively which means these are placeholders and will be replaced by the parameters that getUserByNameAndEmail repository method will receive at runtime in variables name and email.
Simply you can declare below method in you repository interface, implementation will be taken care by Spring-data-jpa
User findByNameAndEmail(String name, String email);

How to beautifully update a JPA entity in Spring Data?

So I have looked at various tutorials about JPA with Spring Data and this has been done different on many occasions and I am no quite sure what the correct approach is.
Assume there is the follwing entity:
package stackoverflowTest.dao;
import javax.persistence.*;
#Entity
#Table(name = "customers")
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long id;
#Column(name = "name")
private String name;
public Customer(String name) {
this.name = name;
}
public Customer() {
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
We also have a DTO which is retrieved in the service layer and then handed to the controller/client side.
package stackoverflowTest.dto;
public class CustomerDto {
private long id;
private String name;
public CustomerDto(long id, String name) {
this.id = id;
this.name = name;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
So now assume the Customer wants to change his name in the webui - then there will be some controller action, where there will be the updated DTO with the old ID and the new name.
Now I have to save this updated DTO to the database.
Unluckily currently there is no way to update an existing customer (except than deleting the entry in the DB and creating a new Cusomter with a new auto-generated id)
However as this is not feasible (especially considering such an entity could have hundreds of relations potentially) - so there come 2 straight forward solutions to my mind:
make a setter for the id in the Customer class - and thus allow setting of the id and then save the Customer object via the corresponding repository.
or
add the id field to the constructor and whenever you want to update a customer you always create a new object with the old id, but the new values for the other fields (in this case only the name)
So my question is wether there is a general rule how to do this?
Any maybe what the drawbacks of the 2 methods I explained are?
Even better then #Tanjim Rahman answer you can using Spring Data JPA use the method T getOne(ID id)
Customer customerToUpdate = customerRepository.getOne(id);
customerToUpdate.setName(customerDto.getName);
customerRepository.save(customerToUpdate);
Is's better because getOne(ID id) gets you only a reference (proxy) object and does not fetch it from the DB. On this reference you can set what you want and on save() it will do just an SQL UPDATE statement like you expect it. In comparsion when you call find() like in #Tanjim Rahmans answer spring data JPA will do an SQL SELECT to physically fetch the entity from the DB, which you dont need, when you are just updating.
In Spring Data you simply define an update query if you have the ID
#Repository
public interface CustomerRepository extends JpaRepository<Customer , Long> {
#Query("update Customer c set c.name = :name WHERE c.id = :customerId")
void setCustomerName(#Param("customerId") Long id, #Param("name") String name);
}
Some solutions claim to use Spring data and do JPA oldschool (even in a manner with lost updates) instead.
Simple JPA update..
Customer customer = em.find(id, Customer.class); //Consider em as JPA EntityManager
customer.setName(customerDto.getName);
em.merge(customer);
This is more an object initialzation question more than a jpa question, both methods work and you can have both of them at the same time , usually if the data member value is ready before the instantiation you use the constructor parameters, if this value could be updated after the instantiation you should have a setter.
If you need to work with DTOs rather than entities directly then you should retrieve the existing Customer instance and map the updated fields from the DTO to that.
Customer entity = //load from DB
//map fields from DTO to entity
So now assume the Customer wants to change his name in the webui -
then there will be some controller action, where there will be the
updated DTO with the old ID and the new name.
Normally, you have the following workflow:
User requests his data from server and obtains them in UI;
User corrects his data and sends it back to server with already present ID;
On server you obtain DTO with updated data by user, find it in DB by ID (otherwise throw exception) and transform DTO -> Entity with all given data, foreign keys, etc...
Then you just merge it, or if using Spring Data invoke save(), which in turn will merge it (see this thread);
P.S. This operation will inevitably issue 2 queries: select and update. Again, 2 queries, even if you wanna update a single field. However, if you utilize Hibernate's proprietary #DynamicUpdate annotation on top of entity class, it will help you not to include into update statement all the fields, but only those that actually changed.
P.S. If you do not wanna pay for first select statement and prefer to use Spring Data's #Modifying query, be prepared to lose L2C cache region related to modifiable entity; even worse situation with native update queries (see this thread) and also of course be prepared to write those queries manually, test them and support them in the future.
I have encountered this issue!
Luckily, I determine 2 ways and understand some things but the rest is not clear.
Hope someone discuss or support if you know.
Use RepositoryExtendJPA.save(entity). Example:
List<Person> person = this.PersonRepository.findById(0)
person.setName("Neo");
This.PersonReository.save(person);
this block code updated new name for record which has id = 0;
Use #Transactional from javax or spring framework. Let put #Transactional upon your class or specified function, both are ok. I read at somewhere that this annotation do a "commit" action at the end your function flow. So, every things you modified at entity would be updated to database.
There is a method in JpaRepository
getOne
It is deprecated at the moment in favor of
getById
So correct approach would be
Customer customerToUpdate = customerRepository.getById(id);
customerToUpdate.setName(customerDto.getName);
customerRepository.save(customerToUpdate);

How to implement soft (logical) delete with MongoDB and Spring?

I have Spring web app with MongoDB. Currently I always permanently delete data from database.
#Repository
public class SessionRepository extends CrudRepository implements SessionService {
...
#Override
public void insert(Session session) {
saveRoom(session);
getTemplate().insert(session);
}
#Override
public void delete(Session session) {
getTemplate().remove(session);
}
...
}
What would be a good way to change this into soft delete?
----------------- edit 1 -------------------
I understand the logic now what I should do, thanks Sarath Nair. But I am unsure how to implement this in Spring. I have a Session object:
#Document(collection = "session")
public class Session {
#Id
private String id;
private Date startDate;
private Date endDate;
//I just put this here
private boolean deleted = false;
public boolean isDeleted() {
return deleted;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
...
}
I want the field boolean isDeleted to be present in the database but I don't wan't to send that piece of information out with a web service.
#Transient is no good because then the field won't show up in database nor in the HTTP response. Right now I am sending deleted: false in my HTTP response.
How should I edit my Session class?
Have an additional field called is_deleted in collection. Insert is_deleted as false for new documents. When you are deleting just update this value to true for that document. Whenever you need to read documents from collection, pass is_deleted : false for the collection.
Solution with "isDeleted" field will not work because #DbRef still retrieves the "isDeleted" records, I am playing around this problem too.
For your second question, you can use custom SpringHttpMessageConverters with GSON to hide "isDeleted" field.

How to get old entity value in #HandleBeforeSave event to determine if a property is changed or not?

I'm trying to get the old entity in a #HandleBeforeSave event.
#Component
#RepositoryEventHandler(Customer.class)
public class CustomerEventHandler {
private CustomerRepository customerRepository;
#Autowired
public CustomerEventHandler(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
#HandleBeforeSave
public void handleBeforeSave(Customer customer) {
System.out.println("handleBeforeSave :: customer.id = " + customer.getId());
System.out.println("handleBeforeSave :: new customer.name = " + customer.getName());
Customer old = customerRepository.findOne(customer.getId());
System.out.println("handleBeforeSave :: new customer.name = " + customer.getName());
System.out.println("handleBeforeSave :: old customer.name = " + old.getName());
}
}
In the event I try to get the old entity using the findOne method but this return the new event. Probably because of Hibernate/Repository caching in the current session.
Is there a way to get the old entity?
I need this to determine if a given property is changed or not. In case the property is changes I need to perform some action.
If using Hibernate, you could simply detach the new version from the session and load the old version:
#RepositoryEventHandler
#Component
public class PersonEventHandler {
#PersistenceContext
private EntityManager entityManager;
#HandleBeforeSave
public void handlePersonSave(Person newPerson) {
entityManager.detach(newPerson);
Person currentPerson = personRepository.findOne(newPerson.getId());
if (!newPerson.getName().equals(currentPerson.getName)) {
//react on name change
}
}
}
Thanks Marcel Overdijk, for creating the ticket -> https://jira.spring.io/browse/DATAREST-373
I saw the other workarounds for this issue and want to contribute my workaround as well, cause I think it´s quite simple to implement.
First, set a transient flag in your domain model (e.g. Account):
#JsonIgnore
#Transient
private boolean passwordReset;
#JsonIgnore
public boolean isPasswordReset() {
return passwordReset;
}
#JsonProperty
public void setPasswordReset(boolean passwordReset) {
this.passwordReset = passwordReset;
}
Second, check the flag in your EventHandler:
#Component
#RepositoryEventHandler
public class AccountRepositoryEventHandler {
#Resource
private PasswordEncoder passwordEncoder;
#HandleBeforeSave
public void onResetPassword(Account account) {
if (account.isPasswordReset()) {
account.setPassword(encodePassword(account.getPassword()));
}
}
private String encodePassword(String plainPassword) {
return passwordEncoder.encode(plainPassword);
}
}
Note: For this solution you need to send an additionally resetPassword = true parameter!
For me, I´m sending a HTTP PATCH to my resource endpoint with the following request payload:
{
"passwordReset": true,
"password": "someNewSecurePassword"
}
You're currently using a spring-data abstraction over hibernate.
If the find returns the new values, spring-data has apparently already attached the object to the hibernate session.
I think you have three options:
Fetch the object in a separate session/transaction before the current season is flushed. This is awkward and requires very subtle configuration.
Fetch the previous version before spring attached the new object. This is quite doable. You could do it in the service layer before handing the object to the repository. You can, however not save an object too an hibernate session when another infect with the same type and id it's known to our. Use merge or evict in that case.
Use a lower level hibernate interceptor as described here. As you see the onFlushDirty has both values as parameters. Take note though, that hibernate normally does not query for previous state of you simply save an already persisted entity. In stead a simple update is issued in the db (no select). You can force the select by configuring select-before-update on your entity.
Create following and extend your entities with it:
#MappedSuperclass
public class OEntity<T> {
#Transient
T originalObj;
#Transient
public T getOriginalObj(){
return this.originalObj;
}
#PostLoad
public void onLoad(){
ObjectMapper mapper = new ObjectMapper();
try {
String serialized = mapper.writeValueAsString(this);
this.originalObj = (T) mapper.readValue(serialized, this.getClass());
} catch (Exception e) {
e.printStackTrace();
}
}
}
I had exactly this need and resolved adding a transient field to the entity to keep the old value, and modifying the setter method to store the previous value in the transient field.
Since json deserializing uses setter methods to map rest data to the entity, in the RepositoryEventHandler I will check the transient field to track changes.
#Column(name="STATUS")
private FundStatus status;
#JsonIgnore
private transient FundStatus oldStatus;
public FundStatus getStatus() {
return status;
}
public FundStatus getOldStatus() {
return this.oldStatus;
}
public void setStatus(FundStatus status) {
this.oldStatus = this.status;
this.status = status;
}
from application logs:
2017-11-23 10:17:56,715 CompartmentRepositoryEventHandler - beforeSave begin
CompartmentEntity [status=ACTIVE, oldStatus=CREATED]
Spring Data Rest can't and likely won't ever be able to do this due to where the events are fired from. If you're using Hibernate you can use Hibernate spi events and event listeners to do this, you can implement PreUpdateEventListener and then register your class with the EventListenerRegistry in the sessionFactory. I created a small spring library to handle all of the setup for you.
https://github.com/teastman/spring-data-hibernate-event
If you're using Spring Boot, the gist of it works like this, add the dependency:
<dependency>
<groupId>io.github.teastman</groupId>
<artifactId>spring-data-hibernate-event</artifactId>
<version>1.0.0</version>
</dependency>
Then add the annotation #HibernateEventListener to any method where the first parameter is the entity you want to listen to, and the second parameter is the Hibernate event that you want to listen for. I've also added the static util function getPropertyIndex to more easily get access to the specific property you want to check, but you can also just look at the raw Hibernate event.
#HibernateEventListener
public void onUpdate(MyEntity entity, PreUpdateEvent event) {
int index = getPropertyIndex(event, "name");
if (event.getOldState()[index] != event.getState()[index]) {
// The name changed.
}
}
Just another solution using model:
public class Customer {
#JsonIgnore
private String name;
#JsonIgnore
#Transient
private String newName;
public void setName(String name){
this.name = name;
}
#JsonProperty("name")
public void setNewName(String newName){
this.newName = newName;
}
#JsonProperty
public void getName(String name){
return name;
}
public void getNewName(String newName){
return newName;
}
}
Alternative to consider. Might be reasonable if you need some special handling for this use-case then treat it separately. Do not allow direct property writing on the object. Create a separate endpoint with a custom controller to rename customer.
Example request:
POST /customers/{id}/identity
{
"name": "New name"
}
I had the same problem, but I wanted the old entity available in the save(S entity) method of a REST repository implementation (Spring Data REST).
What I did was to load the old entity using a 'clean' entity manager from which I create my QueryDSL query:
#Override
#Transactional
public <S extends Entity> S save(S entity) {
EntityManager cleanEM = entityManager.getEntityManagerFactory().createEntityManager();
JPAQuery<AccessControl> query = new JPAQuery<AccessControl>(cleanEM);
//here do what I need with the query which can retrieve all old values
cleanEM.close();
return super.save(entity);
}
The following worked for me. Without starting a new thread the hibernate session will provide the already updated version. Starting another thread is a way to have a separate JPA session.
#PreUpdate
Thread.start {
if (entity instanceof MyEntity) {
entity.previous = myEntityCrudRepository.findById(entity?.id).get()
}
}.join()
Just let me know if anybody would like more context.
Don't know if you're still after an answer, and this is probably a bit 'hacky', but you could form a query with an EntityManager and fetch the object that way ...
#Autowired
EntityManager em;
#HandleBeforeSave
public void handleBeforeSave(Customer obj) {
Query q = em.createQuery("SELECT a FROM CustomerRepository a WHERE a.id=" + obj.getId());
Customer ret = q.getSingleResult();
// ret should contain the 'before' object...
}

Categories

Resources