Spring 事务中不可见的个别字段的数据 (Hibernate) JPA 更新
Spring Data (Hibernate) JPA update of individual fields not visible within transaction
我有一个带有布尔标志的实体 "Job" "suspended":
@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 {}
我需要单独更新 "suspended" 标志,而不覆盖对并发线程中其他字段所做的更新。所以自然而然的事情似乎是写一个只更新 "suspended" 字段的方法:
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 的基础知识。但是,这仍然是非常违反直觉的:为什么更新成功并再次读取数据"from the DB",但jobB.suspended仍然是"true"?为什么单个字段的更新在交易中看不到?
(正如人们所期望的那样,在事务完成后,Job.suspended在数据库中"false"并用于后续读取。)
如何正确处理这件事?我应该如何编写更新各个字段的代码,以便 JPA 知道所做的事情?我是否必须研究 "merge" 才能找到像这样简单的东西?
能够编写我们自己的 SQL 语句对我们的项目至关重要。我正在尝试 Spring Data JPA,主要是为了避免编写大量 CRUD 操作的繁琐工作。但是如果我已经在这个简单的场景中遇到这样的问题,我想知道我们是否会更好地使用 JdbcTemplate。
更新:了解了一些关于 ORM 的知识
伙计,我是无能为力!我在以前使用 JPA 的项目中工作。但我从来不需要详细处理它(我想知道是否有其他人这样做过)。
写 "update method" 的全部努力都是徒劳的! 我将其简化为以下内容:
@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 基本上是针对每个人都能看到的缓存(通过 "attached objects")工作的。然后希望人们将 @Transactional 放在正确的位置(例如,不要放在私有方法上......)并且 ORM 机器知道它在做什么(例如,不要让缓存更新在打开的事务之外可见)。
老实说,这对我来说似乎有点太神奇了。但现在我知道这是怎么回事了,我会试一试。编写大量的 CRUD 方法也不是很吸引人。
如果我弄错了或者您是否有最佳实践链接,请发表评论。 (我开始怀疑是否最好立即分离我从数据库中获得的每个对象,从而破坏 ORM 的全部目的:-)
更新:EntityManager#clear() 足以快速修复
这绝对不是使用 ORM 的巧妙方法,但目前我可以简单地在我编写的几个更新方法中调用 clear() 。这会使整个缓存失效,并且事务中其他地方的下一次读取会收到更新的数据。当然,正确的做法是简单地修改附加实体,即 "job.setSuspended(false);".
不需要调用 flush(),可能只有当您希望在系统崩溃的情况下将丢失数据的风险降到最低时,它才会变得有趣。我想 Hibernate 不会立即将完成的事务写入磁盘?
这很反直觉,但如果你仔细想想,这很正常。
- 您加载 ID 为 3 的实体。Hibernate 将其存储在其会话缓存中
- 您执行更新查询。这个查询对于 Hibernate 来说几乎是一个黑盒子。它不知道哪些行受到更改的影响,并且您不是通过修改实体来进行这些更改,而是直接修改数据库中的行。所以行被修改了,但是 ID 为 3 的实体在会话缓存中保持不变
- 您在同一会话中再次加载该实体。所以 Hibernate 只是 returns 已经在缓存中的实例,因此不包含更改。
如果你想要一个更新的实体,你有两个解决方案:
- 通过修改实体修改数据库,或者
- clear更新查询后的缓存。
我有一个带有布尔标志的实体 "Job" "suspended":
@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 {}
我需要单独更新 "suspended" 标志,而不覆盖对并发线程中其他字段所做的更新。所以自然而然的事情似乎是写一个只更新 "suspended" 字段的方法:
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 的基础知识。但是,这仍然是非常违反直觉的:为什么更新成功并再次读取数据"from the DB",但jobB.suspended仍然是"true"?为什么单个字段的更新在交易中看不到?
(正如人们所期望的那样,在事务完成后,Job.suspended在数据库中"false"并用于后续读取。)
如何正确处理这件事?我应该如何编写更新各个字段的代码,以便 JPA 知道所做的事情?我是否必须研究 "merge" 才能找到像这样简单的东西?
能够编写我们自己的 SQL 语句对我们的项目至关重要。我正在尝试 Spring Data JPA,主要是为了避免编写大量 CRUD 操作的繁琐工作。但是如果我已经在这个简单的场景中遇到这样的问题,我想知道我们是否会更好地使用 JdbcTemplate。
更新:了解了一些关于 ORM 的知识
伙计,我是无能为力!我在以前使用 JPA 的项目中工作。但我从来不需要详细处理它(我想知道是否有其他人这样做过)。
写 "update method" 的全部努力都是徒劳的! 我将其简化为以下内容:
@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 基本上是针对每个人都能看到的缓存(通过 "attached objects")工作的。然后希望人们将 @Transactional 放在正确的位置(例如,不要放在私有方法上......)并且 ORM 机器知道它在做什么(例如,不要让缓存更新在打开的事务之外可见)。
老实说,这对我来说似乎有点太神奇了。但现在我知道这是怎么回事了,我会试一试。编写大量的 CRUD 方法也不是很吸引人。
如果我弄错了或者您是否有最佳实践链接,请发表评论。 (我开始怀疑是否最好立即分离我从数据库中获得的每个对象,从而破坏 ORM 的全部目的:-)
更新:EntityManager#clear() 足以快速修复
这绝对不是使用 ORM 的巧妙方法,但目前我可以简单地在我编写的几个更新方法中调用 clear() 。这会使整个缓存失效,并且事务中其他地方的下一次读取会收到更新的数据。当然,正确的做法是简单地修改附加实体,即 "job.setSuspended(false);".
不需要调用 flush(),可能只有当您希望在系统崩溃的情况下将丢失数据的风险降到最低时,它才会变得有趣。我想 Hibernate 不会立即将完成的事务写入磁盘?
这很反直觉,但如果你仔细想想,这很正常。
- 您加载 ID 为 3 的实体。Hibernate 将其存储在其会话缓存中
- 您执行更新查询。这个查询对于 Hibernate 来说几乎是一个黑盒子。它不知道哪些行受到更改的影响,并且您不是通过修改实体来进行这些更改,而是直接修改数据库中的行。所以行被修改了,但是 ID 为 3 的实体在会话缓存中保持不变
- 您在同一会话中再次加载该实体。所以 Hibernate 只是 returns 已经在缓存中的实例,因此不包含更改。
如果你想要一个更新的实体,你有两个解决方案:
- 通过修改实体修改数据库,或者
- clear更新查询后的缓存。