为了账号安全,请及时绑定邮箱和手机立即绑定

JDBC学习:事务

标签:
Java MySQL Oracle

什么是事务

数据库中一些操作的集合是一个独立的单元,事务就是构成单一逻辑工作单位的集合。

为什么需要事务

事务是为解决数据安全操作提出的,事务控制实际上就是控制数据的安全访问。
比如:银行转帐业务,账户A给账户B转帐100元,需要账户A余额减100元,账户B余额加100元,两个需要同时发生。完成这种操作需要保证要么全部成功,要么全部失败。

什么是回滚

未能成功完成的事务成为中止事务,对中止事务造成的变更需要进行撤销处理,称为事务回滚。

事务的特性(ACID 原则)

  • 原子性(atomicity):对事务中的全部操作是不可分割的,要么全部完成,要么都不执行。
  • 一致性(consistency):事务执行之前和执行之后,数据库都必须处于一致性状态。
  • 隔离性(isolation):事务的执行不受其他事务的干扰,事务执行的中间结果对其他事务必须是透明的。
  • 持久性(durability):对于任意已提交的事务,系统必须保证该对数据库的改变不丢失,即使数据库出现故障。

Java JDBC 事务机制
比如有个业务:当我们修改一个信息后再去查询这个信息。这是一个简单的业务,实现起来也非常容易,但是当这个业务放在多线程高并发的平台下,问题自然就出现了。
比如当执行了一个修改后,在查询之前有一个线程也执行了修改语句,这时再执行查询,看到的信息就有可能和我们修改的不同。为了解决这一问题,就引入了引入JDBC事务机制。

如何操作

把事务操作设置为不自动提交,通过手动提交就能实现事务的处理。

搭建实验环境实验,在当前数据库中创建一个测试表:

CREATE TABLE tb (
	id INT UNSIGNED NOT NULL AUTO_INCREMENT KEY COMMENT '编号',
	name VARCHAR(20) NOT NULL COMMENT '姓名',
	sex VARCHAR(4) NOT NULL COMMENT '性别',
	phone VARCHAR(11) NOT NULL COMMENT '手机号码'
);

插入几条测试数据:

INSERT INTO tb
(name, sex, phone)
VALUES
("张三", "男", "139****2234"),
("李四", "女", "130****3239"),
("王二", "男", "136****1234"),
("小王", "女", "137****1735"),
("赵云", "男", "131****1255"),
("关羽", "男", "139****1930");

相关延伸

事务并发处理可能引起的问题

  • 脏读(dirty read):一个事务读取了另一个事务尚未提交的数据。
  • 不可重复读(non-repeatable read):一个事务的操作导致另一个事务前后两次读取到不同的数据。
  • 幻读(phantom read):一个事务的操作导致另一个事务前后两次查询到的结果数据量不同。

举例:

  • 事务A、B并发执行,当A事务update后,B事务select读取到A尚未提交的数据,此时A事务rollback,则B读取到的数据是无效的“脏”数据。
  • 当B事务select读取数据后,A事务update操作更改B事务select到的数据,此时B事务再次读取该数据,发现前后两次的数据不一样。
  • 当B事务select读取数据后,A事务insert或delete了一条满足A事务的select条件的记录,此时B事务再次select,发现查询到前不存在的数据(“幻影”),或者前面的某个记录不见了。

JDBC的事务支持

JDBC对事务的支持体现在三个方面:

1.自动提交模式(Auto-commit mode)

Connection提供了一个auto-commit的属性来指定事务何时结束。

  • a.当auto-commit为true时,当每个独立的SQL操作执行完毕,事务立即自动提交,也就是说每个SQL操作都是一个事务的。auto-commit默认为true

一个独立SQL操作什么时候什么执行完毕呢?
JDBC规范这样规定

对数据操作语言(DML如insert,update,delete)和数据定义语言(DDL如create,drop),语句一执行完就视为执行完毕。
对select语句,当与它关联的ResultSet对象关闭时,视为执行完毕。
对存储过程或者其他返回多个结果的语句,当与它关联的所有ResultSet对象全部关闭,所有update count(update,delete等语句操作影响的行数)和output parameter(存储过程的输出参数)都已经获取之后,视为执行完毕。

  • b.当auto-commit为false时,每个事务都必须显式调用commit方法进行提交,或者显式调用commit方法进行回滚。

2.事务隔离级别(Transaction Isolation Levels)

JDBC提供了5种不同的事务隔离级别。在Connection中进行了定义。

  • TRANSACTION_NONE:JDBC不支持事务
  • TRANSACTION_READ_UNCOMMITTED:允许脏读、不可重复读和幻读
  • TRANSACTION_READ_COMMITTED:禁止脏读,但允许不可重复读和幻读
  • TRANSACTION_REPEATABLE_READ:禁止脏读和不可重复读,单可以幻读
  • TRANSACTION_SERIALIZABLE:禁止脏读、不可重复读和幻读

3.保存点(SavePoint)

JDBC定义了SavePoint接口,提供在一个更细粒度的事务控制机制。当设置了一个保存点后,可以rollback到该保存点处的状态,而不是rollback整个事务。

连接对象获取和关闭的时机

现状:

  • 连接对象的获取和销毁比较浪费时间
  • 一个事务中多个操作,若每个操作都生成一个连接对象,多用户同时连接数据库时,访问效率会非常的低。

需求:

  • 一个事务中的多个操作应该用同一个连接对象控制,不然无法实现提交和回滚。一个事务开始时获得连接对象,一个事务结束时关闭连接。

解决:

  1. 将连接对象的申请交给专门的连接管理类
    事务申请连接对象时,通过连接管理类申请,而不再通过DriverManager申请
  2. 一个事务一定是由一个线程完成的,使用线程局部变量获得同一个连接对象

接口DataSource

该工厂用于提供到此 DataSource 对象所表示的物理数据源的连接。
作为 DriverManager 工具的替代项,DataSource 对象是获取连接的首选方法。

DataSource接口由驱动程序供应商实现。共有三种类型的实现:

  • 基本实现:生成标准的 Connection 对象
  • 连接池实现:生成自动参与连接池的 Connection 对象。此实现与中间层连接池管理器一起使用。
  • 分布式事务实现:生成一个 Connection 对象,该对象可用于分布式事务,大多数情况下总是参与连接池。此实现与中间层事务管理器一起使用,大多数情况下总是与连接池管理器一起使用。

方法:

  • getConnection():尝试建立与此 DataSource 对象所表示的数据源的连接。
  • getConnection(String username, String password):尝试建立与此 DataSource 对象所表示的数据源的连接。

实例:
先在src目录下新建配置文件config.properties:

driver=com.mysql.jdbc.Driver
url=jdbc\:mysql\://127.0.0.1\:3306/empmgs?useUnicode\=true&characterEncoding\=utf-8
username=root
password=root

实例代码:

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;

public class demo4 {
	static Properties prop = new Properties();
	static DataSource ds = null;
	static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
	private static Connection conn = null;
	private static Statement sm = null;
	/**
	 * 静态初始化块加载注册驱动
	 */
	static {
		try {
			prop.load(new FileInputStream("src/config.properties"));
			ds = BasicDataSourceFactory.createDataSource(prop);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	static public Connection getConnection(){
		try {
			//先看线程局部变量
			conn = threadLocal.get();
			if(conn == null){//线程局部变量中没有保存连接对象
				conn = ds.getConnection();
				threadLocal.set(conn);//设置连接对象到线程局部变量
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return conn;
	}
	public static void main(String[] args) {
		try{
			conn = getConnection ();
			sm = conn.createStatement();
			// 关闭自动提交
			conn.setAutoCommit(false);
			sm.executeUpdate("UPDATE tb SET name = '老王' WHERE id = 3 ");
			sm.executeUpdate("INSERT INTO tb (name, sex, phone) VALUES ('美丽','女','132****5555')");
			conn.commit();
			System.out.println("运行结束");
		} catch (Exception e) {
			try {
				conn.rollback();
			} catch (Exception ex) {
				e.printStackTrace();
			}
		} finally {
			if (sm != null) {
				try {
					sm.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}		
			if (conn != null) {
				try {
					conn.close();
					conn = null;
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
	}

}
点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消