【发布时间】:2016-08-22 04:02:27
【问题描述】:
我有一个带有布尔标志“已暂停”的实体“工作”:
@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 {}
我需要单独更新“挂起”标志,而不会覆盖对并发线程中其他字段所做的更新。所以很自然的事情似乎是编写一个只更新“暂停”字段的方法:
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 仍然是“真”?为什么单个字段的更新在事务中不可见?
(正如人们所期望的那样, 事务完成后,Job.suspended 在数据库中为“假”并用于后续读取。)
如何正确处理这件事?我应该如何编写更新各个字段的代码,以便 JPA 知道所做的事情?像这样简单的事情我是否必须考虑“合并”?
能够编写自己的 SQL 语句对我们的项目至关重要。我正在尝试 Spring Data JPA 主要是为了避免编写大量 CRUD 操作的繁琐工作。但是如果我已经在这个简单的场景中遇到了这样的问题,我想知道使用 JdbcTemplate 是否会更好。
更新:了解 ORM
伙计,我是不是一无所知!我曾在以前使用过 JPA 的项目中工作。但我从来不需要详细处理它(我想知道是否有其他人这样做过)。
编写“更新方法”的全部努力都是徒劳的!我已将其简化为以下内容:
@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 注释足以持久化更改。如果注释被删除,数据库保持不变。所以 ORM 基本上是针对每个人都看到的缓存(通过“附加对象”)工作的。然后希望人们将@Transactional 放在正确的位置(例如,不是放在私有方法上......)并且ORM 机器知道它在做什么(例如,不使缓存更新在打开的事务之外可见)。
老实说,这对我来说似乎有点太神奇了。但现在我知道它的全部内容了,我会试一试。编写大量的 CRUD 方法也不是很吸引人。
如果我弄错了,或者您有最佳实践的链接,请发表评论。 (我开始怀疑是否最好立即分离我从数据库中获得的每个对象,从而破坏 ORM 的全部目的:-)
更新:EntityManager#clear() 足以快速修复
这绝对不是使用 ORM 的聪明方法,但目前我可以在我编写的几个更新方法中简单地调用 clear()。这会使整个缓存无效,并且事务中其他位置的下一次读取会接收更新的数据。当然,正确的做法是简单地修改附加的实体,即“job.setSuspended(false);”。
不需要调用flush(),可能只有当您希望在系统崩溃的情况下将丢失数据的风险降至最低时才会感兴趣。我想 Hibernate 不会立即将完成的事务写入磁盘?
【问题讨论】:
-
JPA 维护托管实例的缓存。读入它们后,您将需要刷新这些实体以反映在 JPA 实体访问之外所做的任何更改。如果您通过批量查询(更新/删除)更改数据库中的数据,那么您应该在将实体读入缓存之前在上下文/事务的开头执行此操作,因为这些语句不会影响托管实体的缓存。或者只是在更新语句之后刷新并清除实体管理器,以便同步任何更改并清除缓存。
标签: spring hibernate jpa orm spring-data-jpa