DAO Manager

时间:2020-01-09 10:35:42  来源:igfitidea点击:

DAO管理器是针对文本DAO设计问题中未解决的问题的解决方案:连接和事务作用域的分界仍然从DAO层泄漏到业务层/域层/服务层(或者我们拥有的任何层)中在DAO层之上)。另外,我们在域逻辑中有很多丑陋的异常处理,我们必须在域逻辑使用DAO的任何地方重复进行。

DaoManager

为了解决前面提到的问题,我们可以将一些代码移到" DaoManager"类中。现在不再单独实例化每个DAO,而是仅实例化DaoManager

从" DaoManager"中,我将可以访问DAO层中的每个DAO。当然,每个DAO的创建都是惰性的,以避免实例化过多的DAO。将使用连接作为实例成员创建DaoManager。延迟创建DAO时,此连接将传递给它。这样,从特定的DaoManager访问的所有DAO都使用相同的连接。这解决了标记连接作用域开始的问题。现在,DaoManager的实例化对此进行了标记。

这是DaoManager的草图:

public class DaoManager{
  protected Connection connection = null;
  protected PersonDao  personDao  = null;

  public DaoManager(Connection connection){
    this.connection = connection;
  }

  public PersonDao getPersonDao(){
    if(this.personDao == null){
      this.personDao = new PersonDao(this.connection);
    }
    return this.personDao;
  }

}

在此草图中,仅可获得一个DAO,即" PersonDao",但我们可以在同一模型中轻松添加更多DAO。

注意,即使getPersonDao()方法返回一种单例,它也不会同步。 " DaoManager"不适用于线程之间的共享,因此不会插入任何同步。不过,这样做将非常容易。

DAO Manager连接范围

使用上面绘制的DaoManager看起来像这样:

DaoManager daoManager = daoFactory.createDaoManager();

Person person = daoManager.getPersonDao().readPerson(666);

请注意,在此示例中,如何关闭连接。与其在DaoManager类中实现close()方法,不如添加一个名为executeAndClose()的模板方法。这是该方法的草图,其中没有异常处理:

public class DaoManager{
  ...

  public Object executeAndClose(DaoCommand command){
    try{
      return command.execute(this);
    } finally {
      this.connection.close();
    }
  }

}
public interface DaoCommand {
  public Object execute(DaoManager daoManager);
}

使用该方法将如下所示:

DaoManager daoManager = daoFactory.createDaoManager();

Person person = (Person)
    daoManager.executeAndClose(new DaoCommand(){

      public Object execute(DaoManager manager){
        return manager.getPersonDao().readPerson(666);
      }

    });

DaoCommand实例调用executeAndClose()方法。然后,此实例将其" execute()"方法以" DaoManager"本身作为参数来调用。当execute()返回时,executeAndClose()关闭连接。现在,连接范围由方法调用executeAndClose()的范围标记。任何与" connection.close()"调用相关的异常处理都可以隐藏在" executeAndClose()"方法内部,并在整个应用程序中重复使用。

读取" Person"可能看起来像很多代码,但是大多数代码可以由IDE的代码完成生成。而且,一旦我们习惯了阅读此类连接作用域模板调用,它们就不难阅读了。

DAO经理交易范围

我们可以添加类似于事务处理管理的execute.Close()方法的transaction()方法。这是这种方法的示意图:

public class DaoManager{
  ...

  public Object transaction(DaoCommand command){
    try{
        this.connection.setAutoCommit(false);
        Object returnValue = command.execute(this);
        this.connection.commit();
        return returnValue;
    } catch(Exception e){
      this.connection.rollback();
      throw e; //or wrap it before rethrowing it
    } finally {
      this.connection.setAutoCommit(true);
    }
  }
}

此处勾画的方法不会保留try-catch-finally块中引发的所有异常。例如,如果commit()rollback()都抛出异常,则不能正确保留或者处理这两个异常。这样做将需要更多代码。不过,此处为了清楚起见,省略了此内容。

使用transaction()方法类似于使用executeAndClose()方法:

DaoManager daoManager = daoFactory.createDaoManager();

daoManager.transaction(new DaoCommand(){

  public Object execute(DaoManager manager){
    Person person = manager.getPersonDao().readPerson(666);
    person.setLastName("Nick");
    manager.getPersonDao().updatePerson(person);
  }

});

本示例读取一个" Person"实例,更改姓氏并再次更新" Person"实例。这是在事务内完成的。

请注意,该示例不会关闭连接。这可以通过将对" transaction()"的调用包装在对" executeAndClose()"的调用内来完成。这是DaoManager中的一种方法:

public class DaoManager{
  ...

  public Object transactionAndClose(DaoCommand command){
    executeAndClose(new DaoCommand(){
      public Object execute(DaoManager manager){
        manager.transaction(command);
      }
    });
  }
}

如我们所见,DaoManager类可以解决标记连接寿命和事务边界的问题,并自动打开和关闭连接以及提交/回滚事务。它还很好地集中了事务管理所需的异常处理。

DAO的单点访问

" DaoManager"还解决了大型应用程序中的另一个问题:找出应用程序中已经存在哪些DAO的问题。所有的DAO都可以从DaoManager中获得。这使得在大型项目中查找和重用现有的DAO变得容易得多。我们是否真正想要这样做是另一个问题。在大型应用程序中重复使用DAO方法会创建依赖关系,而这些依赖关系可能很难跟踪。如果更改DAO方法,则可能会影响应用程序中其他未曾预料到的代码。

隐藏范围边界代码

截至目前," DaoManager"在域逻辑中可见。回顾一下,这是从域逻辑使用DaoManager的样子:

DaoManager daoManager = daoFactory.createDaoManager();

daoManager.transaction(new DaoCommand(){

  public Object execute(DaoManager manager){
   
       Person person = manager.getPersonDao().readPerson(666);
       person.setLastName("Nick");
       manager.getPersonDao().updatePerson(person);
   
  }
});

请注意,真正让我们感兴趣的只是黑体代码。剩下的只是事务划分代码。如果我们像我,就会发现有些"嘈杂"。幸运的是,可以隐藏此代码。

在许多应用程序中,域逻辑被实现为某种事件侦听器。在桌面应用程序中,域逻辑通常由用户事件(键盘/鼠标)激活。在Web应用程序中,域逻辑通常由HTTP请求激活。如果我们实现了一个扩展所有事件侦听器(或者操作,或者框架称为它们的任何对象)的基本事件侦听器类,则可以将DaoManager代码放入该类(或者基类的子类)中。这是此类的示例:

public class PersistenceActionBase {

  protected DaoManager daoManager = null;

  public PersistenceActionBase(DaoManager manager){
    this.daoManager = manager;
  }

  public void doAction() {
    this.daoManager.transactionAndClose(new DaoCommand(){
      public Object execute(DaoManager manager){
        doPersistenceAction(manager);
      }
    });
  }

  protected void doPersistenceAction(DaoManager manager){
    //override this method in subclasses.
  }
}

这是一个子类的示例,该子类执行本节第一个代码框中以粗体标记的持久性代码:

public MyPersistenceAction extends PersistenceActionBase {

  public MyPersistenceAction(DaoManager manager){
    super(manager);
  }

  public void doPersistenceAction(DaoManager manager){
    
        Person person = manager.getPersonDao().readPerson(666);
        person.setLastName("Nick");
        manager.getPersonDao().updatePerson(person);
    
  }

}

现在,域逻辑类仅包含实际上有趣的持久性代码。

实现这样的持久性动作基类可能并不总是可行或者可行的。这取决于动作中发生的任何其他情况。但是我们仍然可以让自己从这个想法中获得启发,并在想法对我们有用的时候加以保存。

延迟打开连接

在大多数应用程序中,我们将从连接池获取数据库连接。在这种情况下,我们保持连接的时间不应超过必要的时间。保持连接的时间越长,其他线程随后可能需要等待更长的时间才能获得该连接。换句话说,我们应该尽可能晚地打开连接,并尽快将其关闭(将其返回到池中)。

如上一节所述,当我们隐藏连接和事务作用域边界时,实际上并没有尽可能晚地打开连接。当创建DaoManager时(即创建持久性动作时),我们正在打开连接。

通常,一个操作在访问数据库之前需要做一些验证。将验证代码放入doPersistenceAction()方法中是很诱人的。但是请记住,实例化" DaoManager"时已经打开了连接,该连接发生在调用" doPersistenceAction()"方法之前。在第一次需要使用连接之前,我们不应该打开连接,而在验证完成之前,不要打开连接。此外,如果验证失败并中止doPersistenceAction()方法调用,则说明我们已经打开连接而从未使用过。当然,它会自动再次关闭,但是仍然浪费打开和关闭连接(无论是否已建立连接)的麻烦。

为了避免在真正需要之前打开连接,可以更改DaoManager。代替在实例化时注入连接,我们将注入DataSource或者类似的构造。如果我们使用的是持久性API,则将注入我们从中获得连接或者其等效项的任何类(Hibernate中的会话,Butterfly Persistence中的IDaos)。这是一个草图:

public class DaoManager{
  protected DataSource dataSource = null;
  protected Connection connection = null;

  protected PersonDao  personDao  = null;

  public DaoManager(DataSource dataSource){
    this.dataSource = dataSource;
  }

  public PersonDao getPersonDaoTx(){
    if(this.personDao == null){
      this.personDao = new PersonDao(getTxConnection());
    }
    return this.personDao;
  }

  protected Connection getConnection(){
    if(this.connection == null){
      this.connection = dataSource.getConnection();
    }
  }

  protected Connection getTxConnection(){
    getConnection().setAutoCommit(false);
  }

  public Object transaction(DaoCommand command){
    try{
        Object returnValue = command.execute(this);
        getConnection().commit();
        return returnValue;
    } catch(Exception e){
      getConnection().rollback();
      throw e; //or wrap it before rethrowing it
    } finally {
      getConnection().setAutoCommit(true);
    }
  }

  public Object executeAndClose(DaoCommand command){
    try{
      return command.execute(this);
    } finally {
      getConnction().close();
    }
  }

  public Object transactionAndClose(DaoCommand command){
    executeAndClose(new DaoCommand(){
      public Object execute(DaoManager manager){
        manager.transaction(command);
      }
    });
  }
}

注意" transaction()"方法是如何稍作更改的。它不再调用connection.setAutoCommit(false)。现在,这是通过getTxConnection()方法完成的。这样做是为了避免在调用DaoCommand.execute()方法之前获得连接,从而尽可能推迟获得连接。在" DaoCommand.execute()"方法内部,将调用" getPersonDaoTx()",然后将调用" getConnectionTx()",然后在那时将调用" connection.setAutoCommit(false)"。

这是调用顺序的示意图:

domain layer --> daoFactory.createDaoManager()
domain layer --> daoManager.transactionAndClose()
 daoManager.transactionAndClose() --> daoManager.executeAndClose()
  daoManager.executeAndClose() --> daoManager.transaction()
   daoManager.transaction() --> command.execute()
    command.execute() --> validation code - Jan about execute() here
    command.execute() --> daoManager.getPersonDaoTx()
     daoManager.getPersonDaoTx() --> daoManager.getTxConnection();
      daoManager.getTxConnection() --> daoManager.getConnection();
       daoManager.getConnection() --> check if a connection exists.
                                      If yes, return it. If no, open new.
      daoManager.getTxConnection() --> connection.setAutoCommit(false);
   daoManager.transaction() --> daoManager.getConnection().commit()
  daoManager.executeAndClose() --> daoManager.getConnection().close();

它有点复杂,但是一旦我们掌握了它,它实际上就没有那么困难了。它解决了事务划分的问题,并且在需要时才真正获得数据库连接。