事务(以转账为例)
事务:
就是一件完整的事情,包含多个操作单元,这些操作要么全部成功,要么全部失败.
例如:转账,包含转出操作和转入操作.
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是常量