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

