事务(TRANSACTION)
是作为单个逻辑工作单元执行的一系列操作。
一、概念
① 这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;要么整体提交成功,整体提交失败。
② 事务是一个不可分割的工作逻辑单元。
- 例如:
银行转账过程就是一个事务。它需要两条UPDATE语句来完成,这两条语句是一个整体,如果其中任一条出现错误,则整个转账业务也应取消,两个账户中的余额应恢复到原来的数据。
事务必须具备以下四个属性,简称ACID 属性:
原子性(Atomicity):事务是一个完整的操作。事务的各步操作是不可分的(原子的);要么都执行,要么都不执行
就像一个连体婴儿,要走大家一起走
一致性(Consistency):当事务完成时(发出commit和rollback),数据必须处于一致状态。
例如:当某一用户对一个表中的内容进行修改了并且提交之后,其他用户查询该表能看到修改后的表的数据。
- 题外话:
oracle中的一致读概念举例说明:
①假设我们有一个数据量非常大非常大的student表,并且该表上没有建立索引;
②小李想要查询在这个表中学号等于15050418号的学生 sno=15050418 ,在10:00中小李发出命令 : select * from student where sno= 15050418; 预计该查询将会在10:30返回结果。
③在此期间,10:20分小刘对该数据库上sno=15050418的学生的信息进行列修改,进行了如下操作:update student set sage=19 where sno=15050418; commit;
那么小李得到的查询结果是修改前的还是修改后的呢?
答案:小李得到的查询结果当然是修改前的啦,因为小李在10:00发出的命令意在表明她想要查找10:00这个时候该表的sno=15050418的学生的信息,如果返回10:20处理后的信息,那就会产生不一致的效果 。但有人会好奇,说小刘10:20已经对结果进行修改了,怎会还是以前的查询结果呢?是因为oracle中包含回滚段,此时查询的结果是修改后表的前镜像。
- 题外话:
隔离性(Isolation):对数据进行修改的所有并发事务是彼此隔离的,这表明事务必须是独立的,它不应以任何方式依赖于或影响其他事务
- 举例说明: 小李在10:00的时候想要修改student表中的4号学生的年龄,故她发出命令:update student set sage=19 where sno=4; 另一用户小刘在不知情的情况下恰巧也要对同一张表中同一个学号的学生的年龄进行修改:update student set sage=27 where sno=4; 那么他发出命令时将一直处于等待状态,直到小李同学结束了(commit、rollback)她的命令。
- 永久性(Durability):事务完成后,它对数据库的修改被永久保持,事务日志能够保持事务的永久性
事务结束后,对事物的修改被永久保存下来了、
二、oracle 11g中事务的命令:
SQL server 中:
开始事务:begin transaction
提交事务:commit transaction
回滚事务:rollback transaction
oracle中:commit savepoint rollback
commit: 对事物的提交,执行此操作才使得事物的操作有用。
oracle中可以设置对事物的自动提交,语句为 set autocommit on
取消自动提交:set autocommit off
savepoint: 保存点,自我认为其功能就像书签一样,可以为其命名,具体语法为 savepoint spname;'
rollback 回退、回滚 撤销之前所做的操作,普通用法就是直接输入rollback;
高级用法:结合保存点, rollback to savepoint spname;
回退到之前定义的某个保存点时的状态。
举例,对一张表进行了增删改三个操作,并在每一个操作完成后建立一个保存点,命名为mark1、mark2、mark3,如果用户进行回退可以实行保存点建立时间倒叙的顺序来回退,即就是先回退到mark3 —>mark2 —>mark1 并且一个保存点处可以回退多次;
但是我们不能在回退到mark2的情况下执行回退到mark3的时刻,会报错 ORA-01086: 从未在此会话中创建保存点 ‘MARK1’ 或者该保存点无效,原因就是你在mark2的时间段中并未创mark3保存点,故你回不到该状态。保存点的回退不能正序回退,只能倒叙回退
三、Java中对oracle种事务的处理
①在高级语言中,对数据库表进行的操作,默认是自动提交的
在上篇博客中我们讲到用Java操纵oracle的技术,我们可以看到如下:
在部分语句中,我们可以写直接sql语句在其中,而并未出现commit等提交语句,而是执行该文件就直接在oracle中生效。
我们该如何避免自动提交呢? setAutoCommit(false)
只需在代码中获取connection对象之后加一行代码,再手工设置是否提交:
②在Java.sql包下有Savepoint这个接口来声明保存点。
四、事务处理例子
题目:
create table yggz(code int, salary number(7,2));
insert into yggz values(1, 1000);
insert into yggz values(2, 150);
commit;
完成任务:
如果1号员工的salary多于300元,则从1号员工的salary中减少300元,同时加到2号员工的salary上。
首先,我们先判断 这两条语句,一个减三百,一个加三百,要么同时执行,要么同时取消、故我们认为这是一个事物。
package com.oaj;
import java.sql.*; //引入java的sql包
public class TestTransaction {
String driver ="oracle.jdbc.driver.OracleDriver"; // 声明驱动
String strUrl ="jdbc:oracle:thin:@127.0.0.1:1521:orcl"; // 声明连接字符串 thin表示瘦客户端
//jdk自己定义的接口
Statement stmt= null ; // 不推荐使用 可能会被别人进行 应用在select1中
ResultSet rs = null;
Connection conn = null;
PreparedStatement ps = null; //有效地防止sql注入 应用select2中
float salary = 0f;
String sqlStr=null;
public static void main(String[] args) { // 主方法
new TestTransaction().insert1(); // 调用插入方法
}
public void insert1() { // stmt插入方法的定义 ,普通插入
try {
Class.forName(driver); //装载一个驱动
conn = DriverManager.getConnection(strUrl,"scott","scott") ; //建立一个连接
conn.setAutoCommit(false) ; // 事物要进行整体提交,故我们必须关闭自动提交
// 得到一号员工的salary
sqlStr= "select salary from yggz where code=1";
ps = conn.prepareStatement(sqlStr); // 实现
rs=ps.executeQuery(); //运行
while (rs.next()) { //只要你能够取出一号员工的工资
salary= rs.getFloat(1); // 给salary赋值,1表示第一列
}
if (salary<300) {
throw new RuntimeException("小于300元不能转账"); // 产生运行时异常,也就是在声明方法时不需要声明
}
// 将一号员工的工资减去300
sqlStr= "update yggz set salary=salary-300 where code=1";
ps = conn.prepareStatement(sqlStr); // 实现
rs=ps.executeQuery();
// 将二号员工的工资加上300
sqlStr= "update yggz set salary=salary+300 where code=2";
ps = conn.prepareStatement(sqlStr); // 实现
rs=ps.executeQuery();
conn.commit();
System.out.println("yeah~成功了~yeah~"); // 插入成功后打印
} catch (SQLException ex) { //捕获Sql异常
if (conn!=null) {
try {
conn.rollback();
System.out.println("失败了~");
} catch (Exception e) {
// TODO: handle exception
} //打印出来
}
} catch (Exception ex){
ex.printStackTrace(); //整体捕获其他异常
}
finally{ //在finally中关闭
try {
if (rs !=null) {
rs.close(); // 关闭掉
rs= null; // 置为空
} if (ps !=null){
ps.close();
ps= null;
} if (conn !=null){
conn.close();
conn= null;
}
} catch (SQLException ex) { //为避免在关闭过程中产生异常 出现问题
ex.printStackTrace();
}
}
}
}
题目2:
create table yggz(code int, salary number(7,2));
insert into yggz values(1, 1000);
insert into yggz values(2, 150);
commit;
完成任务:
如果1号员工的salary多于300元,则从1号员工的salary中减少300元,同时加到2号员工的salary上,但是还要确保转账后的1号员工的salary多于转账后的2号员工的salary。
提示:该问题牵扯到保存点的应用:
package com.oaj;
import java.sql.*; //引入java的sql包
public class TestTransactionLimit {
String driver ="oracle.jdbc.driver.OracleDriver"; // 声明驱动
String strUrl ="jdbc:oracle:thin:@127.0.0.1:1521:orcl"; // 声明连接字符串 thin表示瘦客户端
//jdk自己定义的接口
Statement stmt= null ; // 不推荐使用 可能会被别人进行 应用在select1中
ResultSet rs = null;
Connection conn = null;
PreparedStatement ps = null; //有效地防止sql注入 应用select2中
float salary = 0f;
float salary2 = 0f;
String sqlStr=null;
public static void main(String[] args) { // 主方法
new TestTransactionLimit().insert1(); // 调用插入方法
}
public void insert1() { // stmt插入方法的定义 ,普通插入
try {
Class.forName(driver); //装载一个驱动
conn = DriverManager.getConnection(strUrl,"scott","scott") ; //建立一个连接
conn.setAutoCommit(false) ; // 事物要进行整体提交,故我们必须关闭自动提交
// 得到一号员工的salary
sqlStr= "select salary from yggz where code=1";
ps = conn.prepareStatement(sqlStr); // 实现
rs=ps.executeQuery(); //运行
while (rs.next()) { //只要你能够取出一号员工的工资
salary= rs.getFloat(1); // 给salary赋值,1表示第一列
}
// 检查一号员工工资是否小于300
if (salary<300) {
throw new RuntimeException("小于300元不能转账"); // 产生运行时异常,也就是在声明方法时不需要声明
}
//设置保存点
Savepoint point1 = conn.setSavepoint("point1");
// 将一号员工的工资减去300
sqlStr= "update yggz set salary=salary-300 where code=1";
ps = conn.prepareStatement(sqlStr); // 实现
rs=ps.executeQuery();
// 将二号员工的工资加上300
sqlStr= "update yggz set salary=salary+300 where code=2";
ps = conn.prepareStatement(sqlStr); // 实现
rs=ps.executeQuery();
// 获取1、2号员工工资判断能否满足1号转账给2号以后能否保证其仍大于2号
sqlStr= "select salary from yggz where code=1";
ps = conn.prepareStatement(sqlStr); // 实现
rs=ps.executeQuery(); //运行
while (rs.next()) { //只要你能够取出一号员工的工资
salary= rs.getFloat(1); // 给salary赋值,1表示第一列
}
sqlStr= "select salary from yggz where code=2";
ps = conn.prepareStatement(sqlStr); // 实现
rs=ps.executeQuery(); //运行
while (rs.next()) { //只要你能够取出一号员工的工资
salary2= rs.getFloat(1); // 给salary赋值,1表示第一列
}
if (!(salary > salary2) ){
conn.rollback(point1);
System.out.println("没有划款成功");
}
else {
System.out.println("划款成功");
}
conn.commit();
System.out.println("操作完毕"); // 完成操作打印
} catch (SQLException ex) { //捕获Sql异常
if (conn!=null) {
try {
conn.rollback();
System.out.println("失败了~");
} catch (Exception e) {
// TODO: handle exception
} //打印出来
}
} catch (Exception ex){
ex.printStackTrace(); //整体捕获其他异常
}
finally{ //在finally中关闭
try {
if (rs !=null) {
rs.close(); // 关闭掉
rs= null; // 置为空
} if (ps !=null){
ps.close();
ps= null;
} if (conn !=null){
conn.close();
conn= null;
}
} catch (SQLException ex) { //为避免在关闭过程中产生异常 出现问题
ex.printStackTrace();
}
}
}
}