Spring Data(Hibernate)JPA更新了在事务

时间:2016-04-27 17:30:37

标签: spring hibernate jpa orm spring-data-jpa

我有一个实体" Job"使用布尔标志"暂停":

@Entity
@XmlRootElement(name = "Job")
@Where(clause = "deleted=0")
public class Job {
    ...
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    private boolean suspended;
    ...
}

Spring CrudRepository(JPA Hibernate)用于持久化:

@Repository
public interface JobRepository extends CrudRepository<Job, Integer>, JobStatusSupport {}

我需要更新&#34;暂停&#34;单独标记,不覆盖对并发线程中其他字段的更新。因此,自然而然的事情似乎是编写一种方法,只更新&#34;暂停&#34;字段:

public class JobRepositoryImpl implements JobStatusSupport {
    private final static String SET_SUSPENDED = "UPDATE Job SET suspended = :suspended, modificationDate = :modificationDate WHERE id = :id";

    @Override
    public int setSuspended(int id, boolean suspended, Instant modificationDate) {
        int updateCount =  em.createQuery(SET_SUSPENDED)
                .setParameter("suspended", suspended)
                .setParameter("modificationDate", modificationDate)
                .setParameter("id", id)
                .executeUpdate();
        return updateCount;
    }
}

现在我在我的代码中有以下场景(显然缩短了,实际上这是分散在几个方法上,但这个例子确实重现了这个问题):

@Transactional
public void resumeJob(int id) {
    Job jobA = jobRepository.findOne(Integer.valueOf(id));
      // jobA.suspended == true
      // let's set "suspended" to "false"
    int updateCount = jobRepository.setSuspended(id, false, Instant.now());
      // OK: updateCount is 1
    Job jobB = jobRepository.findOne(Integer.valueOf(id));
      // jobB.suspended == true ??? that was just set to "false, wasn't it?
}

可能我错过了一些关于JPA / Hibernate的基础知识。但是,这仍然非常违反直觉:为什么jobB.suspended仍然是&#34; true&#34;虽然更新成功,但数据再次从数据库中读取&#34;?为什么在交易中看不到单个字段的更新?

(正如人们所料,事务完成后,Job.suspended在数据库中为&#34; false&#34;以及后续读取。)

如何正确地解决这个问题?我应该如何编写更新单个字段的代码,以便JPA知道做了什么?我是否需要研究&#34;合并&#34;对于像这样简单的事情?

能够编写我们自己的SQL语句对我们的项目至关重要。我正在尝试Spring Data JPA,主要是为了避免编写大量CRUD操作的繁琐工作。但是,如果我在这个简单的场景中遇到过这样的问题,我想知道我们是否会更好地使用JdbcTemplate。

更新:了解有关ORM的内容

男人,我一无所知!我曾在之前使用过JPA的项目中工作过。但我从来没有详细处理过它(我想知道其他人是否做过)。

编写&#34;更新方法的全部工作&#34;是徒劳的!我把它减少到以下几点:

@Transactional
public void resumeJob(int id) {
    Job job = jobRepository.findOne(Integer.valueOf(id));
    job.setSuspended(false);
    job.setName("And Now for Something Completely Different.");
}

这就是全部!这会更新数据库和缓存,天知道什么。仅@Transactional注释就足以持久化更改。如果删除注释,则DB保持不变。因此,ORM基本上是针对每个人都看到的缓存(通过&#34;附加对象&#34;)。然后人们希望人们将@Transactional放在正确的位置(而不是私有方法,例如......),并且ORM机器知道它在做什么(例如,在开放事务之外不显示缓存更新)。

老实说,这对我来说似乎有点太神奇了。但既然我知道它的全部意义,我会试一试。编写广泛的CRUD方法也不是很有吸引力。

如果我弄错了或者您有最佳做法的链接,请发表评论。 (我开始怀疑是否最好不要立即分离我从数据库获得的每个对象,从而击败ORM的整个目的: - )

更新:EntityManager#clear()足以进行快速修复

这绝对不是使用ORM的聪明方法,但目前我只需在我编写的一些更新方法中调用clear()。这使整个缓存无效,并且事务中其他位置的下一个读取将接收更新的数据。当然,正确的方法是简单地修改附加的实体,即&#34; job.setSuspended(false);&#34;。

不需要调用flush(),当您想要在系统崩溃时最大限度地降低丢失数据的风险时,它可能会引起您的兴趣。我想Hibernate不会立即将完成的事务写入磁盘?

1 个答案:

答案 0 :(得分:3)

这是违反直觉的,但如果你考虑一下,这很正常。

  1. 您加载ID为3的实体.Hibernate将其存储在会话缓存中
  2. 您执行更新查询。这个查询几乎是Hibernate的黑盒子。它无法知道哪些行受更改影响,并且您不通过修改实体来执行这些更改,而是直接修改数据库中的行。因此,行被修改,但ID为3的实体在会话缓存
  3. 中保持不变
  4. 您在同一会话中再次加载实体。所以Hibernate只返回已经在缓存中的实例,因此不包含更改。
  5. 如果您想要更新的实体,您有两个解决方案:

    • 通过修改实体来修改数据库,或
    • 更新查询完成后,
    • clear缓存。