Search code examples
javapostgresqljpaeclipselinkjava-ee-8

JPA not fetching data that reflects state of database


I have encountered a curious bug or feature while writing code. Here's the situation:

We are using a PostgreSQL database, EclipseLink in a JavaEE project.

What I am doing in this part of the project is fetching an entity from the database i.e.:

User user = userController.get(userId);

Which then goes to our controller and fetches the user via a 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){...}
}

And in my User class I have:

@NamedQuery(name = "User.findByUserId", query = "SELECT u FROM User u WHERE u.id = :userId"),

So the call goes, I get my user object with its respective data from the database.

In the class where I called the userController.get method I continue to modify the data on this object, and call our controller again to update this data on the database

user.setServiceId(1); //any id (integer) pointing to an existing service, this is a ManyToOne relationship
userController.update(user);

And here is where it gets funny. In our update method inside the controller class I have my modified User object and using this object I get the primary key userId and fetch the data again from the database to get the original:

@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;
    }
}

However, the fields in the original object do not reflect the state of the database but instead contains the same data as the modified object. In this case, the serviceId on the database is null, and in the modified I set it to an ID. The original has its serviceId set to the same value as the modified object even though it should contain the fetched data from the database, in this case null

My current solution is to construct a new User object, after fetching the user from the database, and modify the data on that new object:

User user = userController.get(userId);
User newUser = new User(user);
newUser.setService(service);
userController.update(newUser);

Now when I do the update method, the original reflects the state of the database.

Or maybe it reflects the state of the user object that already exists in the persistence context?

But why does this happen? Since I do make a new get call with a SELECT statement to the database in my update method.


Solution

  • You are using the same EntityManager for everything, both the read and the 'merge', which in this case is then a no-op. Everything read in through an EM is managed, so that if you read it back again, you get the same instance back. As long as the User isn't being serialized, it is 'managed' by the EntityManager it was read from, and so that same instance, and its changes, are visible on any get calls on that ID.

    You didn't show how you are getting EntityManagers, but I would guess is isn't container managed, as they would inject a new one for these calls, and then close them for you when done. You haven't shown any transaction logic on how the update and the em context it is using are hooked up, but I would suggest you create a new EntityManager for these calls. Flush also seems unnecessary, as if update is wrapped in a transaction, should handle flushing the update statement to the database without this extra call.