本文根据崔希凡老师的讲课视频和笔记整理而成
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();
}
}