软件项目实训及课程设计指导——如何正确地设计J2EE应用系统持久层中的各个组件结构及关系
1、了解J2EE应用系统数据持久层中的各个组件类的类型
在J2EE系统平台下的软件应用系统的数据持久层,一般都包含有如下的各个组件类:实体类、数据访问对象类(包括接口和对应的实现类)和数据连接类。如下示图为体现持久层中的各个组件类相互关系的示例图。
2、了解J2EE应用系统数据持久层中的各个组件类的类型和职责
(1)持久实体类(Persistant Object)
实体类一般实现对物理数据库系统中的一个具体的数据库表中的各个字段数据的封装,也就是该类的一个对象实例对应于物理数据库系统的数据库表中的一条记录;实体类中的各个成员属性与数据库表中的字段相互映射;在实体类中一般不提供功能方法。
1)实体类中的成员属性和物理数据库系统的数据库表中的字段是相互对应关系的映射,此种形式的实体类一般称为域实体类(Domain Entity)。
2)实体类中的成员属性也可以与物理数据库系统的数据库表中的字段结构不完全产生出相互对应关系的映射,此种形式的实体类称为用户自定义实体类(Custom Entity)。
持久实体类对象实例的生命周期和物理数据库系统连接的状态密切相关——物理数据库系统连接Connection对象被关闭以后,持久实体类对象实例也就不再存在。
作者注:
读者应该要区分持久实体类(PO)和业务实体类(VO,Value Object,简称为值对象)之间的差别,VO的本质是业务对象,并且只存活在系统的业务层中,主要提供给系统的业务逻辑组件类程序使用。
(2)数据访问逻辑组件(包括接口和对应的实现类)
数据访问逻辑组件是一个无状态的物理数据库系统访问功能操作类,调用之间不存在状态之间的传递。在结构和功能设计方面的主要目标如下:
首先,数据访问逻辑组件在结构设计方面要提供接口和对接口中定义的各个功能方法的实现类;
其次,数据访问逻辑组件在接口内定义的各个功能方法主要要处理以下的数据访问技术实现的细节和为上层的业务层组件提供对应的功能服务:
1)实现对目标数据库表中数据的"增、删、改和查"操作,这是其主要的功能实现的任务;
下图所示为银行账户管理系统中的账户DAO组件接口中的各个功能方法定义——这些功能方法主要是实现对银行账户信息数据库表中的数据进行各种形式的"增、删、改、查"操作。
2)正确处理数据访问的安全性和授权问题,避免关键性的数据被非法访问和修改;
3)正确处理事务处理问题,以保证对数据访问的一致性;
下图所示为在银行账户信息管理系统中的账户DAO组件的批处理删除batchDeleteAccountInfo功能方法中应用事务控制的程序代码片段图示。当然,也可以应用面向切面AOP编程中所倡导的分离"核心功能"和"辅助功能"程序代码的设计思想,将事务处理的程序代码单独封装为一个组件类。
4)执行数据分页查询和显示的逻辑,并优化数据访问逻辑的实现以获得最佳的数据访问性能;
下图所示为银行账户信息管理系统中分页显示账户信息的效果图,通过数据分页显示不仅可以提高数据访问的效率、而且也使得目标数据的显示页面更加简洁和清晰。
5)如果应用系统需要缓存以进一步提高数据访问的性能,则还需要为非事务性数据的查询实现缓存策略。
6)执行数据流加工和转换、对数据进行序列化等方面的操作。
(3)数据库连接功能实现类
为了在软件应用系统后台的物理数据库系统的数据源发生变化的情况下,尽可能地减少对数据访问对象功能类的代码的改动。应该在软件应用系统中提供一个数据库连接类以封装数据库类型及对物理数据库系统的连接方式的可能变化。
当然,为了提高软件应用系统中的数据库连接的效率,在设计方面一般都采用实现JDBC 2.0规范中的DataSource接口的数据库连接池提供对物理数据库系统的连接对象或者应用O/R Mapping类型的应用框架(如Hibernate框架、iBatis框架等)中所提供的数据连接组件,也可以应用第三方系统平台所提供的DataSource实现(如Apache DBCP组件提供的DataSource实现)。
下图所示为银行账户信息管理系统中利用Apache DBCP数据库连接池的实现组件为系统提供数据库连接功能类的程序代码片段局部示图截图——Apache DBCP是Apache Commons项目中的一个子项目,为J2EE技术平台下的应用系统提供一种与应用服务器无关的数据库连接池实现方案,有助于应用系统的移植和功能扩展。
3、合理地设计和实现软件应用系统数据持久层中的异常处理功能程序类
(1)合理地设计和实现数据持久层中的异常处理功能程序类
由于软件应用系统数据持久层会涉及大量的对物理数据库表中的数据的各种操作访问,而JDBC API中的许多功能方法都有异常抛出的声明。而且一旦数据访问组件DAO中的某个功能方法抛出异常后,相关程序的执行流程将要发生改变。下图所示为某个软件应用系统在运行过程中直接抛出异常错误提示信息、并在浏览器中显示输出的局部截图。
因此,软件应用系统的设计人员对异常的正确处理是软件应用系统设计和开发实现中极为重要的一个方面,对异常层次的合理划分不仅提高了程序代码的可维护性,而且也有利于开发过程中的程序调试、跟踪相关程序代码中的错误状况、并快速地定位出错误的代码位置。
(2)异常处理设计的基本原则
在处理软件应用系统中的异常时,要注意不能让最终的用户看到原始的Java异常信息。因此,软件应用系统的设计人员应该首先对原始的Java异常信息进行包装,然后向最终用户显示出容易理解的错误信息或者将英文的错误提示翻译为中文的错误提示,从而使得最终用户更清晰地了解目前软件应用系统的故障原因。
其次还应该将原始的Java异常信息记录到系统日志文件中,以帮助软件应用系统的系统管理员或者软件开发人员进一步查找错误的原因提供方便。下图所示为银行账户信息管理系统中,在AccountManageDAOJDBCImple程序类中的deleteOneAccountInfoPO方法中实现将原始的Java异常信息记录到系统日志文件中的程序代码片段截图。
最后在软件应用系统表示层的相关组件中捕获系统底层中各个功能组件所抛出的系统异常,然后显示出与特定的应用逻辑相关的错误提示文字。下图所示为银行账户信息管理系统中,在AccountInfoManageAction的J2EE MVC Struts2应用框架的Action类中捕获异常、并转发到目标JSP页面显示输出的程序代码片段截图。
4、有关数据访问操作功能实现类中的事务控制的设计问题
(1)不应该将事务控制代码直接写在系统的业务逻辑层中
事务控制代码到底是放在软件应用系统的持久层中的DAO组件中还是放在系统上层的数据访问服务层中的DAOService组件类中,这取决于系统中的数据访问服务的"粒度"!
如果系统中的数据访问服务只需要单个DAO组件中相关的功能方法,而且每个DAO组件都是基于对物理数据库系统中的单一数据库表操作,也就是系统中的数据访问服务的"粒度"比较小,此时则应该将事务控制代码放在DAO组件内。由于每个方法实现了一个特定的功能,也就是一个事务,那么直接在方法开始启动事务,方法结束提交事务就可以。
当然,如果系统中的数据访问服务的"粒度"比较大,某个业务处理功能(如转账)需要多个相关的DAO组件中的方法相互配合才能完成,也就是说需要多个DAO组件内的方法在一个事务中执行,则应该将事务控制代码放在系统服务层中的DAOService组件类程序中(需要将事务控制提到DAO组件外部)。因为,此时再将事务控制代码如果放在DAO组件类程序中,则无法进行单一的事务控制、并且每个DAO组件中的功能方法都是单独的一个数据库连接Connection对象,一旦该方法执行结束,对物理数据库系统连接的数据库连接Connection对象就会被关闭。
当然,不应该将事务控制代码直接写在软件应用系统的业务逻辑层中。如果采用这样的设计方案,将导致软件应用系统中的业务逻辑组件中包含有与业务无关的功能实现代码,同时也造成了软件应用系统中的业务层与持久层之间的紧密耦合关系。
此外,事务在设计方面除了要遵守事务所具有的原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)(简称为ACID)的要求以外,在事务划分的设计方面,也还应该要遵守如下的基本设计原则:使事务尽可能短小,因此保持事务尽可能短小的最好方法当然是在事务处理中不做任何不必要耗费时间的事,特别是不要在事务中间等待用户输入。
(2)应用面向切面AOP编程思想分离数据访问逻辑和事务控制逻辑的实现代码
由于事务控制逻辑的实现代码是与所采用的数据访问实现技术紧密相关的,如JDBC 数据库访问API、基于O/R Mapping技术的Hibernate框架和Spring应用框架、J2EE JTA分布式事务等相关的实现技术中都分别提供各自的事务控制实现方法和对应的事务控制API。
因此,为了提供软件应用系统中事务控制实现的可移植性,应该要应用面向切面AOP编程思想分离数据访问逻辑和事务控制逻辑的实现代码。
具体的功能程序的实现方式可以采用Java语言中的动态代理技术设计事务控制的通用代理类或者应用Spring框架中提供的声明形式的事务控制实现技术。下图所示是在银行账户信息管理系统中的对前面示图所示的事务控制实现代码进行重构后的实现代码,将事务控制实现代码放到一个动态代理的程序类中。
