Jsp教学中三层架构规划

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

在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=1

ROW_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=1

ROW_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作了轻量级的封装。





  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值