英文:
JPA not fetching data that reflects state of database
问题
我在编写代码时遇到了一个奇怪的错误或特性。以下是情况:
我们正在使用PostgreSQL数据库,在JavaEE项目中使用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> q = em.createNamedQuery("User.findByUserId", User.class);
q.setParameter("userId", userId);
retval = q.getSingleResult();
}
public User update(final User modified){...}
}
在我的User类中,我有:
@NamedQuery(name = "User.findByUserId", query = "SELECT u FROM User u WHERE u.id = :userId"),
因此,调用进行,我从数据库中获取了用户对象及其相应的数据。
在调用了userController.get方法的类中,我继续修改该对象上的数据,并再次调用我们的控制器以更新数据库中的这些数据:
user.setServiceId(1); //任何指向现有服务的ID(整数),这是ManyToOne关系
userController.update(user);
这就是情况有趣的地方。在控制器类内的我们的update方法中,我有了修改后的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()); //在这里,我获取了DB的当前状态
if(original != null){
Set<Modifications> modifications = apply(original, modified); //应用修改的方法
retval = em.merge(original); //将更改合并到数据库
em.flush(); //强制将数据持久化
} 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
调用从数据库中获取了数据。
英文:
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.
答案1
得分: 2
你正在对所有操作使用同一个EntityManager,无论是读取还是'merge'操作,而在这种情况下'merge'操作实际上是一个空操作。通过EntityManager读取的所有内容都是受管理的,因此如果您再次读取它,您将获得相同的实例。只要User对象没有被序列化,它就会被它所属的EntityManager“管理”,因此对于该ID上的任何获取调用,都可以看到同一实例及其更改。
您没有展示如何获取EntityManagers,但我猜想它可能不是容器管理的,因为容器管理的话会在这些调用中注入一个新的EntityManager,并在完成后关闭它们。您没有展示更新操作的事务逻辑以及它使用的EntityManager上下文是如何连接的,但我建议您为这些调用创建一个新的EntityManager。此外,Flush似乎是不必要的,如果更新操作被包装在事务中,应该可以在不需要这个额外调用的情况下将更新语句刷新到数据库中。
英文:
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.
答案2
得分: 1
如果在管理“user”实体时调用了user.setServiceId(1);
,该调用将更新数据库行。
您可以查看管理实体生命周期。
英文:
If user.setServiceId(1);
is called when the "user" entity is managed, the call is going to update the database row.
you can check the manage entity lifecycle
答案3
得分: 0
你需要在将数据保存到数据库后进行刷新
操作,以获取对象的最新状态,例如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()); //在这里获取数据库的当前状态
if(original != null){
Set<Modifications> modifications = apply(original, modified); //应用修改的方法
retval = em.merge(original); //将更改合并到数据库
em.flush(); //强制将数据持久化
em.refresh(retval); // 这将从数据库中获取更新的数据
}
} catch(Exception e){
}
}
return retval;
}
}
英文:
You need to refresh
the data after saving it to the database and to get the latest state of the object, as em.refresh(retval)
You can find the code added below.
@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;
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论