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

时间:2012-09-24 19:03:42

标签: java spring jpa entitymanager spring-orm

我正在编写一个具有典型两个实体的应用程序: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 - &gt;   x.y.z.model.UserGroup

我调查了它,我认为这是因为每个DAO实例都有不同的entityManager分配(我检查过 - 每个DAO中的引用与实体管理器不同)并且对于用户entityManager不管理传递了UserGroup实例。

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

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

当使用相同的实体管理器进行查找和持久化时,描述的情况有效。这指出了一个问题:

  • 我的设计坏了吗?我认为它与this answer中推荐的非常相似。是否应该为所有DAO单独使用EntityManager(否则网络声称)?
  • 或者是否应该在DAO内完成小组分配?在这种情况下,我最终会在DAO中编写大量代码
  • 我应该摆脱DAO吗?如果是的话,如何很好地处理数据访问?
  • 任何其他解决方案?

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

3 个答案:

答案 0 :(得分:2)

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

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

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

答案 1 :(得分:2)

我不知道Spring,但JPA问题是你持有User引用了UserGroup,但JPA认为UserGroup是{{1} }}

transient是JPA实体可以进入的生命周期状态之一。它意味着它只是使用new运算符创建的,但尚未持久化(尚​​未具有持久性标识)。< / p>

由于您通过DAO获得了transient实例,因此似乎出现了问题。您的实例不应该是UserGroup,而是transient。您是否可以在从DAO收到后立即打印detached实例的ID?也许还会显示UserGroup实现?

你没有findById关系上的级联持久性,所以这通常应该在实体确实分离的情况下起作用。如果没有新的实体,JPA根本无法正确设置FK,因为它需要group实例的Id,但(看似)不存在。

合并也不应“覆盖”您的分离实体。你来到这里的例外是什么?

我只是部分同意这里其他人给出的关于必须将所有内容放在一个交易中的答案。是的,这确实可能更方便,因为UserGroup实例仍然是“附加”的,但它不应该是 - 必要的 - 。 JPA完全能够通过引用其他新事件或在另一个事务中获得的现有(分离)实体来持久化新实体。参见例如JPA cascade persist and references to detached entities throws PersistentObjectException. Why?

答案 2 :(得分:1)

我不知道怎么做但我设法解决了这个问题。我试图将用户分配给数据库中的具有NULL版本字段的用户组(使用@Version注释的字段)。当我测试使用此表的GWT RequestFactory时,我发现这是一个问题。当我将字段设置为1时,一切都开始工作(不需要更改事务处理)。

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