家居網購項目實現011 以下皆為部分代碼,詳見 https://github.com/liyuelian/furniture_mall.git 27.功能25-事務管理 27.1下訂單問題思考 在生成訂單的功能中,系統會去同時修改資料庫中的order,order_item,furn三張表,如果有任意 ...
家居網購項目實現011
以下皆為部分代碼,詳見 https://github.com/liyuelian/furniture_mall.git
27.功能25-事務管理
27.1下訂單問題思考
在生成訂單的功能中,系統會去同時修改資料庫中的order,order_item,furn三張表,如果有任意一個表修改失敗,就會出現數據不一致問題。因此出現了事務控制問題。
27.2思路分析
之前,我們每次調用底層的dao操作,每次進行的都是獨立事務,因此一但在一次業務中調用了多個dao操作,就不能保證多表的事務一致性。
因為JDBC局部事務是控制是由java.sql.Connection來完成的,要保證多個DAO的數據訪問處於一個事務中,我們需要保證他們使用的是同一個java.sql.Connection.
要保證數據一致性,就要使用事務。使用事務的前提是保證同一個連接connection。我們的想法是,在進行dao操作的前面就開啟事務,然後在進行各種dao操作後,如果沒有出現異常,則手動進行事務提交,否則進行回滾。
現在的問題是:
q1. 我們之前使用資料庫連接池,無法保證每次進行dao操作都是同一個connection連接對象
q2. 設置開啟手動提交事務以及事務回滾的時機
解決方法:
- 使用Filter+ThreadLocal進行事務管理
- 在一次http請求,servlet-service-dao的調用過程,始終是一個線程,這是使用ThreadLocal的前提
- 使用ThreadLocal來確保所有dao操作都在同一個Connection連接對象中完成
- 根據過濾器的機制,在所有代碼都走完之後會回來走過濾器的chain.dofilter()的後置代碼,這個特性非常適合進行事務管理

27.3代碼實現
27.3.1uilts包
重寫JDBCUtilsByDruid,修改getConnection方法,同時設置手動提交事務
package com.li.furns.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* 基於Druid資料庫連接池的工具類
*/
public class JDBCUtilsByDruid {
private static DataSource ds;
//定義屬性ThreadLocal,這裡存放一個Connection
private static ThreadLocal<Connection> threadLocalConn = new ThreadLocal<>();
//在靜態代碼塊完成ds的初始化
//靜態代碼塊在載入類的時候只會執行一次,因此數據源也只會初始化一次
static {
Properties properties = new Properties();
try {
//因為我們是web項目,它的工作目錄不在src下麵,文件的載入需要使用類載入器
properties.load(JDBCUtilsByDruid.class.getClassLoader()
.getResourceAsStream("druid.properties"));
//properties.load(new FileInputStream("src\\druid.properties"));
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
// //編寫getConnection方法
// public static Connection getConnection() throws SQLException {
// return ds.getConnection();
// }
/**
* 獲取連接方法
* 從ThreadLocal中獲取connection,
* 從而保證在同一個線程中獲取的是同一個Connection
*
* @return
* @throws SQLException
*/
public static Connection getConnection() {
Connection connection = threadLocalConn.get();
if (connection == null) {//說明當前的threadLocalConn沒有連接
//就從資料庫連接池中獲取一個連接,放到ThreadLocal中
try {
connection = ds.getConnection();
//設置為手動提交,即不要自動提交
connection.setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
threadLocalConn.set(connection);
}
return connection;
}
/**
* 提交事務
*/
public static void commit() {
Connection connection = threadLocalConn.get();
if (connection != null) {//確保該連接是有效的
try {
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
connection.close();//將連接釋放回連接池
} catch (SQLException e) {
e.printStackTrace();
}
}
//1.當提交後,需要把connection從threadLocalConn中清除掉
//2.否則會造成ThreadLocalConn長時間持有該連接,會影響效率
//3.也因為我們Tomcat底層使用的是線程池技術
threadLocalConn.remove();
}
}
/**
* 回滾,回滾的是和connection相關的dml操作
*/
public static void rollback() {
Connection connection = threadLocalConn.get();
if (connection != null) {//保證當前的連接是有效的
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
threadLocalConn.remove();
}
//關閉連接(註意:在資料庫連接池技術中,close不是真的關閉連接,而是將Connection對象放回連接池中)
public static void close(ResultSet resultSet, Statement statemenat, Connection connection) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statemenat != null) {
statemenat.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
因為現在連接的關閉是在commit或者rollback中發生的,因此BasicDAO中寫的關閉連接已經沒有意義了,將其刪掉即可。
27.3.2filter
配置TransactionFilter
<filter>
<filter-name>TransactionFilter</filter-name>
<filter-class>com.li.furns.filter.TransactionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TransactionFilter</filter-name>
<!--這裡我們對所有請求都進行事務管理-->
<url-pattern>/*</url-pattern>
</filter-mapping>
TransactionFilter:
package com.li.furns.filter;
import com.li.furns.utils.JDBCUtilsByDruid;
import javax.servlet.*;
import java.io.IOException;
/**
* 管理事務
*
* @author 李
* @version 1.0
*/
public class TransactionFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
try {
//先放行
chain.doFilter(request, response);
//統一提交
JDBCUtilsByDruid.commit();
} catch (Exception e) {
//只有在try{}中出現了異常,才會進行catch{}
//這裡想要捕獲異常,前提是底層的代碼沒有將拋出的異常捕獲
JDBCUtilsByDruid.rollback();//回滾
e.printStackTrace();
}
}
}
由於之前在BasicServlet中捕獲了異常,因此需要修改BasicServlet,將捕獲的異常拋出給Filter,否則無法在出現異常時進行回滾。
27.4完成測試
為了測試,在FurnDAOImpl操作中寫入錯誤的sql語句,模擬表操作失敗

現在來測試一下,當發生dao操作失敗後會產生什麼現象。
登錄用戶,點擊添加某個家居,點擊購物車生成訂單,因為生成訂單涉及到furn表的操作,因此可以看到點擊後頁面沒有跳轉到正常的顯示訂單頁面

查看後臺輸出,發現拋出異常

查看資料庫:
相關的表沒有進行改動,說明事務管理起作用了。
order_item表:

order表:

furn表:(操作前後的sales和stock欄位一致)

28.功能26-統一錯誤提示頁面
28.1需求分析/圖解
- 如果在訪問/操作網站時,出現了內部錯誤,統一顯示 500.jsp
- 如果訪問/操作不存在的頁面/servlet時,統一顯示 404.jsp
28.2思路分析
- 在發生錯誤/異常時,將錯誤/異常 拋給tomcat
- 在web.xml配置不同的錯誤顯示不同的頁面即可
28.3代碼實現
404.jsp用於顯示404錯誤;500.jsp用於顯示伺服器內部錯誤。
-
頁面代碼:略。
-
在web.xml文件中配置錯誤提示頁:
<!--404錯誤提示頁面-->
<error-page>
<error-code>404</error-code>
<location>/views/error/404.jsp</location>
</error-page>
<!--500錯誤提示頁面-->
<error-page>
<error-code>500</error-code>
<location>/views/error/500.jsp</location>
</error-page>
如果在代碼中捕獲了異常,那麼將不會起到效果,應該要將異常拋出給tomcat,讓tomcat可以根據不同的異常進行頁面展示。
TransactionFilter:

28.4完成測試
在瀏覽器中輸入一個項目不存在的資源http://localhost:8080/furniture_mall/abc.jsp
,訪問結果:

內部發生錯誤:
