DAO和Service层(JPA/Hibernate + Spring)

62

我正在设计一个基于JPA/Hibernate、Spring和Wicket的新应用程序。然而,DAO和Service层之间的区别对我来说并不是很清楚。根据维基百科,DAO是一个对象,它提供了一个面向某种类型数据库或持久性机制的抽象接口,提供一些特定的操作而不暴露数据库的细节。

我在想,一个DAO是否可以包含那些与数据访问无关却更容易使用查询执行的方法呢?比如“获取在某一组机场运营的所有航空公司的列表”?这听起来更像是一个服务层的方法,但我不确定在服务层使用JPA EntityManager是否是一个良好实践的例子?

5个回答

53
DAO应该提供对单个相关数据源的访问,并根据业务模型的复杂程度返回完整的业务对象或简单的数据对象。无论哪种方式,DAO方法都应该与数据库相对应。
服务可以提供更高级别的接口,不仅可以处理业务对象,还可以首先访问它们。如果我从服务获取业务对象,那么该对象可能来自不同的数据库(和不同的DAO),它可能带有由HTTP请求生成的信息。它可能具有某些业务逻辑,将几个数据对象转换为单个健壮的业务对象。
通常,我创建DAO时会考虑到任何使用该数据库或一组业务相关数据的人都将使用它,除触发器、函数和存储过程之外,它实际上是数据库中最低级别的代码。
对于大多数情况而言,你不应该在DAO中包含不必要的数据访问方法,而是应该在服务层中进行更复杂的业务逻辑操作,组装来自不同查询的数据等。然而,如果你关心处理速度,服务层可能会将某个操作委派给DAO,即使它破坏了模型的完整性,这类似于C++程序员编写汇编代码加速某些操作。
如果您要在服务中使用实体管理器,则认为实体管理器就是您的DAO,因为它确实是。如果您需要删除某些冗余的查询构建,则不要在服务类中执行此操作,而是将其提取到使用实体管理器的类中,并将其作为DAO。如果您的用例非常简单,则可以完全跳过服务层,并在控制器中使用实体管理器或DAO,因为您的所有服务都将传递调用`getAirplaneById()`给DAO的`findAirplaneById()`。
更新 - 为了澄清下面的讨论,大多数情况下,在服务中使用实体管理器可能不是最佳决策,原因如评论中所述。但是,我认为,在以下情况下使用实体管理器是完全合理的:
1.服务需要与不同数据集交互 2.至少一个数据集已经有DAO 3.服务类驻留在需要一些持久性的模块中,该持久性足够简单,不需要自己的DAO
例如。
//some system that contains all our customers information
class PersonDao {
   findPersonBySSN( long ssn )
}

//some other system where we store pets
class PetDao {
   findPetsByAreaCode()
   findCatByFullName()
}

//some web portal your building has this service
class OurPortalPetLostAndFoundService {

   notifyOfLocalLostPets( Person p ) {
      Location l = ourPortalEntityManager.findSingle( PortalUser.class, p.getSSN() )
        .getOptions().getLocation();
      ... use other DAO's to get contact information and pets...
   }
}

1
谢谢您提供如此详细的答案。我想知道:在服务层中既拥有DAO集合又使用EntityManager是否可行? - John Manak
7
在服务层同时拥有DAO集合并使用EntityManager是否可行?这样做有什么意义?如果你在DAO层的目标是抽象出持久化技术的选择,那么在服务层使用JPA就违背了这个目标。如果这种抽象并不是你的目标,那么你就不需要通过虚构一个分离的层级来跳跃障碍。 - matt b
@John Manak - 关于实体与DAO的1对1观点,我不敢苟同。虽然传统智慧遵循您的方法论,但我想指出DRY(不要重复自己)原则,在实践中,您会有许多类来执行简单的CRUD操作,而这可以通过一个简单的通用方法轻松处理。我认为类的泛滥会分散注意力。当您遵循单个DAO用于数据库时,您将开始看到什么是冗余的,并且可以有机地重构DAO。 - walnutmon
非常感谢您的评论,对我来说越来越清晰了。我真的很喜欢单个DAO的想法,但我仍然不确定如何在其中实现通用的CRUD方法。您能否给我展示一个例子或分享一个链接? - John Manak
问题 - 在服务层中进行事务管理不是必须使用EntityManager吗?您希望您的服务方法控制事务边界 - - Peter
显示剩余5条评论

15

有一点是确定的:如果您在服务层使用 EntityManager,则不需要 dao 层(只有一层应该知道实现细节)。除此之外,有不同的观点:

  • 有人说 EntityManager 公开了所需的所有 dao 功能,因此他们在服务层中注入 EntityManager。
  • 其他人则拥有传统的基于接口的 dao 层(因此服务层不会与实现细节绑定)。

当涉及到关注点分离时,第二种方法更加优雅,它还将使从一个持久化技术切换到另一个更加容易(您只需使用新技术重新实现 dao 接口),但如果您知道没有什么会改变,那么第一种方法更容易。

我建议在小项目中,使用 JPA 在服务层上,但在大型项目中使用专用的 DAO 层。


在你的例子中,实体管理器就是你的DAO。 - walnutmon
1
在大型项目中,服务层应该是持久化机制无关的。 - Bozho
+1。同意您的评论,第二种方法提供了更清晰的关注点分离。使用第一种方法,你会在服务层看到很多类似这样的代码: List<Event> result = entityManager.createQuery( "from Event", Event.class ).getResultList(); 现在,如果相同的事情被隐藏在DAO层后面,服务层只需要期望一个事件对象列表,而不必处理获取所需对象列表的“how”部分。 - Suketu Bhuta

5
这篇由Adam Bien撰写的文章可能会有所帮助。

4

传统上,您需要编写接口来定义服务层和数据层之间的合同。然后编写实现,这些是您的DAO。

回到您的示例。假设机场和航空公司之间的关系是多对多的,其中包含机场ID和航空公司ID的表格,您可能会拥有一个接口;

public interface AirportDAO
{
   public List<Airline> getAirlinesOperatingFrom(Set<Airport> airports);
}

...并且您可能需要提供一个Hibernate实现;

public class HibernateAirportDAO implements AirportDAO
{
   public List<Airline> getAirlinesOperatingFrom(Set<Airport> airports)
   {
      //implementation here using EntityManager.
   }
}

你也可以考虑在你的航空公司实体上创建一个列表,并使用@ManyToMany JPA注释定义关系。这将消除完全需要此特定DAO方法的必要性。
你还可以研究抽象工厂模式来编写DAO工厂。例如;
public abstract class DAOFactory
{
   private static HibernateDAOFactory hdf = new HibernateDAOFactory();

   public abstract AirportDAO getAirlineDAO();

   public static DAOFactory getFactory()
   {
      //return a concrete implementation here, which implementation you
      //return might depend on some application configuration settings.
   }
}

public class HibernateDAOFactory extends DAOFactory
{
   private static EntityManagerFactory emFactory = Persistence.createEntityManagerFactory("myPersistenceUnit");

   public static EntityManager getEM()
   {
      return emFactory.createEntityManager();
   }

   public AirportDAO getAirportDAO()
   {
      return new HibernateAirportDAO();
   }
}

这种模式允许您的HibernateDAOFactory持有一个单独的EMF并为每个DAO实例提供EM。如果您不想使用工厂方式,则Spring非常擅长处理DAO实例,具有依赖注入功能。
注:澄清了一些假设。

是的,但这不是将业务/服务和数据访问层混合在一起吗?我特别指的是getAirlinesOperatingFrom()。这是好的实践吗?这是我在这个领域的第一个项目,所以我不太确定。 - John Manak
1
在Spring场景中,这种工厂方法并没有意义。 - Sean Patrick Floyd
@seanizer 是的,使用Spring框架,OP可能希望将DAO配置为应用程序上下文的一部分并进行注入。 - Qwerky
@John Manak 我假设航空公司和机场之间的关系在数据中,例如使用包含airline_id和airport_id的表的JPA @ManyToMany。如果关系不在数据中(例如必须调用Web服务),则此方法不应该在DAO中。 - Qwerky

0

Dao是数据访问对象。它在数据库上存储/更新/选择实体。实体管理器对象用于此目的(至少在open jpa中)。您还可以使用此实体管理器运行查询。它不是SQL而是JPQL(Java持久性查询语言)。

简单示例:

emf = Persistence.createEntityManagerFactory("localDB");
em = emf.createEntityManager();

Query q = em.createQuery("select u from Users as u where u.username = :username", Users.class);
q.setParameter("username", username);

List<Users> results = q.getResultList();

em.close();
emf.close();

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