JDBC学习笔记(11):事务

事务(Transaction):是并发控制的单元,是用户定义的一系列操作序列。这些操作要么都做,要么都不做,是一个不可分割的工作单位。通过事务,可以将逻辑相关的一组操作绑定在一起,以便服务器保持数据的完整性。事务通常是以begin transaction开始,以commit或rollback结束。commit表示提交,即提交事务的所有操作。具体地说就是将事务中所有的数据的更新写回到磁盘上的物理数据库中去,事务正常结束。rollback表示回滚,即在事务运行的过程中发生了某种故障,事务不能继续进行,系统将事务中的数据库的所有已完成的操作全部撤销,滚回到事务开始的状态。

事务的基本特性:

  • 原子性(atomicity):组成事务处理的语句形成了一个逻辑单元,不能只执行其中的一部分。
  • 一致性(consistency):在事务处理执行前后,数据库是一致的(数据库数据完整约束)。
  • 隔离性(isolcation):一个事务处理对另一个事务处理的影响。
  • 持续性(durability):事务处理的效果能够被永久保存下来。
  • connection.setAutoCommit(false);// 打开事务
  • connection.commit(); // 提交事务
  • connection.rollback();// 回滚事务

以现实生活中银行转账的为例子,从A账户转账10元到B账户,如果B账户的余额超过300就不转账。查询数据库初始数据如下:

id name         birthday         money

1 zhangs         1985-01-01 400

2 lisi                 1986-01-01 500

3 wangwu         1987-01-01 300

4 qianqi         2015-03-15 500

5 qianqi         2015-03-15 500

6 daoname1 2015-03-17 9000000

示例代码:

 1 package com.xxyh.jdbc;
 2 import java.sql.Connection;
 3 import java.sql.ResultSet;
 4 import java.sql.SQLException;
 5 import java.sql.Statement;
 6 public class TxTest {
 7     public static void main(String[] args) {
 8         Connection conn = null;
 9         Statement stmt = null;
10         ResultSet rs = null;
11         try {
12             conn = JdbcUtils.getConnection();
13             stmt = conn.createStatement();
14             String sql = "update user set money=money-10 where id=1";
15             stmt.executeUpdate(sql);
16
17             sql = "select money from user where id=2";
18             rs = stmt.executeQuery(sql);
19             float money = 0.0f;
20             if (rs.next()) {
21                 money = rs.getFloat("money");
22             }
23             if (money > 300)
24                 throw new RuntimeException("已经超过最大值!");
25             sql = "update user set money=money+10 where id=2";
26            stmt.executeUpdate(sql);
27         } catch (SQLException e) {
28             // TODO Auto-generated catch block
29             e.printStackTrace();
30         } finally{
31             JdbcUtils.close(rs, stmt, conn);
32         }
33     }
34 }

【运行结果】:

Exception in thread "main" java.lang.RuntimeException: 已经超过最大值!

at com.xxyh.jdbc.TxTest.main(TxTest.java:27)

再次查询数据库:

id name         birthday         money

1 zhangs       1985-01-01   390

2 lisi              1986-01-01   500

3 wangwu      1987-01-01   300

4 qianqi         2015-03-15   500

5 qianqi         2015-03-15   500

6 daoname1 2015-03-17     9000000

发现A账号的余额减少了10元,而B账户的余额却并没有增加。

通过添加事务,防止事务并发可能引起的为题,代码如下:

 1 package com.xxyh.jdbc;
 2 import java.sql.Connection;
 3 import java.sql.ResultSet;
 4 import java.sql.SQLException;
 5 import java.sql.Statement;
 6 public class TxTest {
 7     public static void main(String[] args) throws SQLException {
 8         Connection conn = null;
 9         Statement stmt = null;
10         ResultSet rs = null;
11         try {
12             conn = JdbcUtils.getConnection();
13             conn.setAutoCommit(false);// 打开事务,关闭自动提交
14             stmt = conn.createStatement();
15             String sql = "update user set money=money-10 where id=1";
16             stmt.executeUpdate(sql);
17
18             sql = "select money from user where id=2";
19             rs = stmt.executeQuery(sql);
20             float money = 0.0f;
21             if (rs.next()) {
22                 money = rs.getFloat("money");
23             }
24             if (money > 300)
25                 throw new RuntimeException("已经超过最大值!");
26             sql = "update user set money=money+10 where id=2";
27             stmt.executeUpdate(sql);
28             conn.commit();
29         } catch (SQLException e) {
30             if (conn != null)
31                 conn.rollback();
32             throw e;
33         } finally{
34             JdbcUtils.close(rs, stmt, conn);
35         }
36     }
37 }

【运行结果】:

Exception in thread "main" java.lang.RuntimeException: 已经超过最大值!

at com.xxyh.jdbc.TxTest.main(TxTest.java:28)

再次查询数据库:

id name         birthday         money

1 zhangs       1985-01-01   400

2 lisi              1986-01-01   500

3 wangwu      1987-01-01   300

4 qianqi         2015-03-15   500

5 qianqi         2015-03-15   500

6 daoname1  2015-03-17    9000000

如果将B账号的余额最大设置为600,再次运行以上代码,【运行结果】:

id name         birthday         money

1 zhangs       1985-01-01   390

2 lisi              1986-01-01   510

3 wangwu      1987-01-01   300

4 qianqi         2015-03-15   500

5 qianqi         2015-03-15   500

6 daoname1   2015-03-17   9000000

JDBC中的事务保存点,即事务发生回滚的时候,回滚到保存点即止,事务开始到保存点之间的部分不回滚。

保存点在事务中的应用:

初始状体数据库的数据状况:

id name         birthday         money

1 zhangs       1985-01-01   370

2 lisi              1986-01-01   520

3 wangwu      1987-01-01   310

4 qianqi         2015-03-15   500

5 qianqi         2015-03-15   500

6 daoname1  2015-03-17    9000000

 1 package com.xxyh.jdbc;
 2 import java.sql.Connection;
 3 import java.sql.ResultSet;
 4 import java.sql.SQLException;
 5 import java.sql.Savepoint;
 6 import java.sql.Statement;
 7 public class SavePointTest {
 8     public static void main(String[] args) throws SQLException {
 9         Connection conn = null;
10         Statement stmt = null;
11         ResultSet rs = null;
12         Savepoint sp = null;
13         try {
14             conn = JdbcUtils.getConnection();
15             conn.setAutoCommit(false);// 打开事务,关闭自动提交
16             stmt = conn.createStatement();
17             String sql = "update user set money=money-10 where id=1";
18             stmt.executeUpdate(sql);
19
20             sp = conn.setSavepoint();
21
22             // 转账的同时更新id=3的账户余额
23             sql = "update user set money=money+10 where id=3";
24             stmt.executeUpdate(sql);
25
26             sql = "select money from user where id=2";
27             rs = stmt.executeQuery(sql);
28             float money = 0.0f;
29             if (rs.next()) {
30                 money = rs.getFloat("money");
31             }
32             if (money > 200)
33                 throw new RuntimeException("已经超过最大值!");
34             sql = "update user set money=money+10 where id=2";
35             stmt.executeUpdate(sql);
36             conn.commit();
37         } catch(RuntimeException e) {
38             if (conn != null && sp != null) {
39                 conn.rollback(sp);// 回滚到保存点
40                 conn.commit();
41             }
42             throw e;
43         } catch (SQLException e) {
44             if (conn != null)
45                 conn.rollback();
46             throw e;
47         } finally{
48             JdbcUtils.close(rs, stmt, conn);
49         }
50     }
51 }

【运行结果】:

Exception in thread "main" java.lang.RuntimeException: 已经超过最大值!

at com.xxyh.jdbc.SavePointTest.main(SavePointTest.java:35)

运行以上程序后,查询数据库:

id name         birthday         money

1 zhangs       1985-01-01   360

2 lisi              1986-01-01   530

3 wangwu      1987-01-01   320

4 qianqi         2015-03-15   500

5 qianqi         2015-03-15   500

6 daoname1  2015-03-17    9000000

只有1的余额减少了,而2和3的余额均没有改变,因为事务只回滚到了保存点的位置并没有会滚到事务的开始位置。

事务并发可能导致的问题:

  • 丢失更新:撤销一个事务时,把其他事务已提交的更新数据覆盖(A和B事务并行执行,A事务执行更新后,提交;B事务在A事务更新后,B事务结束前也做了对该行数据的更新操作,然后回滚,则两次更新操作都丢失了)。
  • 脏读:一个事务读到另一个事物未提交的更新数据(A和B事务并发执行,B事务执行更新后,A事务查询B事务没有提交的数据,B事务回滚,则A事务得到的数据不是数据库中的真实数据。也就是脏数据,即和数据库中不一致的数据)。
  • 不可重复读:一个事务读到另一个事务已提交的更新数据(A和B并发执行,A事务查询数据,然后B事务更新该数据,A再次查询该数据时,发现数据变化了)。
  • 覆盖更新:这是不可重复读的一个特例。一个事务覆盖另一个事务已提交的数据(即A事务更新数据,然后B事务更新该数据,A事务查询发现自己更新的数据发生了变化)。
  • 幻读:一个事务读到另一个事务新插入的数据(A和B事务并发执行,A事务查询数据,B事务插入或者删除数据,A事务再次查询发现结果集中有以前没有的数据或者以前有的数据消失了)。
时间: 2024-08-05 11:13:11

JDBC学习笔记(11):事务的相关文章

JDBC学习笔记(7)——事务的隔离级别&批量处理

数据库事务的隔离级别 对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:脏读: 对于两个事物 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的.不可重复读: 对于两个事物 T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了.幻读: 对于两个事物 T1, T2, T1 从一个表中读取了一个字段,

【转】JDBC学习笔记(7)——事务的隔离级别&批量处理

转自:http://www.cnblogs.com/ysw-go/ 数据库事务的隔离级别 对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的.不可重复读: 对于两个事务 T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了.幻

《C++ Primer Plus》学习笔记11

<C++ Primer Plus>学习笔记11 第17章 输入.输出和文件 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

sqlite学习笔记11:C语言中使用sqlite之删除记录

最后一节,这里记录下如何删除数据. 前面所有的代码都继承在这里了,在Ubuntu14.04和Mac10.9上亲测通过. #include <stdio.h> #include <stdlib.h> #include "sqlite/sqlite3.h" #define DB_NANE "sqlite/test.db" sqlite3 *db = NULL; char* sql = NULL; char *zErrMsg = NULL; con

lua学习笔记11:lua中的小技巧

lua中的小技巧,即基础lua语言本身的特种,进行一个些简化的操作 一 巧用or x = x or v 等价于: if not x then x = v end 如果x为nil或false,就给他赋值为 二 三元运算符实现 a and b or c 类似C语言: a ? b : c and 的运算由优先级高于or lua学习笔记11:lua中的小技巧,布布扣,bubuko.com

mybatis学习笔记(11)-多对多查询

mybatis学习笔记(11)-多对多查询 mybatis学习笔记11-多对多查询 示例 多对多查询总结 resultMap总结 本文实现多对多查询,查询用户及用户购买商品信息. 示例 查询主表是:用户表 关联表:由于用户和商品没有直接关联,通过订单和订单明细进行关联,所以关联表:orders.orderdetail.items sql SELECT orders.*, user.username, user.sex, user.address, orderdetail.id orderdeta

mybatis学习笔记(11)-一对多查询

mybatis学习笔记(11)-一对多查询 mybatis学习笔记11-一对多查询 示例 小结 本文实现一对多查询,查询订单及订单明细的信息 示例 sql 确定主查询表:订单表 确定关联查询表:订单明细表 在一对一查询基础上添加订单明细表关联即可. SELECT orders.*, user.username, user.sex, user.address, orderdetail.id orderdetail_id, orderdetail.items_id, orderdetail.item

学习笔记11

目前正在专修CSS这一块,现在对内联元素和块元素进行深入的学习一下: 内联元素(inline element)一般都是基于语义级(semantic)的基本元素.内联元素只能容纳文本或者其他内联元素,常见内联元素"a". 块元素(block element)和内联元素(inline element)都是html规范中的概念.块元素和内联元素的基本差异是块元素一般都从新行开始.而当加入了css控制以后,块元素和内联元素的这种属性差异就不成为差异 了.比如,我们完全可以把内联元素cite加上

Swift学习笔记(11)--类与结构体

类与结构是编程人员在代码中会经常用到的代码块.在类与结构中可以像定义常量,变量和函数一样,定义相关的属性和方法以此来实现各种功能. 和其它的编程语言不太相同的是,Swift不需要单独创建接口或者实现文件来使用类或者结构.Swift中的类或者结构可以在单文件中直接定义,一旦定义完成后,就能够被直接其它代码使用. 注意:一个类的实例一般被视作一个对象,但是在Swift中,类与结构更像是一个函数方法,在后续的章节中更多地是讲述类和结构的功能性. 1.类和结构的异同 类和结构有一些相似的地方,它们都可以