Jsp教学中三层架构规划

来源:互联网 发布:郑大网络远程教育学院 编辑:程序博客网 时间:2024/06/06 03:39

<本博客的内容语言组织还要进一步精炼,修订后的示范代码稍后提交!>

在JavaWeb开发的教学中,我们通常都会带着学生用JSP+Servlet+JavaBean技术完成一个或多个小型管理信息系统。在教学中,为了加深学生对灵活的软件设计的理解,采用三层架构的方式搭建应用框架。在实际操作过程中,有3个难点的地方比较难理解。

1.各层的接口声明中返回什么值?

2.异常如何处理?

3.service层如何实现事务?

接下来,我们就以上三个问题逐一分析并提出解决方案。

注:也许有人认为这个没有必要,用struts、hibernate等框架来做,整个系统架构更清楚。但是《Java Web开发》这门课程是教给学生Servlet、JSP、JavaBean等技术。只有真正地掌握了这些技术,再用框架的时候,才能更深刻的理解框架带来的好处。另外,一些小的MIS系统,完全没有必要搞的那么复杂,一堆的配置文件或注解。

首先,我们简单来回顾三层架构方面的知识。虽然我们经常谈三层架构,实际上,在使用JSP进行分层开发的时候,通常是4层,也就是业务逻辑层分离出一个Serlvet层,主要负责获取用户提交数据,业务流程跳转等。具体如下图一所示。根据面向对象的一些基本思想:面向抽象编程(方便后期的维护和拓展)。

                                        图1 三层架构与四层架构

每一层都公开了一些接口给调用它的上层,但是对上层隐藏了自身的实现。一般的做法就是每一层提供一个接口,然后提供一个实现了该接口的类。接下来我们通过一个简单的案例,来探讨上面提出的三个问题。为了尽可能的精简代码,此处设计了两张最简单的表。具体MySQL 的DDL如下:

CREATE DATABASE `three` ;USE `three`;DROP TABLE IF EXISTS `log`;CREATE TABLE `log` (  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,  `info` varchar(6) NOT NULL,  PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 CHECKSUM=1 DELAY_KEY_WRITE=1ROW_FORMAT=DYNAMIC;DROP TABLE IF EXISTS `student`;CREATE TABLE `student` (  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,  `name` varchar(6) NOT NULL,  PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8 CHECKSUM=1 DELAY_KEY_WRITE=1ROW_FORMAT=DYNAMIC;


实体类的代码此处就不贴出了,我们首先来看第一个问题,接口层中方法的返回值。在网络上的很多讲三层架构的案例中,每个人的返回值都不一样。比如有的add方法返回一个整数,有的就是一个void。数据访问层,只关心如何操作数据库。因此个人觉得,用void更合适一些。至于添加成功与否,我们用异常来解决。此处值得商榷,本例中采用返回void的方式。至于业务逻辑层的接口,比如register方法,实际上就相当于是要往数据库中添加一条记录。个人理解,既然是业务,必然会有成功和失败,应该是返回boolean型的变量,但网络上大多数案例都是返回void。本例中也简化处理,一律返回void。

第二个问题就是异常的处理,按照异常的使用原则,异常能处理就处理,不能处理就抛出。在DAO层中,不可避免的要处理SQLException。如果在DAO中直接生命抛出异常,如下错误示范:

public void LogDao{    public void addLog(Log log) throws SQLException;    ......}
这样的话接口被污染了,如果我们的项目采用其他框架如Hibernate来持久化,Hibernate中数据库操作产生的异常类不是SQLException。这样的话,这个接口的设计就没有意义了,需要调整接口,进而影响service层对它的调用。失去了面向对象灵活的特性了。因此此处,我们的接口不应该抛出异常,而是采用自定义的异常RuntimeException类。

此处我们定义一个

public class MyRunException extends RuntimeException {    public MyRunException(String message) {        super(message);    }    public MyRunException() {    }    }
在DAO中,碰到SQL异常的时候,就直接捕获之后,抛出一个MyRunException异常的实例。由于该异常是运行时异常,不用在方法体中显式抛出。因此我们的接口可以非常干净。那么DAO中的方法就可以如下写:

public class LogDao {    ...    public void addLog(Log log){        ...        try {            ...        } catch (SQLException ex) {            throw new MyRunException();        }    }}

第三个问题就是service的事务处理。JDBC支持事务处理,我们的案例中MySQL也支持事务,为什么还要讨论了。在实际的业务逻辑中,往往设计对多表的操作。通常在DAO中,每一个DAO针对的是一个数据表的CRUD。这样的话,我们就需要将一个Connection传递给多个DAO,然后在service层commit业务。如此一来,我们的service的代码就类似这样的操作:

public void  register(Student student){Connection conn = DBHelp.getConn();StudentDao sDao = new StudentDao(conn);LogDao lDao = new LogDao(conn);conn.setAutoCommit(false);try{sDao.add(student);lDao.add(new Log(...));}catch(MyRunException e){try{conn.commmit();}catch(SQLException e){.....}}}
如此一来,我们的service还是要与SQLException耦合在一起。如何解决这个问题呢?我们引入自己的类包装一下Connection,具体代码如下:

DBExecute

public class DBExecute {    private Connection conn;    public DBExecute(Connection conn) {        this.conn = conn;    }    public void setAutoCommit(boolean flag){        try {            conn.setAutoCommit(flag);        } catch (SQLException ex) {            throw new UserSQLException(ex.getMessage());        }    }    public void commit(){        try {            conn.commit();        } catch (SQLException ex) {            throw new UserSQLException(ex.getMessage());        }    }    public Connection getConn() {        return conn;    }}
我们的数据库的操作,数据库的连接都封装DBExecute中。我们的service将彻底地从JDBC这些类和异常中分离出来。

修改后的Dao实现如下所示:

....public class LogDaoJdbcImpl implements LogDao {    private DBExecute execute;    public LogDaoJdbcImpl(DBExecute execute) {        this.execute = execute;    }    public void addLog(Log log){        String sql = "insert into log ( info ) values( ? ) ";        PreparedStatement ps;        try {            ps = execute.getConn().prepareStatement(sql);            ps.setString(1, log.getInfo());            ps.execute();        } catch (SQLException ex) {            throw new UserSQLException();        }    }}
到此为止,我们解决了以上提出的三个问题。

这些操作只是介绍了基本的思想,真正要进行良好的,可以通用的类,我们的DBExecute还要封装更多的操作。就像Apache的DBUtil一样,对JDBC作了轻量级的封装。





原创粉丝点击