Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do changes to my JPA entity not get persisted to the database?

In a Spring Boot Applicaion, I have an entity Task with a status that changes during execution:

@Entity
public class Task {

  public enum State {
    PENDING,
    RUNNING,
    DONE
  }

  @Id @GeneratedValue
  private long id;
  private String name;
  private State state = State.PENDING;

  // Setters omitted

  public void setState(State state) {
    this.state = state; // THIS SHOULD BE WRITTEN TO THE DATABASE
  }

  public void start() { 
    this.setState(State.RUNNING);

    // do useful stuff
    try { Thread.sleep(2000); } catch(InterruptedException e) {}
    this.setState(State.DONE);
  }
} 

If state changes, the object should be saved in the database. I'm using this Spring Data interface as repository:

 public interface TaskRepository extends CrudRepository<Task,Long> {}

And this code to create and start a Task:

Task t1 = new Task("Task 1");
Task persisted = taskRepository.save(t1);
persisted.start();

From my understanding persisted is now attached to a persistence session and if the object changes this changes should be stored in the database. But this is not happening, when reloading it the state is PENDING.

Any ideas what I'm doing wrong here?

like image 510
Jörg Brandstätt Avatar asked Jul 16 '14 18:07

Jörg Brandstätt


1 Answers

tl;dr

Attaching an instance to a persistence context does not mean every change of the state of the object gets persisted directly. Change detection only occurs on certain events during the lifecycle of persistence context.

Details

You seem to misunderstood the way change detection works. A very central concept of JPA is the so called persistence context. It is basically an implementation of the unit-of-work pattern. You can add entities to it in two ways: by loading them from the database (executing a query or issuing an EntityManager.find(…)) or by actively adding them to the persistence context. This is what the call to the save(…) method effectively does.

An important point to realize here is that "adding an entity to the persistence context" does not have to be equal to "stored in the database". The persistence provider is free to postpone the database interaction as long as it thinks is reasonable. Providers usually do that to be able to batch up modifying operations on the data. In a lot of cases however, an initial save(…) (which translates to an EntityManager.persist(…)) will be executed directly, e.g. if you're using auto id increment.

That said, now the entity has become a managed entity. That means, the persistence context is aware of it and will persist the changes made to the entity transparent, if events occur that need that to take place. The two most important ones are the following ones:

  1. The persistence context gets closed. In Spring environments the lifecycle of the persistence context is usually bound to a transaction. In your particular example, the repositories have a default transaction (and thus persistence context) boundary. If you need the entity to stay managed around it, you need to extend the transaction lifecycle (usually by introducing a service layer that has @Transactional annotations). In web applications we often see the Open Entity Manager In View Pattern, which is basically a request-bound lifecycle.

  2. The persistence context is flushed. This can either happen manually (by calling EntityManager.flush() or transparently. E.g. if the persistence provider needs to issue a query, it will usually flush the persistence context to make sure, currently pending changes can be found by the query. Imagine you loaded a user, changed his address to a new place and then issue a query to find users by their addresses. The provider will be smart enough to flush the address change first and execute the query afterwards.

like image 84
Oliver Drotbohm Avatar answered Jan 01 '23 09:01

Oliver Drotbohm