EntityManager 应该如何在一个很好的解耦的服务层和数据访问层中使用?

Posted

技术标签:

【中文标题】EntityManager 应该如何在一个很好的解耦的服务层和数据访问层中使用?【英文标题】:How should EntityManager be used in a nicely decoupled service layer and data access layer? 【发布时间】:2011-12-12 23:18:00 【问题描述】:

与我的另一个问题 Should raw Hibernate annotated POJO's be returned from the Data Access Layer, or Interfaces instead? 有点相关,我在创建良好解耦的层方面经验丰富,但不使用 Hibernate 或 J2EE/JPA。我一直在查看文档和教程,对如何以优雅的方式使用 EntityManger 感到困惑,因为它似乎负责事务(我想在我的服务层执行)和持久性方法(我想要保存在数据访问层中)。我应该在服务层创建它并将其注入数据访问层,还是有更好的方法?下面的伪java大致显示了我正在考虑做的事情。

编辑:我下面的伪代码基本上取自 hibernate JPA 教程并针对层分离进行了修改,并不反映正在开发该产品以在 EJB 容器(Glassfish)中运行。在您的回答中,请提供在 Glassfish 或等效程序中运行的代码的最佳实践和代码示例。

MyService


  setup()
  
       EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory( "Something" ); //is the String you pass in important?
       entityManager = entityManagerFactory.createEntityManager();
  

  myServiceMethod()
   
   entityManager.getTransaction().begin();
   MyDao.setEntityManager(entityManagerFactory); 
   MyDao.doSomething();
   MyDao.doSomethingElse();
   entityManager.getTransaction().commit();
   entityManager.close();
   
 

MyDao

   doSomething()
    
     entityManager.persist(...); //etc 
    


【问题讨论】:

我看到您没有使用 JTA - 是故意的吗?您正在手动创建实体管理器并使用资源级事务。您是在 Tomcat 还是 Java EE 服务器上? @PedroKowalski 我基本上是从 Hibernate 教程中窃取了该代码。该应用程序实际上将在 Glassfish 中运行。我将编辑问题以寻求帮助:) 【参考方案1】:

首先,是否应该使用 DAO 层是自 JPA 和许多人认为是 DAO 本身的 EntityManager 出现以来一直存在的争论。这个问题的答案取决于您正在开发的应用程序的类型,但在大多数情况下,您会希望:

使用 JPA 标准或自定义查询。在这种情况下,您可能不想将业务逻辑与查询创建混合在一起。这会导致方法过大,并且会违反single responsibility principle。 尽可能重用您的 JPA 代码。假设您创建了一个条件查询,该查询检索年龄在 40 到 65 岁之间且在公司工作超过 10 年的员工列表。您可能希望在服务层的其他地方重用这种类型的查询,如果是这种情况,将它放在服务中会使这项任务变得困难。

话虽这么说,如果您的应用程序中只有 CRUD 操作,并且您认为您可能不需要重用任何 JPA 代码,那么 DAO 层可能有点矫枉过正,因为它只会充当 EntityManager 的包装器,这听起来不对。

其次,我建议尽可能使用容器管理的事务。如果您使用的是像 TomEE 或 JBoss 这样的 EJB 容器,这将避免大量专用于以编程方式创建和管理事务的代码。

如果您使用的是 EJB 容器,则可以利用声明式事务管理。使用 DAO 的一个示例是将您的服务层组件创建为 EJB 和您的 DAO。

@Stateless
public class CustomerService 

    @EJB
    CustomerDao customerDao;

    public Long save(Customer customer) 

        // Business logic here
        return customerDao.save(customer);
    


@Stateless
public class CustomerDao 

    @PersistenceContext(unitName = "unit")
    EntityManager em;

    public Long save(Customer customer) 
        em.persist(customer);
        return customer.getId();
    

    public Customer readCustomer(Long id) 
            // Criteria query built here
    


在上面的示例中,默认事务配置是 REQUIRED,这意味着在调用者组件中没有事务时,EJB 将创建一个新事务。如果调用者已经创建了一个事务 (CustomerService),则被调用的组件 (CustomerDao) 将继承该事务。这可以使用@TransactionAttribute 注释进行自定义。

如果您不使用 EJB 容器,我认为您上面的示例可能是等效的。

已编辑:为简单起见,我在上面使用了无接口 EJB,但最好使用接口来制作它们,例如更可测试。

【讨论】:

这将在 GlassFish 中运行,因此我将编辑问题以明确说明这一点。您的示例代码假定一个 EJB 容器,是吗? 是的。上面的代码假设了一个 JEE 兼容的容器,Glassfish 实际上就是这样。 那么我会选择这个解决方案,所以 Gonzalo - 你有我的斧头......投票我的意思是:-)。您可以对其进行一些修改,以便为您的服务提供 CRUD 操作(即创建 GenericCRUDService)。然后,您的一些明显基于 CRUD 的服务将没有任何特定的 DAO,并且如果这些服务需要重用一些复杂的查询,那么这些可以在单独的类中定义,如 Gonzalo 所示,或视为 DDD 存储库。 【参考方案2】:

通常,您希望将任何持久性代码隔离到您的 DAO 层。所以服务层甚至不应该知道EntityManager。我认为如果 DAO 层返回带注释的 pojos 就可以了,因为它们仍然是 pojos。

关于事务管理,我建议你看Spring ORM。但是如果您选择不使用 Spring 或其他 AOP 解决方案,您始终可以通过您的 DAO 公开与事务相关的方法,以便从服务层调用它们。这样做会让你的生活更加艰难,但选择权在你...

【讨论】:

EntityManager 不是已经是 DAO 了吗?它为您提供了漂亮的 CRUD 界面,并且与数据库无关。 我很想使用 Spring,但不是这个项目的一个选项。将事务逻辑公开为 DAO 方法似乎是错误的——也许你可以用一个代码示例来澄清一下。【参考方案3】:

对于像 getItem()、getEmployee() 等简单的情况,最好将 entitymanager 直接注入到 Service 层使用的方法中,而不是调用用户 entitymanager 的 Dao(其中注入了实体管理器)方法的 Service 方法返回对象。这是矫枉过正,DAO 只是充当包装器。对于涉及查询和条件的复杂业务逻辑,让服务方法调用与数据库对话的 Dao。

【讨论】:

以上是关于EntityManager 应该如何在一个很好的解耦的服务层和数据访问层中使用?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Symfony 单命令应用程序中访问 EntityManager?

我是不是应该经常调用 EntityManager.clear() 以避免内存泄漏?

我应该多久创建一次 EntityManager?

如何模拟EntityManager?

什么被认为是一个很好的规范? Rspec初学者的例子

Entitymanager.flush() VS EntityManager.getTransaction().commit - 我应该更喜欢啥?