一、事务的概念:
事务指的是在逻辑上的一个整体,在这整体中,所有相关的操作要么全部执行成功,要么全部失败!
转账:在你老爸的卡上减掉一部分金额(就是一条SQL语句);还得在你的卡上增强一部分金额(也是一条SQL语句) 当你老爸自己卡上的钱扣掉了,突然ATM机断电了,但是你卡上的每增加!
如果使用了事务,一旦你老爸卡上的钱被扣,然后你卡上的钱没有发生变化,这整个操作应该都是失败的!
事务的作用:能够保证在同一个事务中,数据的完整性(操作前和操作后)
转账:操作前:你卡上的金额(0)+你老爸卡上的金额(100000)=100000 转账失败!
操作后:你卡上的金额(10000)+你老爸卡上的金额(90000)=100000 转账成功!
二、MySQL的事务操作
开启事务:start transaction;
执行删除操作: delete from product; //一条记录一条记录的删除,如果主键是自动增长的,那么主键记录不会清零。
回滚事务:rollback; //一旦执行这个操作,说明我们希望整个执行操作全部失败 ,事务可以保证数据的完整性。
提交事务:commit;//一旦执行这个操作,说明我们希望整个操作全部成功,调用commit可以让之前的操作生效。
truncate table:即使使用了事务,删除的数据也无法找回;先把整张表摧毁,然后创建一张结构一模一样的表,那么它的主键记录清零。
三、JDBC的事务操作
JDBC的事务操作全都依靠Connection对象!
他
开启事务:
JDBC默认的是自动提交事务,所有我们需要设置为手动提交;connection.setAutoCommit(false);//设置手动提交
执行相关操作:
你老爸卡上的金额-10000;
你卡上的金额+10000;
然后才提交事务:connection.commit();
一旦中间出现问题,那么整个操作应该都算失败的!
回滚操作:connection.rollback();
在JDBC代码中应该如何体现呢?
@Test
public void test(){
Connection conn = JDBCUtils.getConnection();
//1.设置手动提交
conn.setAutoCommit(false);
//2.编写SQL语句
String sql = "update account set money=money-10000";
//3.获得执行SQL语句对象
PreparedStatement pstmt = conn.prepareStatement(sql);
//4.执行SQL语句
try{
int rows = pstmt.executeUpdate();
……
//5.提交事务
conn.commit();
}catch(){
……
//6.回滚
conn.rollback();
}finally{
//7.关闭资源
}
}
四、使用JDBC的事务操作完成转账功能
第一步:导入原型(account.jsp)
第二步:创建数据库(表)并初始化数据
第三步:寻找程序的入口!(account.jsp的表单,书写action的请求路径)
第四步:书写AccountServlet(获得表单提交的三个参数,调用service层转账方法)
第五步:书写service层(获得Connection对象,完成JDBC事务操作)
第六步:书写dao层(2个方法,付款和收款)
代码
倒入原型和程序的入口
<%@ 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>
<!-- 客户端路径:使用的是相对路径,带有/,相对当前主机。 /day19_JDBC_245/AccountServlet -->
<form action="${pageContext.request.contextPath}/AccountServlet" method="post">
<table border="1" width="50%" align="center">
<tr>
<td>收款人</td>
<td><input type="text" name="to"/></td>
</tr>
<tr>
<td>付款人</td>
<td><input type="text" name="from"/></td>
</tr>
<tr>
<td>付款金额</td>
<td><input type="text" name="money"/></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="转账"/></td>
</tr>
</table>
</form>
</body>
</html>
WEB层
public class AccountServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
request.setCharacterEncoding("utf-8");
String form = request.getParameter("from");
String to = request.getParameter("to");
double money = Double.parseDouble(request.getParameter("money"));
AccountService service = new AccountService();
try {
service.transfer(form,to,money);
} catch (Exception e) {
// TODO: handle exception
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
Service层
public void transfer1(String form, String to, double money) {
Connection conn = C3P0Utils.getConnnection();
try {
conn.setAutoCommit(false);
AccountDao dao = new AccountDao();
dao.transferFrom(form,money,conn);
dao.transferTo(to,money,conn);
conn.commit();
} catch (SQLException e) {
// TODO Auto-generated catch block
try {
conn.rollback();
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
Dao层
/**
* 付款方法:JDBC方式
* @param from 付款人
* @param money 付款金额
* @param conn 连接对象
*/
public void transferFrom(String from, double money, Connection conn) {
//1.编写SQL语句
String sql = "update tbl_account set money=money-? where username=?";
try {
//2.获得执行SQL语句对象
PreparedStatement pstmt = conn.prepareStatement(sql);
//3.设置实际参数
pstmt.setDouble(1, money);
pstmt.setString(2, from);
//4.执行更新操作
pstmt.executeUpdate();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 收款方法:JDBC方式
* @param to 收款人
* @param money 收款金额
* @param conn 连接对象
*/
public void transferTo(String to, double money, Connection conn) {
//1.编写SQL语句
String sql = "update tbl_account set money=money+? where username=?";
try {
//2.获得执行SQL语句对象
PreparedStatement pstmt = conn.prepareStatement(sql);
//3.设置实际参数
pstmt.setDouble(1, money);
pstmt.setString(2, to);
//4.执行更新操作
pstmt.executeUpdate();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
效果演示:(展示的不美观? 美观是美工的事情)
数据库结果:
五、DBUtils的事务操作
DBUtils是对JDBC的一个封装,主要是简化JDBC的代码书写。这意味DBUtils操作事务也是依赖Connection对象。
回顾DBUtils的操作
//1.获得QueryRunner核心对象
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource()); //C3P0Utils.getDataSource()主要是获得Connection
//2.编写SQL语句
String sql = "update tbl_account set money=money-? where username=?";
//3.设置实际参数
Object[] params = {money,from};
//4.执行更新操作
qr.update(sql,params);
//现在要使用事务进行操作,那么必须保证service层得到的Connection与Dao里面的Connection是同一个!!!!我们使用之前的方式以方法的参数传递进来!那么代码可以这么改写:
//1.获得QueryRunner核心对象
QueryRunner qr = new QueryRunner();//Connection对象是service层传递过来的!
//2.编写SQL语句
String sql = "update tbl_account set money=money-? where username=?";
//3.设置实际参数
Object[] params = {money,from};
//4.执行更新操作
qr.update(conn,sql,params);
代码:
和JDBC的区别只有Dao层
/**
* 付款方法:DBUtils方式
* @param from 付款人
* @param money 付款金额
* @param conn 连接对象
*/
public void transferFrom(String from, double money, Connection conn) {
//1.获得QueryRunner核心对象(不能使用数据源获得Connection对象,否则与service层不是同一个)
QueryRunner qr = new QueryRunner();
//2.编写SQL语句
String sql = "update tbl_account set money=money-? where username=?";
//3.设置实际参数
Object[] params = {money,from};
//4.执行更新操作
try {
qr.update(conn, sql, params);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 收款方法:DBUtils方式
* @param to 收款人
* @param money 收款金额
* @param conn 连接对象
*/
public void transferTo(String to, double money, Connection conn) {
//1.获得QueryRunner核心对象(不能使用数据源获得Connection对象,否则与service层不是同一个)
QueryRunner qr = new QueryRunner();
//2.编写SQL语句
String sql = "update tbl_account set money=money+? where username=?";
//3.设置实际参数
Object[] params = {money,to};
//4.执行更新操作
try {
qr.update(conn, sql, params);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
六、使用ThearLocal改善方法传递Connection对象
从之前的实现,我们发现每次要想保证service层与dao层的connection是同一个对象,就必须在service层调用dao层方法的时候以方法参数的形式进行传递!这种处理方式不太好,我们想到了一个对象ThreadLocal!
之前我们学习了一个对象Map(key,value);TheadLocal对于Map内部的存储结构一样!
Map设置值:map.put(key,value);
Map获取值:map.get(key);
ThrealLocal设置值:local.set(key,value); //key就是当前线程对象,不需要我们去维护,有ThreadLocal自己维护
ThrealLocal获取值:local.get(key);
但是他们之间有点区别:
Map的key需要我们去维护,而ThreadLocal的key不用我们维护,这个对象自己负责维护,而且能够保证key是唯一的!那意味着调用对应的方法是:
ThrealLocal设置值:local.set(value);
ThrealLocal获取值:local.get();
ThreadLocal的key是什么呢?它就是当前线程对象!这个ThreadLocal就好比是咱们进入超市存放东西的柜子!
现在有A线程 类似这个条形码 张三去存包 买完东西取包
现在有B线程 类似这个条形码 班长去存包 买完东西取包
代码:主要service层 dao层 C3P0Utils不一样
改写C3P0Utils
public class C3P0Utils {
private static DataSource dataSource;
static{
dataSource=new ComboPooledDataSource("oracle");
}
private static ThreadLocal<Connection> local = new ThreadLocal<>();
public static Connection getConnectionForThreadLocal(){
Connection conn = local.get();
if (conn==null) {
try {
conn = dataSource.getConnection();
local.set(conn);
return local.get();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return conn;
}
}
Service层
public void transfer(String form, String to, double money) {
Connection conn = C3P0Utils.getConnectionForThreadLocal();
try {
conn.setAutoCommit(false);
AccountDao dao = new AccountDao();
dao.transferFrom(form,money);
dao.transferTo(to,money);
conn.commit();
} catch (SQLException e) {
// TODO Auto-generated catch block
try {
conn.rollback();
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
Dao层
public void transferFrom(String form, double money) {
// TODO Auto-generated method stub
Connection conn = C3P0Utils.getConnectionForThreadLocal();
QueryRunner qr = new QueryRunner();
// 2.编写SQL语句
String sql = "update transfer set money=money-? where name=?";
// 3.设置实际参数
Object[] params = { money, form };
// 4.执行更新操作
try {
qr.update(conn, sql, params);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void transferTo(String to, double money) {
// TODO Auto-generated method stub
Connection conn = C3P0Utils.getConnectionForThreadLocal();
QueryRunner qr = new QueryRunner();
// 2.编写SQL语句
String sql = "update transfer set money=money+? where name=?";
// 3.设置实际参数
Object[] params = { money, to };
// 4.执行更新操作
try {
qr.update(conn, sql, params);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
七、验证事务和同一个connnection的必要
a.验证事务
public void transfer(String to, String from, double money) {
/**
* 整个转账功能就是要执行Dao层的2条SQL语句(减金额和加金额),我们要使用事务将这2条SQL语句绑定在一起
*/
//1.获得Connection对象
Connection conn = C3P0Utils.getConnectionForThreadLocal();
try {
//2.手动开启事务
conn.setAutoCommit(false);
AccountDao dao = new AccountDao();
//3.调用dao付钱方法
dao.transferFrom(from,money);
//模拟断电让其后收钱代码失效
int i = 1/0;
//4.调用dao收钱方法
dao.transferTo(to,money);
//5.提交事务
conn.commit();
} catch (Exception e) {
//6.回滚事务
try {
conn.rollback();
} catch (Exception e2) {
throw new RuntimeException(e);
}
throw new RuntimeException(e);
}
}
虽然模拟断电其后的收钱方法失效 但是运行后转账人的钱没有减少 验证了事务的要么全成功要么全失败
b.同一个connection的必要性
如果service层不是一个connection对象 那么就不能保证实务操作 如果断电操作后 实务不会全部失败 转账人钱减少 收款人钱没增加
八、事务的特性(笔试) ACID
* A 原子性:事务中的多条语句是不可再分隔的!要么完全成功,要么完全失败!
* C 一致性(核心):在事务执行前后,要保证业务数据前后一致!
* I 隔离性:多个事务并发执行,需要隔离开来,保存不相互影响!
* D 持久性:数据只要保存到了数据库中,数据就是持久的!就算数据库马上崩溃,也要在重启后有能力恢复数据。
九、事务并发访问问题:
脏读:读到了另外一个事务未提交的数据
不可重复读:读到了另外一个事务修改了数据
幻读:读到了另外一个事务添加了的数据
解决办法:设置事务隔离级别:
串行化:单线程,性能低,可以解决所有的并发访问问题。
可重复读:能够解决脏读和不可重复读
读已提交:能够解决脏读问题。
读未提交:啥都没干,没卵用