事務(以轉賬為例)
事務:
就是一件完整的事情,包含多個操作單元,這些操作要么全部成功,要么全部失敗.
例如:轉賬,包含轉出操作和轉入操作.
mysql中的事務: mysql中事務默認是自動提交,一條sql語句就是一個事務. 開啟手動事務方式 方式1:關閉自動事務.(了解) set autocommit = off; 方式2:手動開啟一個事務.(理解) start transaction;-- 開啟一個事務 commit;-- 事務提交 rollback;-- 事務回滾 擴展: oracle中事務默認是手動的,必須手動提交才可以.
java中的事務: Connection接口的api:★ setAutoCommit(false);//手動開啟事務
commit():事務提交 rollback():事務回滾 擴展:了解 Savepoint還原點 void rollback(Savepoint savepoint) :還原到那個還原點 Savepoint setSavepoint() :設置還原點
例如:創建數據庫和表 create database hjh; use hjh; create table account( name varchar(20), money int ); insert into account values('hejh','1000'); insert into account values('swy','1000'); 完成 hejh給swy轉500; update account set money = money - 100 where name=''; update account set money = money + 100 where name='swy';
轉賬案例:
步驟分析: 1.數據庫和表 2.新建一個項目 transfer 3.導入jar包和工具類 驅動 jdbcUtils c3p0及其配置文件和工具類 dbutils 4.新建一個account.jsp 表單 5.accountservlet: 接受三個參數 調用accountservice.account方法完成轉賬操作 打印信息 6.account方法中: 使用jdbc不考慮事務 調用dao完成轉出操作 調用dao完成轉入操作 7.dao中
代碼實現:
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1"> <servlet> <servlet-name>AccountServlet</servlet-name> <servlet-class>com.hjh.servlet.AccountServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>AccountServlet</servlet-name> <url-pattern>/account</url-pattern> </servlet-mapping> </web-app>
account.jsp
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>轉賬頁面</title>
</head>
<body>
<form action="${pageContext.request.contextPath }/account" method="post">
<table border="1" width="400">
<tr>
<td>付款人:</td>
<td><input type="text" name="fromUser"/></td>
</tr>
<tr>
<td>收款人:</td>
<td><input type="text" name="toUser"/></td>
</tr>
<tr>
<td>轉賬金額:</td>
<td><input type="text" name="money"/></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="轉賬"/></td>
</tr>
</table>
</form>
</body>
</html>
AccountServlet.java
package com.hjh.servlet; import java.io.IOException; import java.io.PrintWriter; import java.sql.SQLException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.hjh.service.AccountService; /** * 轉賬案例 */
public class AccountServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //設置編碼
request.setCharacterEncoding("utf-8"); response.setContentType("text/html; charset=utf-8"); //獲取輸出流
PrintWriter w = response.getWriter(); //接收jsp頁面傳來的三個參數
String fromUser = request.getParameter("fromUser"); String toUser = request.getParameter("toUser"); int money = Integer.parseInt(request.getParameter("money")); //調用AccountService的transterAccount(fromUser,toUser,money)方法
try { new AccountService().transterAccount(fromUser,toUser,money); } catch (Exception e) { e.printStackTrace(); w.println("轉賬失敗"); return; } //打印提示信息
w.print("轉賬成功"); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
AccountService.java
package com.hjh.service; import java.sql.SQLException; import com.hjh.dao.AccountDao; public class AccountService { public void transterAccount(String fromUser, String toUser, int money) throws Exception { AccountDao dao = new AccountDao(); //轉出方,出錢
dao.accountFrom(fromUser,money); //轉入方,進錢
dao.accountTo(toUser,money); } }
AccountDao.java
package com.hjh.dao; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import com.hjh.utils.JDBCUtil; public class AccountDao { //出賬
public void accountFrom(String fromUser, int money) throws SQLException { Connection conn = null; PreparedStatement st = null; try { //獲取連接
conn = JDBCUtil.getConnection(); //編寫sql
String sql="update account set money = money - ? where name = ?"; //獲取sql語句執行者
st = conn.prepareStatement(sql); //設置sql參數
st.setInt(1, money); st.setString(2, fromUser); //執行sql
int i = st.executeUpdate(); System.out.println("轉出錢成功"+i); } catch (SQLException e) { e.printStackTrace(); throw e; }finally { JDBCUtil.closeResourse(conn, st); } } //入賬
public void accountTo(String toUser, int money) throws SQLException { Connection conn = null; PreparedStatement st = null; try { //獲取連接
conn = JDBCUtil.getConnection(); //編寫sql
String sql="update account set money = money + ? where name = ?"; //獲取sql語句執行者
st = conn.prepareStatement(sql); //設置sql參數
st.setInt(1, money); st.setString(2, toUser); //執行sql
int i = st.executeUpdate(); System.out.println("轉入錢成功"+i); } catch (SQLException e) { e.printStackTrace(); throw e; } finally { JDBCUtil.closeResourse(conn, st); } } }
JDBCUtil.java
package com.hjh.utils; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class JDBCUtil { final static String driver = "com.mysql.jdbc.Driver"; final static String url = "jdbc:mysql://localhost/hjh?useUnicode=true&characterEncoding=UTF-8"; final static String user = "root"; final static String password = "root"; Connection conn = null; PreparedStatement ps = null; Statement st = null; ResultSet rs = null; /**獲取連接*/
public static Connection getConnection() throws SQLException { Connection conn = null; try { //注冊驅動
Class.forName(driver); //獲取連接
conn = DriverManager.getConnection(url, user, password); } catch (ClassNotFoundException e) { e.printStackTrace(); } return conn; } /**關閉資源closeResourse(conn,st)*/
public static void closeResourse(Connection conn,Statement st) { try { if(st!=null) { st.close(); }else { st = null; } } catch (SQLException e) { e.printStackTrace(); } try { if(conn!=null) { conn.close(); }else { conn = null; } } catch (SQLException e) { e.printStackTrace(); } } /**關閉資源closeResourse(conn,ps)*/
public static void closeResourse(Connection conn,PreparedStatement ps) { try { if(ps!=null) { ps.close(); }else { ps = null; } } catch (SQLException e) { e.printStackTrace(); } try { if(conn!=null) { conn.close(); }else { conn = null; } } catch (SQLException e) { e.printStackTrace(); } } /**關閉資源closeResourse(rs)*/
public static void closeResourse(ResultSet rs) { try { if(rs!=null) { rs.close(); }else { rs = null; } } catch (SQLException e) { e.printStackTrace(); } } }
啟動項目,進行轉賬,不發生異常的時候,轉賬是成功的,
轉賬前:
轉賬后:
一旦出現異常,錢飛了.
模擬斷電:
accountService,java中加一個異常,模擬斷電的場景:
package com.hjh.service; import java.sql.SQLException; import com.hjh.dao.AccountDao; public class AccountService { public void transterAccount(String fromUser, String toUser, int money) throws Exception { AccountDao dao = new AccountDao(); //轉出方,出錢
dao.accountFrom(fromUser,money); int i=3/0; //轉入方,進錢
dao.accountTo(toUser,money); } }
轉賬前:
轉賬但是中途發生斷電后,轉賬失敗,但是在查詢數據庫之后發現,轉出方的錢被扣了,但是轉入方的錢不變,這樣就會造成很重大的影響:
一旦出現異常,錢飛了. 要想避免這事情,必須添加事務,在service添加事務. 為了保證所有的操作在一個事務中,必須保證使用的是同一個連接 在service層我們獲取了連接,開啟了事務.如何dao層使用此連接呢????
方法1:
向下傳遞參數.注意連接應該在service釋放
accountDao.java
package com.hjh.dao; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import com.hjh.utils.JDBCUtil; public class AccountDao { //出賬
public void accountFrom(Connection conn, String fromUser, int money) throws SQLException { PreparedStatement st = null; try { //編寫sql
String sql="update account set money = money - ? where name = ?"; //獲取sql語句執行者
st = conn.prepareStatement(sql); //設置sql參數
st.setInt(1, money); st.setString(2, fromUser); //執行sql
int i = st.executeUpdate(); System.out.println("轉出錢成功"+i); } catch (SQLException e) { //e.printStackTrace();
throw e; }finally { JDBCUtil.closeStatement(st); } } //入賬
public void accountTo(Connection conn, String toUser, int money) throws SQLException { PreparedStatement st = null; try { //編寫sql
String sql="update account set money = money + ? where name = ?"; //獲取sql語句執行者
st = conn.prepareStatement(sql); //設置sql參數
st.setInt(1, money); st.setString(2, toUser); //執行sql
int i = st.executeUpdate(); System.out.println("轉入錢成功"+i); } catch (SQLException e) { //e.printStackTrace();
throw e; } finally { JDBCUtil.closePreparedStatement(st); } } }
accountService.java
package com.hjh.service; import java.sql.Connection; import com.hjh.dao.AccountDao; import com.hjh.utils.JDBCUtil; public class AccountService { public void transterAccount(String fromUser, String toUser, int money) throws Exception { Connection conn = null; AccountDao dao = new AccountDao(); //開啟事務
try { conn = JDBCUtil.getConnection(); conn.setAutoCommit(false);//設置事務為手動提交 //轉出方,出錢
dao.accountFrom(conn,fromUser,money); int i=3/0; //轉入方,進錢
dao.accountTo(conn,toUser,money); //事務提交
conn.commit(); } catch (Exception e) { //e.printStackTrace(); //事務回滾
conn.rollback(); throw e; }finally { JDBCUtil.closeConnection(conn); } } }
accountServlet.java
package com.hjh.servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.hjh.service.AccountService; /** * 轉賬案例 * 解決中途斷電問題: * 方法1: 向下傳遞參數.注意連接應該在service釋放 */
public class AccountServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //設置編碼
request.setCharacterEncoding("utf-8"); response.setContentType("text/html; charset=utf-8"); //獲取輸出流
PrintWriter w = response.getWriter(); //接收jsp頁面傳來的三個參數
String fromUser = request.getParameter("fromUser"); String toUser = request.getParameter("toUser"); int money = Integer.parseInt(request.getParameter("money")); //調用AccountService的transterAccount(fromUser,toUser,money)方法
try { new AccountService().transterAccount(fromUser,toUser,money); } catch (Exception e) { e.printStackTrace(); w.println("轉賬失敗"); return; } //打印提示信息
w.print("轉賬成功"); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
JDBCUtil.java
package com.hjh.utils; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class JDBCUtil { final static String driver = "com.mysql.jdbc.Driver"; final static String url = "jdbc:mysql://localhost/hjh?useUnicode=true&characterEncoding=UTF-8"; final static String user = "root"; final static String password = "root"; Connection conn = null; PreparedStatement ps = null; Statement st = null; ResultSet rs = null; /**獲取連接*/
public static Connection getConnection() throws SQLException { Connection conn = null; try { //注冊驅動
Class.forName(driver); //獲取連接
conn = DriverManager.getConnection(url, user, password); } catch (ClassNotFoundException e) { e.printStackTrace(); } return conn; } public static void closeConnection_Statement_ResultSet(Connection conn,Statement st,ResultSet rs) { closeResultSet(rs); closeStatement(st); closeConnection(conn); } public static void closeConnection_Statement(Connection conn,Statement st) { closeStatement(st); closeConnection(conn); } public static void closeConnection_PreparedStatement_ResultSet(Connection conn,PreparedStatement ps,
ResultSet rs) { closeResultSet(rs); closePreparedStatement(ps); closeConnection(conn); } public static void closeConnection_PreparedStatement(Connection conn,PreparedStatement ps) { closePreparedStatement(ps); closeConnection(conn); } /**關閉資源 closeStatement((st)*/
public static void closeStatement(Statement st) { try { if(st!=null) { st.close(); }else { st = null; } } catch (SQLException e) { e.printStackTrace(); } } /**關閉資源closeResourse(conn,ps)*/
public static void closeResourse(Connection conn,PreparedStatement ps) { closePreparedStatement(ps); closeConnection(conn); } public static void closePreparedStatement(PreparedStatement ps) { try { if(ps!=null) { ps.close(); }else { ps = null; } } catch (SQLException e) { e.printStackTrace(); } } public static void closeConnection(Connection conn) { try { if(conn!=null) { conn.close(); }else { conn = null; } } catch (SQLException e) { e.printStackTrace(); } } /**關閉資源closeResourse(rs)*/
public static void closeResultSet(ResultSet rs) { try { if(rs!=null) { rs.close(); }else { rs = null; } } catch (SQLException e) { e.printStackTrace(); } } }
account.jsp
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>轉賬頁面</title>
</head>
<body>
<form action="${pageContext.request.contextPath }/account" method="post">
<table border="1" width="400">
<tr>
<td>付款人:</td>
<td><input type="text" name="fromUser"/></td>
</tr>
<tr>
<td>收款人:</td>
<td><input type="text" name="toUser"/></td>
</tr>
<tr>
<td>轉賬金額:</td>
<td><input type="text" name="money"/></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="轉賬"/></td>
</tr>
</table>
</form>
</body>
</html>
accountService.java的這一行代碼注釋掉,即轉入和轉出這2個步驟中是連貫的,不存在異常中斷的,轉賬能成功
int i=3/0;
accountService.java的這一行代碼不注釋,轉出操作完成,扣款;發生異常被捕捉,事務回滾到轉賬操作之前,金額不變,轉賬失敗
int i=3/0;
方法2: 可以將connection對象綁定當前線程上 jdk中有一個ThreadLocal類, ThreadLocal 實例通常是類中的 private static 字段, 它們希望將狀態與某一個線程(例如,用戶 ID 或事務 ID)相關聯。
ThreadLocal的方法:
構造:
new ThreadLocal()
set(Object value):將內容和當前線程綁定
Object get():獲取和當前線程綁定的內容
remove():將當前線程和內容解綁
內部維護了map集合
map.put(當前線程,內容);
map.get(當前線程)
map.remove(當前線程)
AccountDao.java
package com.hjh.dao; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import com.hjh.utils.DataSourseUtils; public class AccountDao { //出賬
public void accountFrom( String fromUser, int money) throws Exception { Connection conn = null; PreparedStatement st = null; try { conn = DataSourseUtils.getConnection(); //編寫sql
String sql="update account set money = money - ? where name = ?"; //獲取sql語句執行者
st = conn.prepareStatement(sql); //設置sql參數
st.setInt(1, money); st.setString(2, fromUser); //執行sql
int i = st.executeUpdate(); System.out.println("轉出錢成功"+i); } catch (SQLException e) { e.printStackTrace(); throw e; }finally { DataSourseUtils.closeStatement(st); } } //入賬
public void accountTo( String toUser, int money) throws Exception { Connection conn = null; PreparedStatement st = null; try { conn = DataSourseUtils.getConnection(); //編寫sql
String sql="update account set money = money + ? where name = ?"; //獲取sql語句執行者
st = conn.prepareStatement(sql); //設置sql參數
st.setInt(1, money); st.setString(2, toUser); //執行sql
int i = st.executeUpdate(); System.out.println("轉入錢成功"+i); } catch (SQLException e) { //e.printStackTrace();
throw e; } finally { DataSourseUtils.closeStatement(st); } } }
AccountService.java
package com.hjh.service; import com.hjh.dao.AccountDao; import com.hjh.utils.DataSourseUtils; public class AccountService { public void transterAccount(String fromUser, String toUser, int money) throws Exception { AccountDao dao = new AccountDao(); try { //開啟事務
DataSourseUtils.startTransaction(); //轉出方,出錢
dao.accountFrom(fromUser,money); //int i=3/0; //轉入方,進錢
dao.accountTo(toUser,money); //事務提交
DataSourseUtils.commitAndClose(); } catch (Exception e) { e.printStackTrace(); //事務回滾
DataSourseUtils.rollbackAndClose(); throw e; } } }
AccountServlet.java
package com.hjh.servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.hjh.service.AccountService; /** * 轉賬案例 * 解決中途斷電問題: * 方法1: 向下傳遞參數.注意連接應該在service釋放 */
public class AccountServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //設置編碼
request.setCharacterEncoding("utf-8"); response.setContentType("text/html; charset=utf-8"); //獲取輸出流
PrintWriter w = response.getWriter(); //接收jsp頁面傳來的三個參數
String fromUser = request.getParameter("fromUser"); String toUser = request.getParameter("toUser"); int money = Integer.parseInt(request.getParameter("money")); //調用AccountService的transterAccount(fromUser,toUser,money)方法
try { new AccountService().transterAccount(fromUser,toUser,money); } catch (Exception e) { e.printStackTrace(); w.println("轉賬失敗"); return; } //打印提示信息
w.print("轉賬成功"); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
DataSourseUtils.java
package com.hjh.utils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import javax.sql.DataSource; import com.mchange.v2.c3p0.ComboPooledDataSource; public class DataSourseUtils { //建立連接池ds
private static ComboPooledDataSource ds = new ComboPooledDataSource(); //將connection綁定在當前線程中
private static ThreadLocal<Connection> tl = new ThreadLocal<>(); //獲取數據源
public static DataSource getDataSourse() { return ds; } //獲取連接,從當前線程中獲取
public static Connection getConnection() throws Exception { Connection conn = tl.get(); if(conn==null) { //第一次獲取,創建一個連接和當前線程綁定在一起
conn =ds.getConnection(); //綁定
tl.set(conn); } return conn; } //獲取連接,開啟事務
public static void startTransaction() throws Exception { getConnection().setAutoCommit(false); } //事務提交|解除綁定
public static void commitAndClose() { try { Connection conn = getConnection(); //提交事務
conn.commit(); //解除綁定
tl.remove(); //釋放資源
closeConnection(conn); } catch (Exception e) { e.printStackTrace(); } } //事務回滾
public static void rollbackAndClose() { try { Connection conn = getConnection(); //提交事務
conn.rollback(); //釋放資源
closeConnection(conn); //解除綁定
tl.remove(); } catch (Exception e) { e.printStackTrace(); } } //釋放資源connection
public static void closeConnection(Connection conn) { try { if(conn!=null) { conn.close(); //並和當前線程解綁
tl.remove(); } } catch (Exception e) { e.printStackTrace(); } conn = null; } //釋放資源Statement
public static void closeStatement(Statement st) { try { if(st!=null) { st.close(); }else { st = null; } } catch (SQLException e) { e.printStackTrace(); } } /**釋放資源closePreparedStatement*/
public static void closePreparedStatement(PreparedStatement ps) { try { if(ps!=null) { ps.close(); }else { ps = null; } } catch (SQLException e) { e.printStackTrace(); } } //釋放資源Connection ,Statement
public static void close2Resourse(Connection conn,Statement st) { closeStatement(st); closeConnection(conn); } /**釋放資源closeResourse(conn,ps)*/
public static void close2Resourse(Connection conn,PreparedStatement ps) { closePreparedStatement(ps); closeConnection(conn); } /**釋放資源closeResourse(rs)*/
public static void closeResourse(ResultSet rs) { try { if(rs!=null) { rs.close(); }else { rs = null; } } catch (SQLException e) { e.printStackTrace(); } } }
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation=
"http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<servlet>
<servlet-name>AccountServlet</servlet-name>
<servlet-class>com.hjh.servlet.AccountServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AccountServlet</servlet-name>
<url-pattern>/account</url-pattern>
</servlet-mapping>
</web-app>
account.jsp
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>轉賬頁面</title>
</head>
<body>
<form action="${pageContext.request.contextPath }/account" method="post">
<table border="1" width="400">
<tr>
<td>付款人:</td>
<td><input type="text" name="fromUser"/></td>
</tr>
<tr>
<td>收款人:</td>
<td><input type="text" name="toUser"/></td>
</tr>
<tr>
<td>轉賬金額:</td>
<td><input type="text" name="money"/></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="轉賬"/></td>
</tr>
</table>
</form>
</body>
</html>
DButils: 1.創建queryrunner 2.編寫sql 3.執行sql QueryRunner: 構造: new QueryRunner(DataSource ds):自動事務 new QueryRunner():手動事務 常用方法: update(Connection conn,String sql,Object ... params):執行的cud操作 query(Connection conn....):執行查詢操作 注意: 一旦使用手動事務,調用方法的時候都需要手動傳入connection,並且需要手動關閉連接
accountDao.java
package com.hjh.dao; import org.apache.commons.dbutils.QueryRunner; import com.hjh.utils.DataSourseUtils; public class AccountDao { //出賬 public void accountFrom( String fromUser, int money) throws Exception { QueryRunner qr = new QueryRunner(); //編寫sql String sql="update account set money = money - ? where name = ?"; //執行sql qr.update(DataSourseUtils.getConnection(),sql,money,fromUser); System.out.println("轉入錢成功"); } //入賬 public void accountTo( String toUser, int money) throws Exception { QueryRunner qr = new QueryRunner(); //編寫sql String sql="update account set money = money + ? where name = ?"; //執行sql qr.update(DataSourseUtils.getConnection(),sql,money,toUser); System.out.println("轉出錢成功"); } }
accountService.java
package com.hjh.service; import com.hjh.dao.AccountDao; import com.hjh.utils.DataSourseUtils; public class AccountService { public void transterAccount(String fromUser, String toUser, int money) throws Exception { AccountDao dao = new AccountDao(); try { //開啟事務
DataSourseUtils.startTransaction(); //轉出方,出錢
dao.accountFrom(fromUser,money); //int i=3/0; //轉入方,進錢
dao.accountTo(toUser,money); //事務提交
DataSourseUtils.commitAndClose(); } catch (Exception e) { e.printStackTrace(); //事務回滾
DataSourseUtils.rollbackAndClose(); throw e; } } }
accountServlet.java
package com.hjh.servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.hjh.service.AccountService; /** * 轉賬案例 * 解決中途斷電問題: * 方法1: dbutils */
public class AccountServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //設置編碼
request.setCharacterEncoding("utf-8"); response.setContentType("text/html; charset=utf-8"); //獲取輸出流
PrintWriter w = response.getWriter(); //接收jsp頁面傳來的三個參數
String fromUser = request.getParameter("fromUser"); String toUser = request.getParameter("toUser"); int money = Integer.parseInt(request.getParameter("money")); //調用AccountService的transterAccount(fromUser,toUser,money)方法
try { new AccountService().transterAccount(fromUser,toUser,money); } catch (Exception e) { e.printStackTrace(); w.println("轉賬失敗"); return; } //打印提示信息
w.print("轉賬成功"); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
DataSourceUtils.java
package com.hjh.utils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import javax.sql.DataSource; import com.mchange.v2.c3p0.ComboPooledDataSource; public class DataSourseUtils { //建立連接池ds
private static ComboPooledDataSource ds = new ComboPooledDataSource(); //將connection綁定在當前線程中
private static ThreadLocal<Connection> tl = new ThreadLocal<>(); //獲取數據源
public static DataSource getDataSourse() { return ds; } //獲取連接,從當前線程中獲取
public static Connection getConnection() throws Exception { Connection conn = tl.get(); if(conn==null) { //第一次獲取,創建一個連接和當前線程綁定在一起
conn =ds.getConnection(); //綁定
tl.set(conn); } return conn; } //獲取連接,開啟事務
public static void startTransaction() throws Exception { getConnection().setAutoCommit(false); } //事務提交|解除綁定
public static void commitAndClose() { try { Connection conn = getConnection(); //提交事務
conn.commit(); //解除綁定
tl.remove(); //釋放資源
closeConnection(conn); } catch (Exception e) { e.printStackTrace(); } } //事務回滾
public static void rollbackAndClose() { try { Connection conn = getConnection(); //提交事務
conn.rollback(); //釋放資源
closeConnection(conn); //解除綁定
tl.remove(); } catch (Exception e) { e.printStackTrace(); } } //釋放資源connection
public static void closeConnection(Connection conn) { try { if(conn!=null) { conn.close(); //並和當前線程解綁
tl.remove(); } } catch (Exception e) { e.printStackTrace(); } conn = null; } //釋放資源Statement
public static void closeStatement(Statement st) { try { if(st!=null) { st.close(); }else { st = null; } } catch (SQLException e) { e.printStackTrace(); } } /**釋放資源closePreparedStatement*/
public static void closePreparedStatement(PreparedStatement ps) { try { if(ps!=null) { ps.close(); }else { ps = null; } } catch (SQLException e) { e.printStackTrace(); } } //釋放資源Connection ,Statement
public static void close2Resourse(Connection conn,Statement st) { closeStatement(st); closeConnection(conn); } /**釋放資源closeResourse(conn,ps)*/
public static void close2Resourse(Connection conn,PreparedStatement ps) { closePreparedStatement(ps); closeConnection(conn); } /**釋放資源closeResourse(rs)*/
public static void closeResourse(ResultSet rs) { try { if(rs!=null) { rs.close(); }else { rs = null; } } catch (SQLException e) { e.printStackTrace(); } } }
c3p0-config.xml
<c3p0-config>
<!-- 默認配置,如果沒有指定則使用這個配置 -->
<default-config>
<!-- 基本配置 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost/hjh?characterEncoding=UTF-8 </property>
<property name="user">root</property>
<property name="password">root</property>
<!--擴展配置-->
<property name="checkoutTimeout">30000</property>
<property name="idleConnectionTestPeriod">30</property>
<property name="initialPoolSize">10</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
<property name="maxStatements">200</property>
</default-config>
<!-- 命名的配置 -->
<named-config name="XXX">
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/xxxx</property>
<property name="user">root</property>
<property name="password">1234</property>
<!-- 如果池中數據連接不夠時一次增長多少個 -->
<property name="acquireIncrement">5</property>
<property name="initialPoolSize">20</property>
<property name="minPoolSize">10</property>
<property name="maxPoolSize">40</property>
<property name="maxStatements">20</property>
<property name="maxStatementsPerConnection">5</property>
</named-config>
</c3p0-config>
account.jsp
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>轉賬頁面</title>
</head>
<body>
<form action="${pageContext.request.contextPath }/account" method="post">
<table border="1" width="400">
<tr>
<td>付款人:</td>
<td><input type="text" name="fromUser"/></td>
</tr>
<tr>
<td>收款人:</td>
<td><input type="text" name="toUser"/></td>
</tr>
<tr>
<td>轉賬金額:</td>
<td><input type="text" name="money"/></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="轉賬"/></td>
</tr>
</table>
</form>
</body>
</html>
事務總結:
事務的特性:★★★
ACID
原子性:事務里面的操作單元不可切割,要么全部成功,要么全部失敗
一致性:事務執行前后,業務狀態和其他業務狀態保持一致.
隔離性:一個事務執行的時候最好不要受到其他事務的影響
持久性:一旦事務提交或者回滾.這個狀態都要持久化到數據庫中
不考慮隔離性會出現的讀問題★★
臟讀:在一個事務中讀取到另一個事務沒有提交的數據
不可重復讀:在一個事務中,兩次查詢的結果不一致(針對的update操作)
虛讀(幻讀):在一個事務中,兩次查詢的結果不一致(針對的insert操作)
通過設置數據庫的隔離級別來避免上面的問題(理解)
read uncommitted 讀未提交 上面的三個問題都會出現
read committed 讀已提交 可以避免臟讀的發生
repeatable read 可重復讀 可以避免臟讀和不可重復讀的發生
serializable 串行化 可以避免所有的問題
了解
演示臟讀的發生:
將數據庫的隔離級別設置成 讀未提交
set session transaction isolation level read uncommitted;
查看數據庫的隔離級別
select @@tx_isolation;
避免臟讀的發生,將隔離級別設置成 讀已提交
set session transaction isolation level read committed;
不可避免不可重復讀的發生.
避免不可重復讀的發生 經隔離級別設置成 可重復讀
set session transaction isolation level repeatable read;
演示串行化 可以避免所有的問題
set session transaction isolation level serializable;
鎖表的操作.
四種隔離級別的效率 read uncommitted>read committed>repeatable read>serializable 四種隔離級別的安全性 read uncommitted<read committed<repeatable read<serializable
開發中絕對不允許臟讀發生.
mysql中默認級別:repeatable read
oracle中默認級別:read committed
java中控制隔離級別:(了解) Connection的api void setTransactionIsolation(int level) level是常量