JDBC学习:事务
什么是事务
数据库中一些操作的集合是一个独立的单元,事务就是构成单一逻辑工作单位的集合。
为什么需要事务
事务是为解决数据安全操作提出的,事务控制实际上就是控制数据的安全访问。
比如:银行转帐业务,账户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整个事务。
连接对象获取和关闭的时机
现状:
- 连接对象的获取和销毁比较浪费时间
- 一个事务中多个操作,若每个操作都生成一个连接对象,多用户同时连接数据库时,访问效率会非常的低。
需求:
- 一个事务中的多个操作应该用同一个连接对象控制,不然无法实现提交和回滚。一个事务开始时获得连接对象,一个事务结束时关闭连接。
解决:
- 将连接对象的申请交给专门的连接管理类
事务申请连接对象时,通过连接管理类申请,而不再通过DriverManager申请 - 一个事务一定是由一个线程完成的,使用线程局部变量获得同一个连接对象
接口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(); } } } } }

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Java图形化:布局方式
布局方式 FlowLayout:流布局 BorderLayout:边框布局 GridLayout:网格布局 FlowLayout(流布局) 像Word打字,组件从左向右排列,一列排满后自动换下一行。组件默认居中对齐,可以设置左/右对齐。流布局会维持组件的原始大小。流布局是JPanel(面板)的默认布局。容器可以使用setLayout()方法改变布局。 示例代码: import java.awt.FlowLayout; import javax.swing.JButton; import javax.swing.JFrame; public class TestLowLayout { public static void main(String[] args) { JFrame jf = new JFrame("流布局Demo"); FlowLayout fl = new FlowLayout(); jf.setLayout(fl); JButton jb1 = new JButton("按钮1"); JButton jb2 = new JButton("按钮2"); jf.add(jb...
- 下一篇
Java学习:反射
反射 使用反射可以直接获取class字节码文件中的类型、属性、方法。 演示代码: 新建一个名为User的类作为反射的操作对象 public class User { private int id; private String name; private String password; public User() { } /** * @return the id */ public int getId() { return id; } /** * @return the name */ public String getName() { return name; } /** * @return the password */ public String getPassword() { return password; } /** * @param id the id to set */ public void setId(int id) { this.id = id; } /** * @param name the name to set */ public void setName...
相关文章
文章评论
共有0条评论来说两句吧...