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

gre*_*rej 5 java spring jpa entitymanager spring-orm

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

用户:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

用户组:

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

}
Run Code Online (Sandbox Code Playgroud)

现在我为每个实体(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);
   }
}
Run Code Online (Sandbox Code Playgroud)

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

UserGroup ug = userGroupDao.findById(1);

User u = new User();
u.setName("john");
u.setGroup(ug);

userDao.save(u);
Run Code Online (Sandbox Code Playgroud)

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

对象引用未保存的瞬态实例 - 在刷新之前保存瞬态实例: xyzmodel.User.group -> xyzmodel.UserGroup

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

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

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

当使用相同的实体管理器进行 find 和 persist 时,描述的情况有效。这指向一个问题:

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

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

rol*_*lve 3

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

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

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