JavaWeb的BaseServlet与Service事务

BaseServlet

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

 

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

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 {
	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 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(":")) {
				// 使用冒号分割字符串,得到前缀和后缀
				int index = result.indexOf(":");//获取冒号的位置
				String s = result.substring(0, index);//截取出前缀,表示操作
				String path = result.substring(index+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);
		}
	}
}

假如现在有一个login功能,我们的AdminServlet只需要这么写:

public class AdminServlet extends BaseServlet {


	/**
	 * 管理员登陆
	 * @param request
	 * @param response
	 * @return
	 * @throws ServletException
	 * @throws IOException
	 */
	public String login(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		
		AdminService service = new AdminService();
	
		//封装表单数据到Admin form中
		Admin form = WebUtils.request2Bean(request, Admin.class);
		try {
			//调用service的login()方法,得到返回的Admin admin对象。
			Admin admin = service.login(form);
			//如果没有异常:保存返回值到session中,重定向到首页
			request.getSession().setAttribute("sessionUser", admin);
			//重定向到首页
			//response.sendRedirect(request.getContextPath() + "/index.jsp");
			return "r:/adminjsps/admin/main.jsp";
			
		} catch (UserException e) {
			//如果抛出异常:获取异常信息,保存到request域,再保存form,转发到adminLogin.jsp
			request.setAttribute("errorMessage", e.getMessage());
			//存入数据,回显给login.jsp页面
			request.setAttribute("admin", form);
			//跳转
			//request.getRequestDispatcher("/adminLogin.jsp").forward(request, response);
			return "/adminLogin.jsp";
		}
		
		
	}
}

 

Service事务

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

//DAO
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.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

import com.mchange.v2.c3p0.ComboPooledDataSource;

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

	//创建JNDI的上下文对象
	static{
		
		try {
			Context cxt = new InitialContext();
			//查询出入口
			//Context envContext = (Context)cxt.lookup("java:comp/env");
			//使用的是名称与元素的name对应
			//DataSource dataSource = (DataSource)envContext.lookup("jdbc/FreshMarket");
			dataSource = (ComboPooledDataSource)cxt.lookup("java:comp/env/jdbc/FreshMarket");
		} catch (NamingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
		

	
	// 它是事务专用连接!
	private static ThreadLocal tl = new ThreadLocal();
	
	/**
	 * 使用连接池返回一个连接对象
	 * @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();
	}
}

 

你可能感兴趣的:(JavaWeb)