javaWeb学习记录:BaseServlet 与 service事务

本文根据崔希凡老师的讲课视频和笔记整理而成

1. BaseServlet

分析

通常,写一个项目可能会出现N多个Servlet,而且一般一个Servlet只有一个方法(doGet或doPost),如果项目大一些,那么Servlet的数量就会很惊人。为了避免Servlet的“膨胀”,我们写一个BaseServlet。它的作用是让一个Servlet可以处理多种不同的请求。不同的请求调用Servlet的不同方法。我们写好了BaseServlet后,让其他Servlet继承BaseServlet,例如CustomerServlet继承BaseServlet,然后在CustomerServlet中提供add()、update()、delete()等方法,每个方法对应不同的请求。并且每个方法放回一个字符串,指出它重定向或转发请求的路径,BaseServlet获得这个路径,再帮助子类转发请求或重定向到特定的页面。
见下图:
这里写图片描述


我们知道,Servlet中处理请求的方法是service()方法,这说明我们需要让service()方法去调用其他方法。例如调用add()、mod()、delele()、findAll()等方法!具体调用哪个方法需要在请求中给出方法名称!然后service()方法通过方法名称来调用指定的方法。
无论是点击超链接,还是提交表单,请求中必须要有method参数,这个参数的值就是要请求的方法名称,这样BaseServlet的service()才能通过方法名称来调用目标方法。例如某个链接如下:
<a href=”/xxx/CustomerServlet?method=add”>添加客户”</a>
见下图:
这里写图片描述

BaseServlet完整代码

import java.io.IOException;
import java.lang.reflect.Method;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public abstract class BaseServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    public void service(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        /*
         * 1. 获取参数,用来识别用户想请求的方法
         * 2. 然后判断是否哪一个方法,是哪一个我们就调用哪一个
         */
        String methodName = req.getParameter("method");

        if(methodName == null || methodName.trim().isEmpty()) {
            throw new RuntimeException("您没有传递method参数!无法确定您想要调用的方法!");
        }

        /*
         * 1. 得到方法名,通过方法名再得到Method类的对象!
         *   * 需要得到Class,然后调用它的方法进行查询!得到Method
         *   * 我们要查询的是当前类的方法,所以我们需要得到当前类的Class
         */
        Class<? extends BaseServlet> c = this.getClass();//得到当前类的class对象
        Method method = null;
        try {
            method = c.getMethod(methodName, 
                    HttpServletRequest.class, HttpServletResponse.class);
        } catch (Exception e) {
            throw new RuntimeException("您要调用的方法:" + methodName + "(HttpServletRequest,HttpServletResponse),它不存在!");
        }

        /*
         * 调用method表示的方法
         */
        try {
            String result = (String)method.invoke(this, req, resp);
            /*
             * 获取请求处理方法执行后返回的字符串,它表示转发或重定向的路径!
             * 如果用户返回的是字符串为null,或为"",那么什么也不做!
             */
            if(result == null || result.trim().isEmpty()) {
                return;
            }
            /*
             * 查看返回的字符串中是否包含冒号,如果没有,表示转发
             * 如果有,使用冒号分割字符串,得到前缀和后缀!
             * 其中前缀如果是f,表示转发,如果是r表示重定向,后缀就是要转发或重定向的路径了!
             */
            if(result.contains(":")) {
                // 使用冒号分割字符串,得到前缀和后缀
                String str[] = result.split(":");//用冒号分割字符串
                String s = str[0];//取出前缀,表示操作
                String path = str[1];//取出后缀,表示路径
                if(s.equalsIgnoreCase("r")) {//如果前缀是r,那么重定向!
                    resp.sendRedirect(req.getContextPath() + path);
                } else if(s.equalsIgnoreCase("f")) {
                    req.getRequestDispatcher(path).forward(req, resp);
                } else {
                    throw new RuntimeException("你指定的操作:" + s + ",当前版本还不支持!");
                }
            } else {//没有冒号,默认为转发!
                req.getRequestDispatcher(result).forward(req, resp);
            }
        } catch (Exception e) {
            System.out.println("您调用的方法:" + methodName + ", 它内部抛出了异常!");
            throw new RuntimeException(e);
        }
    }
}

2. service事务

  我们要清楚一件事,DAO中不是处理事务的地方,因为DAO中的每个方法都是对数据库的一次操作,而Service中的方法才是对应一个业务逻辑。也就是说我们需要在Service中的一方法中调用DAO的多个方法,而这些方法应该在一个事务中。怎么才能让DAO的多个方法使用相同的Connection呢?方法不能再自己来获得Connection,而是由外界传递进去。
  

public void daoMethod1(Connection con, …) {
}
public void daoMethod2(Connection con, …) {
}

由于在Service中不应该出现Connection,它应该只在DAO中出现。所以我们把对事务的开启和关闭放到JdbcUtils工具类中,在Service中调用JdbcUtils的方法来完成事务的处理。DAO中的方法不用再让Service来传递Connection了。DAO会主动从JdbcUtils中获取Connection对象,这样,JdbcUtils成为了DAO和Service的中介!
我们在JdbcUtils中添加beginTransaction()和rollbackTransaction(),以及commitTransaction()方法。为了处理多线程的问题,我们还要使用ThreadLocal类,为每个线程提供一个Connection,这样每个线程都可以开启自己的事务了。
JdbcUtils的完整代码:

import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;

public class JdbcUtils {
    // 配置文件的默认配置!要求你必须给出c3p0-config.xml!!!
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource();

    // 它是事务专用连接!
    private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

    /**
     * 使用连接池返回一个连接对象
     * @return
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        Connection con = tl.get();
        // 当con不等于null,说明已经调用过beginTransaction(),表示开启了事务!
        if(con != null) return con;
        return dataSource.getConnection();
    }

    /**
     * 返回连接池对象!
     * @return
     */
    public static DataSource getDataSource() {
        return dataSource;
    }

    /**
     * 开启事务
     * 1. 获取一个Connection,设置它的setAutoComnmit(false)
     * 2. 还要保证dao中使用的连接是我们刚刚创建的!
     * --------------
     * 1. 创建一个Connection,设置为手动提交
     * 2. 把这个Connection给dao用!
     * 3. 还要让commitTransaction或rollbackTransaction可以获取到!
     * @throws SQLException 
     */
    public static void beginTransaction() throws SQLException {
        Connection con = tl.get();
        if(con != null) throw new SQLException("已经开启了事务,就不要重复开启了!");
        /*
         * 1. 给con赋值!
         * 2. 给con设置为手动提交!
         */
        con = getConnection();//给con赋值,表示事务已经开始了
        con.setAutoCommit(false);

        tl.set(con);//把当前线程的连接保存起来!
    }

    /**
     * 提交事务
     * 1. 获取beginTransaction提供的Connection,然后调用commit方法
     * @throws SQLException 
     */
    public static void commitTransaction() throws SQLException {
        Connection con = tl.get();//获取当前线程的专用连接
        if(con == null) throw new SQLException("还没有开启事务,不能提交!");
        /*
         * 1. 直接使用con.commit()
         */
        con.commit();
        con.close();
        // 把它设置为null,表示事务已经结束了!下次再去调用getConnection()返回的就不是con了
        tl.remove();//从tl中移除连接
    }

    /**
     * 提交事务
     * 1. 获取beginTransaction提供的Connection,然后调用rollback方法
     * @throws SQLException 
     */
    public static void rollbackTransaction() throws SQLException {
        Connection con = tl.get();
        if(con == null) throw new SQLException("还没有开启事务,不能回滚!");
        /*
         * 1. 直接使用con.rollback()
         */
        con.rollback();
        con.close();
        tl.remove();
    }

    /**
     * 释放连接 
     * @param connection
     * @throws SQLException 
     */
    public static void releaseConnection(Connection connection) throws SQLException {
        Connection con = tl.get();
        /*
         * 判断它是不是事务专用,如果是,就不关闭!
         * 如果不是事务专用,那么就要关闭!
         */
        // 如果con == null,说明现在没有事务,那么connection一定不是事务专用的!
        if(con == null) connection.close();
        // 如果con != null,说明有事务,那么需要判断参数连接是否与con相等,若不等,说明参数连接不是事务专用连接
        if(con != connection) connection.close();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值