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

12
与我之前的问题有些相关 (Hibernate注释的POJO应该从数据访问层返回,还是应该使用接口?) ,我擅长创建漂亮的解耦层,但不使用Hibernate或J2EE/JPA。我一直在查找文档和教程,对如何优雅地使用EntityManager感到困惑,因为它似乎负责事务(我希望在服务层中执行)和持久化方法(我希望保留在数据访问层中)。我应该在服务层创建它并将其注入到数据访问层中,还是有更好的方法?下面的伪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 
    }

}

1
我看到你没有使用JTA - 这是有意为之吗?你手动创建实体管理器并使用资源级事务。你在使用Tomcat还是Java EE服务器? - Piotr Nowicki
@PedroKowalski 我基本上是从Hibernate教程中窃取了那段代码。这个应用程序实际上将在Glassfish中运行。我将编辑问题,以便请求对此的帮助 :) - Peter
3个回答

13

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

  • 使用JPA标准或自定义查询。在这种情况下,您可能不希望将业务逻辑与查询创建混合在一起。这会导致方法过长,并违反单一职责原则
  • 尽可能多地重用JPA代码。假设您创建了一个标准查询,检索年龄在40至65岁之间且在公司工作超过10年的员工列表。您可能希望在服务层的其他位置重用此类查询,如果是这种情况,则将其放在服务中将使此任务变得困难。

话虽如此,如果您的应用程序中只有CRUD操作,并且您认为可能不需要重用任何JPA代码,则DAO层可能过度包装EntityManager,这听起来不太对。

其次,我建议在可能的情况下使用容器管理的事务。如果您正在使用像TomEE或JBoss这样的EJB容器,这将避免大量的代码专门用于编程式地创建和管理事务。

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

@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容器,对吗? - Peter
是的。上面的代码假设使用符合JEE标准的容器,而Glassfish实际上就是这样的容器。 - Gonzalo Garcia Lasurtegui
1
那我会选择这个解决方案,所以Gonzalo - 你有我的支持票...我是说 :-). 你可以稍微修改一下,提供带有CRUD操作的服务(例如:创建GenericCRUDService)。然后你的一些明显基于CRUD的服务将不需要任何特定的DAO,如果这些服务需要重用某些复杂的查询,那么这些查询可以在单独的类中定义,就像Gonzalo所展示的那样,或者被视为DDD Repository。 - Piotr Nowicki

2
通常,您希望将任何持久性代码隔离到DAO层。因此,服务层甚至不应该知道EntityManager的存在。如果DAO层返回已注释的POJO,则我认为这是可以接受的,因为它们仍然是POJO。
对于事务管理,我建议您查看Spring ORM。但是,如果您选择不使用Spring或其他AOP解决方案,您始终可以通过DAO公开与事务相关的方法,以便从服务层调用它们。这样做会使您的生活更加困难,但选择权在您手中...

EntityManager不就是DAO吗?它提供了漂亮的CRUD接口,而且与数据库无关。 - Piotr Nowicki
我很想使用Spring,但这个项目不适用。将事务逻辑公开为DAO方法似乎是错误的 - 也许您可以通过代码示例来澄清。 - Peter

0
对于像getItem()、getEmployee()等简单情况,最好直接将entitymanager注入到Service层中的方法中,而不是Service方法调用Dao(其中entity manager被注入)方法,该方法使用entitymanager返回对象。这是过度设计,DAO只是一个包装器。对于涉及查询和条件的复杂业务逻辑,请让Service方法调用Dao与数据库交互。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接