DAO Manager
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();
它有点复杂,但是一旦我们掌握了它,它实际上就没有那么困难了。它解决了事务划分的问题,并且在需要时才真正获得数据库连接。