JPA 不获取反映数据库状态的数据
JPA not fetching data that reflects state of database
我在编写代码时遇到了一个奇怪的错误或功能。情况是这样的:
我们在 JavaEE 项目中使用 PostgreSQL 数据库 EclipseLink。
我在这部分项目中所做的是从数据库中获取一个实体,即:
User user = userController.get(userId);
然后转到我们的控制器并通过 TypedQuery 获取用户:
@Stateless
@LocalBean
public class UserController {
private EntityManager em;
public User get(Integer userId){
User retval = null;
TypedQuery<User> = em.createNamedQuery("User.findByUserId", User.class);
q.setParameter("userId", userId);
retval = q.getSingleResult();
}
public User update(final User modified){...}
}
在我的用户 class 中我有:
@NamedQuery(name = "User.findByUserId", query = "SELECT u FROM User u WHERE u.id = :userId"),
所以调用开始了,我从数据库中获取了我的用户对象及其各自的数据。
在我调用userController.get方法的class中,我继续修改这个对象上的数据,并再次调用我们的控制器来更新数据库上的这个数据
user.setServiceId(1); //any id (integer) pointing to an existing service, this is a ManyToOne relationship
userController.update(user);
这就是有趣的地方。在控制器内的更新方法中 class 我修改了 User 对象并使用该对象获取主键 userId 并再次从数据库中获取数据以获取原始数据:
@Stateless
@LocalBean
public class userController {
private EntityManager em;
public User get(Integer userId){...}
public User update(final User modified){
User retval = null;
if(modified != null){
try {
User original = get(modified.getId()); //Here I fetch the current state of the DB
if(original != null){
Set<Modifications> modifications = apply(original, modified); //Method to apply modifications
retval = em.merge(original); //Merge changes into database
em.flush(); //Force data to be persisted
catch(Exception e){
}
return retval;
}
}
但是,original
对象中的字段不反映数据库的状态,而是包含与 modified
对象相同的数据。在这种情况下,数据库上的 serviceId
是 null
,而在 modified
中我将其设置为一个 ID。 original
将其 serviceId
设置为与 modified
对象相同的值,即使它应该包含从数据库中获取的数据,在本例中为 null
我目前的解决方案是构造一个新的 User 对象,从数据库中获取用户后,修改该新对象上的数据:
User user = userController.get(userId);
User newUser = new User(user);
newUser.setService(service);
userController.update(newUser);
现在,当我执行更新方法时,original
反映了数据库的状态。
或者它反映了持久性上下文中已经存在的 user
对象的状态?
但是为什么会这样呢?因为我确实在我的更新方法中使用 SELECT
语句对数据库进行了新的 get
调用。
如果在管理“用户”实体时调用 user.setServiceId(1);
,该调用将更新数据库行。
您需要refresh
将数据保存到数据库后获取对象的最新状态,如em.refresh(retval)
您可以在下面找到添加的代码。
@Stateless
@LocalBean
public class userController {
private EntityManager em;
public User get(Integer userId){...}
public User update(final User modified){
User retval = null;
if(modified != null){
try {
User original = get(modified.getId()); //Here I fetch the current state of the DB
if(original != null){
Set<Modifications> modifications = apply(original, modified); //Method to apply modifications
retval = em.merge(original); //Merge changes into database
em.flush(); //Force data to be persisted
em.refresh(retval); // This will fetch the updated data from database
catch(Exception e){
}
return retval;
}
}
您对所有内容使用相同的 EntityManager,包括读取和 'merge',在本例中为 no-op。通过 EM 读入的所有内容都是受管理的,因此如果您再次读回它,您会得到相同的实例。只要 User 没有被序列化,它就会被 EntityManager 'managed' 从中读取,因此同一实例及其更改在对该 ID 的任何 get 调用中都是可见的。
您没有说明您是如何获得 EntityManagers 的,但我猜这不是容器管理的,因为它们会为这些调用注入一个新的,然后在完成后为您关闭它们。您没有显示任何关于更新和它正在使用的 em 上下文如何连接的事务逻辑,但我建议您为这些调用创建一个新的 EntityManager。 Flush 似乎也没有必要,就像更新被包裹在事务中一样,应该处理将更新语句刷新到数据库而无需额外调用。
我在编写代码时遇到了一个奇怪的错误或功能。情况是这样的:
我们在 JavaEE 项目中使用 PostgreSQL 数据库 EclipseLink。
我在这部分项目中所做的是从数据库中获取一个实体,即:
User user = userController.get(userId);
然后转到我们的控制器并通过 TypedQuery 获取用户:
@Stateless
@LocalBean
public class UserController {
private EntityManager em;
public User get(Integer userId){
User retval = null;
TypedQuery<User> = em.createNamedQuery("User.findByUserId", User.class);
q.setParameter("userId", userId);
retval = q.getSingleResult();
}
public User update(final User modified){...}
}
在我的用户 class 中我有:
@NamedQuery(name = "User.findByUserId", query = "SELECT u FROM User u WHERE u.id = :userId"),
所以调用开始了,我从数据库中获取了我的用户对象及其各自的数据。
在我调用userController.get方法的class中,我继续修改这个对象上的数据,并再次调用我们的控制器来更新数据库上的这个数据
user.setServiceId(1); //any id (integer) pointing to an existing service, this is a ManyToOne relationship
userController.update(user);
这就是有趣的地方。在控制器内的更新方法中 class 我修改了 User 对象并使用该对象获取主键 userId 并再次从数据库中获取数据以获取原始数据:
@Stateless
@LocalBean
public class userController {
private EntityManager em;
public User get(Integer userId){...}
public User update(final User modified){
User retval = null;
if(modified != null){
try {
User original = get(modified.getId()); //Here I fetch the current state of the DB
if(original != null){
Set<Modifications> modifications = apply(original, modified); //Method to apply modifications
retval = em.merge(original); //Merge changes into database
em.flush(); //Force data to be persisted
catch(Exception e){
}
return retval;
}
}
但是,original
对象中的字段不反映数据库的状态,而是包含与 modified
对象相同的数据。在这种情况下,数据库上的 serviceId
是 null
,而在 modified
中我将其设置为一个 ID。 original
将其 serviceId
设置为与 modified
对象相同的值,即使它应该包含从数据库中获取的数据,在本例中为 null
我目前的解决方案是构造一个新的 User 对象,从数据库中获取用户后,修改该新对象上的数据:
User user = userController.get(userId);
User newUser = new User(user);
newUser.setService(service);
userController.update(newUser);
现在,当我执行更新方法时,original
反映了数据库的状态。
或者它反映了持久性上下文中已经存在的 user
对象的状态?
但是为什么会这样呢?因为我确实在我的更新方法中使用 SELECT
语句对数据库进行了新的 get
调用。
如果在管理“用户”实体时调用 user.setServiceId(1);
,该调用将更新数据库行。
您需要refresh
将数据保存到数据库后获取对象的最新状态,如em.refresh(retval)
您可以在下面找到添加的代码。
@Stateless
@LocalBean
public class userController {
private EntityManager em;
public User get(Integer userId){...}
public User update(final User modified){
User retval = null;
if(modified != null){
try {
User original = get(modified.getId()); //Here I fetch the current state of the DB
if(original != null){
Set<Modifications> modifications = apply(original, modified); //Method to apply modifications
retval = em.merge(original); //Merge changes into database
em.flush(); //Force data to be persisted
em.refresh(retval); // This will fetch the updated data from database
catch(Exception e){
}
return retval;
}
}
您对所有内容使用相同的 EntityManager,包括读取和 'merge',在本例中为 no-op。通过 EM 读入的所有内容都是受管理的,因此如果您再次读回它,您会得到相同的实例。只要 User 没有被序列化,它就会被 EntityManager 'managed' 从中读取,因此同一实例及其更改在对该 ID 的任何 get 调用中都是可见的。
您没有说明您是如何获得 EntityManagers 的,但我猜这不是容器管理的,因为它们会为这些调用注入一个新的,然后在完成后为您关闭它们。您没有显示任何关于更新和它正在使用的 em 上下文如何连接的事务逻辑,但我建议您为这些调用创建一个新的 EntityManager。 Flush 似乎也没有必要,就像更新被包裹在事务中一样,应该处理将更新语句刷新到数据库而无需额外调用。