在Spring Boot Applicaion中,我有一个实体Task
,其状态在执行期间会发生变化:
@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);
}
}
如果状态发生变化,该对象应保存在数据库中。我使用这个Spring Data接口作为存储库:
public interface TaskRepository extends CrudRepository<Task,Long> {}
此代码用于创建和启动Task
:
Task t1 = new Task("Task 1");
Task persisted = taskRepository.save(t1);
persisted.start();
从我的理解persisted
现在附加到持久性会话,如果对象发生更改,则此更改应存储在数据库中。但是这种情况并没有发生,重新加载时,状态就是PENDING。
我在这里做错了什么想法?
答案 0 :(得分:13)
将实例附加到持久性上下文并不意味着对象状态的每个更改都会直接持久化。变更检测仅发生在持久化上下文生命周期中的某些事件上。
您似乎误解了变更检测的工作方式。 JPA的一个非常核心的概念是所谓的持久化上下文。它基本上是unit-of-work pattern的实现。您可以通过两种方式向其添加实体:从数据库加载它们(执行查询或发出EntityManager.find(…)
)或者将它们主动添加到持久性上下文中。这就是对save(…)
方法的调用有效。
这里要认识到的一个重点是“将实体添加到持久化上下文”不必等于“存储在数据库中”。只要认为合理,持久性提供者就可以自由推迟数据库交互。提供商通常这样做是为了能够批量修改数据操作。但是,在很多情况下,初始save(…)
(转换为EntityManager.persist(…)
)将直接执行,例如如果您正在使用自动ID增量。
那就是说,现在该实体已成为一个管理实体。这意味着,持久化上下文可以识别它,并且如果发生需要发生的事件,则会将对实体所做的更改保持透明。最重要的两个是以下几个:
持久性上下文已关闭。在Spring环境中,持久化上下文的生命周期通常绑定到事务。在您的特定示例中,存储库具有默认事务(以及持久性上下文)边界。如果您需要实体保持对其进行管理,则需要延长事务生命周期(通常通过引入具有@Transactional
注释的服务层)。在Web应用程序中,我们经常看到Open Entity Manager In View Pattern,它基本上是一个受请求限制的生命周期。
持久化上下文已刷新。这可以手动发生(通过调用EntityManager.flush()
或透明地发生。例如,如果持久性提供程序需要发出查询,它通常会刷新持久性上下文以确保查询可以找到当前挂起的更改。想象一下加载用户,将他的地址更改为新地点,然后发出查询以按地址查找用户。提供商将足够聪明,可以先刷新地址更改,然后再执行查询。