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 对象相同的数据。在这种情况下,数据库上的 serviceIdnull,而在 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);,该调用将更新数据库行。

你可以查看manage entity lifecycle

您需要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 似乎也没有必要,就像更新被包裹在事务中一样,应该处理将更新语句刷新到数据库而无需额外调用。