每个实体一个DAO-如何处理引用



我正在编写一个具有典型两个实体的应用程序:User和UserGroup。后者可以包含前者的一个或多个实例。我有以下(更多/更少)的映射:

用户:

public class User {
@Id
@GeneratedValue
private long id;
@ManyToOne(cascade = {CascadeType.MERGE})
@JoinColumn(name="GROUP_ID")
private UserGroup group;
public UserGroup getGroup() {
return group;
}
public void setGroup(UserGroup group) {
this.group = group;
}
}

用户组:

public class UserGroup {
@Id
@GeneratedValue
private long id;
@OneToMany(mappedBy="group", cascade = {CascadeType.REMOVE}, targetEntity = User.class)
private Set<User> users;
public void setUsers(Set<User> users) {
this.users = users;
}
}

现在,我为这些实体(UserDao和UserGroupDao)中的每一个都有一个单独的DAO类。我的所有DAO都使用@PersistenceContext注释注入了EntityManager,如下所示:

@Transactional
public class SomeDao<T> {
private Class<T> persistentClass;
@PersistenceContext
private EntityManager em;
public T findById(long id) {
return em.find(persistentClass, id);
}
public void save(T entity) {
em.persist(entity);
}
}

使用这种布局,我想创建一个新用户,并将其分配给现有的用户组。我是这样做的:

UserGroup ug = userGroupDao.findById(1);
User u = new User();
u.setName("john");
u.setGroup(ug);
userDao.save(u);

不幸的是,我得到了以下异常:

对象引用未保存的瞬态实例-保存瞬态刷新前的实例:x.y.z.model.User.group->x.y.z.model.UserGroup

我对此进行了调查,我认为之所以会发生这种情况,是因为每个DAO实例都分配了不同的entityManager(我检查了这一点-每个DAO中对实体管理器的引用不同),而对于用户而言,entityManager不管理传递的UserGroup实例。

我已经尝试将分配给用户的用户组合并到UserDAO的实体管理器中。有两个问题:

  • 它仍然不起作用-实体管理器想要覆盖现有的UserGroup,但它得到了异常(显然)
  • 即使它有效,我最终也会为每个相关实体编写合并代码

当find和persistent都使用同一实体管理器时,所描述的案例会起作用。这指向一个问题:

  • 我的设计坏了吗?我认为这与这个答案中推荐的非常相似。所有DAO都应该有一个EntityManager吗(web声明除外)
  • 还是应该在DAO内部完成组分配?在这种情况下,我最终会在DAO中编写大量代码
  • 我应该去掉DAO吗?如果是,如何很好地处理数据访问
  • 还有其他解决方案吗

我使用Spring作为容器,使用Hibernate作为JPA实现。

EntityManager的不同实例在Spring中是正常的。它创建动态使用当前事务中的实体管理器(如果存在)的代理。否则,将创建一个新的。

问题是您的交易时间太短。检索用户组在事务中执行(因为findById方法是隐式@Transactional)。但是随后事务提交并且组被分离。保存新用户时,它将创建一个事务,该事务失败,因为用户引用了一个分离的实体。

解决这个问题的方法(以及通常做这些事情)是创建一个方法,在单个事务中完成整个操作。只需在服务类中创建该方法(任何Spring托管组件都可以工作),并用@Transactional对其进行注释。

我不知道Spring,但JPA的问题是,您正在持久化一个引用了UserGroupUser,但JPA.认为UserGroup就是transient

transient是JPA实体可以处于的生命周期状态之一。这意味着它只是用新操作符创建的,但尚未持久化(还没有持久化标识)。

由于您是通过DAO获得UserGroup实例的,因此似乎出现了问题。您的实例不应该是transient,而是detached。您能在从DAO收到UserGroup实例的Id后立即打印它吗?也许还展示了findById的实现?

group关系上没有级联持久化,所以如果实体确实分离了,这通常应该有效。如果没有新实体,JPA根本无法正确设置FK,因为它需要UserGroup实例的Id,但(似乎)不存在。

合并也不应该"覆盖"分离的实体。你来这里有什么例外?

我只是部分同意其他人给出的关于必须将所有东西都放在一笔交易中的答案。是的,这确实可能更方便,因为UserGroup实例仍将"附加",但不应该是-必需的-。JPA完全能够通过引用其他新实体或在另一个事务中获得的现有(分离的)实体来持久化新实体。请参见例如JPA级联持久化和对分离实体的引用引发PersistentObjectException。为什么?

我不确定如何解决,但我已经设法解决了这个问题。我试图将用户分配给的用户组在数据库中的版本字段为NULL(该字段用@version注释)。在测试使用此表的GWT RequestFactory时,我发现这是一个问题。当我将字段设置为1时,一切都开始工作(不需要更改事务处理)。

如果NULL版本字段真的导致了问题,那么这将是我收到的最具误导性的异常消息之一。

最新更新