1. 程式人生 > >javaWeb學習記錄:BaseServlet 與 service事務

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();
    }
}