使用对其他对象的引用来持久化对象

时间:2010-11-25 15:52:57

标签: design-patterns orm architecture dao


我正在开发一个带有Web界面的简单CRUD应用程序。我在持久层中使用通用DAO模式。我的通用DAO的界面如下所示:

public interface GenericDAO<T> {

    void create( T entity ) throws CannotPersistException;
    T findById( Long id ) throws NotFoundException;
    Collection<T> findAll();
    void update( T entity ) throws CannotPersistException, NotFoundException;
    void delete( Long id ) throws CannotPersistException, NotFoundException;
}

此接口使用JPA实现,但我也有内存实现,主要用于测试目的。 我在持久层中没有任何其他类,我只是为每个域类创建一个这个DAO的实例。

我面临的问题是如何正确地持久化对象,该对象引用了还需要持久化的其他对象。 我知道在JPA中级联可以解决这个问题,而不需要我在我的持久层中添加新类。然而,这会让我依赖于特定的实现(并且还会破坏我的内存中的DAO实现) )。

正确地知道,我正在使用服务层来管理DAO类,我通过使用多个DAO来解决问题。我将展示一个例子:

public class PlayerServiceImpl implements PlayerService {

    private GenericDAO<Player> playerDAO;
    private GenericDAO<Club> clubDAO;

    //constructor ommited

    public Player addNewPlayer( Player player, Long clubId ) throws NotFoundException,
            CannotPersistException {
        Club club = clubDAO.findById( clubId );
        player.setClub( club );
        playerDAO.create( player );
        club.addPlayer( player );
        clubDAO.update( club );
        return player;
    }

    //... other methods

}

这种方法的第一个缺点是它使我的域模型变得贫乏 - 大多数方法只是getter和setter,大多数业务逻辑都在服务层。

我在测试应用程序时发现了另一个缺点 - 我无法真正单独测试GenericDao类,如果我想使用其对象字段填充了Player对象的Club对象。
我需要先保留Player对象,以便将其添加到Club对象中,以便将其传递给正在测试的GenericDao。

我一直在寻找Fowler的PoEAA书籍来寻求解决方案,但我对Hibernate这样的O / R框架符合他描述的模式感到困惑。

4 个答案:

答案 0 :(得分:2)

使用内存中的数据源

看起来您最好使用内存中的DataSource,而不是重新发明JPA级联的解决方法。我建议您考虑配置测试环境,以允许您选择以内存中HSQLDB实例为目标的不同DataSource。然后,您可以注释您的域对象以级联并测试您的内容。这是一个演示简单配置的Spring上下文:

  <bean id="testDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    <property name="driverClass" value="org.hsqldb.jdbcDriver"/>
    <property name="jdbcUrl" value="jdbc:hsqldb:mem:test"/>
    <property name="user" value="sa"/>
    <property name="password" value=""/>
  </bean>

您的测试代码可以有一个Factory,它可以创建您希望测试的某些场景(使用瞬态域对象的集合,直到它们到达将填充其主键字段的DAO)。由于您可能会发现自己在测试数据集中添加了许多可能的重复项,因此应考虑在域对象中添加UUID字段,以便在主键为空时区分它们。通常,这只是一个延迟加载的getUUID()方法,它与其他字段一起保存。您甚至可以考虑将其用作主键,因为根据定义,碰撞的可能性是无穷小的。

此外,我将通过同时具有JDBC连接和DAO JPA连接来进行测试。您可以查看数据库的状态,以便通过JDBC连接进行断言,并通过DAO连接进行更新。

贫血域对象不是反模式

根据您的域建模策略,贫血域对象没有特别的错误。例如,您可以拥有一堆细粒度的业务对象,这些对象根据业务规则对域对象执行状态更改操作。基本上,您的服务层提供应用程序事务,您将拥有工作单元/命令/ DTO样式方法。

答案 1 :(得分:1)

在C#中,我可能会考虑创建一个属性来告诉dao是否应该将属性视为父级(“child”)的一部分。我知道Java中有类似的东西,但我不记得它是如何调用的。

抱歉,这是C#:

class Club
{
  [ChildEntity]
  List<Player> Players { get; private set; }
}

需要一些反思来找到所有孩子及其孩子。如果您不喜欢这样,您可以考虑编写一个返回子项的接口:

class Club : IComplexEntity
{
  List<Player> Players { get; private set; }

  public IEnumerable<object> EntityChildren { get { return Players; } }
}

答案 2 :(得分:1)

  

我面临的问题是如何   适当地持有一个有的对象   也引用其他对象   需要坚持下去。我知道那个级联   在JPA中可以解决这个问题   要求我为我的课程添加新课程   持久层。 然而,那会   让我依赖于具体的   实施(也会破坏   我的内存DAO实现。)

您确实意识到JPA是持久性的Java标准,它不是实际的实现。要使用JPA,您仍然需要PersistenceProvider(实现),但您可以在多个实现(Toplink,Hibernate等)之间进行选择。

我认为你应该在这里使用JPA Cascade。

答案 3 :(得分:0)

您有一个泛型类型(DOA),但您需要实现的行为不是通用的。这就像试图在一个圆孔中放一个方形钉:它永远不会起作用。

一些可能的方法:

要具体:如果是我,我会使用定义特定内容的类型:Club会(只考虑可能的示例)addressDJ会有Genre / Style

是的,我最终会选择很多课程,但是每节课都会有一份工作,并且会做得很好。因为他们是分开的,所以他们会与变革分离 - 这在很大程度上是你现在遇到的问题。

使用继承:如果您的所有类都有这些基本的共同点,那么您可以将它作为基础使用,并分别处理其余的吗?

使用多个界面:有点像固有但不同。您保留现有的处理基础的界面;然后,您可以为其他行为定义其他接口。当一个对象进入时,需要对其执行某些操作,评估它实现的接口并依次处理它们。您还可以选择更改现有界面以更好地支持此方法 - 如果这有意义的话。

您可以混合使用这些选项或其中的一些选项。