JDBC事务(学习分享)

JDBC事务

什么是JDBC事务

JDBC是Java数据库连接相关的API,所以Java的事务管理也是通过这个API进行的。

JDBC的核心是Connection接口。JDBC的事务管理是基于Connection接口来实现的,通过Connection对象进行事务管理。JDBC对事物的处理规则,必须是基于同一个Connection对象的。

JDBC进行事务管理的三个方法

  • setAutoCommit():设置自动提交,方法中需要传入一个Boolean类型的参数,true(默认)为自动提交,false为手动提交。
connection.setAutoCommit(false)//标识开启事务
  • commit():提交结束事务。提交事务的所有操作,对数据的操作写入数据库,事务正常结束。
  • rollback():回滚结束事务。事务执行中出现异常,不能继续执行,撤销该事务已完成的所有操作,回到事务开始的状态。

保存点(SavePoint)

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

当指向撤销事务中的部分操作时可以使用保存点SvarPoint。

事务的隔离级别

SQL标准定义了四类隔离级别,包括了一些具体规则,用来限定事务内外的那些改变是可见的,那些是不可见的。
低级别的隔离级一般支持更高的并发处理,且拥有更低的系统开销。

  • Read Uncommitted(读取未提交内容)(最低级别)

所有事物都可以看到其他未提交事务的执行结果。
实际中很少使用,与其他隔离级别性能差不多。
读取未提交的数据,会产生脏读(dirty read)。

  • Read Committed(读取提交内容)(锁行)

大多数数据库默认的隔离级别(像Oracle,但不是MySQL的默认级别)。
一个事务只能看见已提交事务所作的改变。
会产生不可重复读(Nonrepeatable Read)。

  • Repeatable Read(可重读)

MySQL的默认事务隔离级别。
确保同一事物的多个实例在并发读取数据时,会看到同样的数据。
会产生幻读(Phantom Read)。
InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
如果检索条件有索引(包括主键索引)的时候,默认加锁方式是next-key锁;如果检索条件没有索引,更新数据时会锁住整张表。一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读。

  • Serializable(可串行化)(锁整张表)

最高的隔离级别。
通过强制事务排序,是他们不能相互冲突,来解决幻读问题。
在每个读的数据行上加共享锁。
会导致大量的超时现象和锁竞争。

隔离级别由低到高:
在这里插入图片描述

总结:

隔离级别越高,越能保证数据的完整性和一致性,但对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Commited,他能避免脏读,而且具有较号的并发性能;尽管会导致不可重复读、幻读等并发问题,在可能出现这些问题的场合,可以通过应用程序采用的悲观锁或乐观锁控制。
大多数数据库的默认隔离级别是Read Commited,比如SQL server和Oracle。
MySQL的默认隔离级别是Repeatable read。

并发问题实例:

  • 脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。
  • 不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。
  • 幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。

JDBC操作流程是什么

  • 1、获取JDBC连接
  • 2、声明SQL
  • 3、预编译SQL
  • 4、执行SQL
  • 5、处理结果集
  • 6、释放结果集
  • 7、释放Statement
  • 8、提交事务
  • 9、处理异常并回滚事务
  • 10、释放JDBC连接

获取JDBC连接数据库

在数据库连接之前,首先要加载待连接的数据库驱动到JVM,通过java.lang.Class类的静态方法forName(String className)实现,成功加载后,会将Driver类的实例注册到DriverManager类中。

static {
	//访问数据库的连接对象
	Connection conn = null;
	try{
		Class.forName("com.mysql.jdbc.Driver");//加载数据库驱动
		
		conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root","root");//连接数据库
	}catch(ClassNotFoundException e){
		e.printStackTrace();
	}
}

Statement创建(预编译SQL)

要执行SQL语句,必须获得java.sql.Statement实例,Statement实例分为以下3 种类型:

  • 执行静态SQL语句。通常通过Statement实例实现。
  • 执行动态SQL语句。通常通过PreparedStatement实例实现
  • 执行数据库存储过程。通常通过CallableStatement实例实现。
public static Statement getStmt(Connection conn){
	Connection conn = null;//访问数据库的连接对象
	PreparedStatement ps= null;//SQL语句的执行对象
	try {
		ps = conn.preparedStatement();
	} catch (SQLException e) {
		e.printStackTrace();
	}
	return stmt;
}

注意:

  • createStatement() :获取传输器(不能防SQL注入攻击)
  • prepareStatement(sql) :获取预编译的传输器(能防SQL注入攻击)

执行SQL

Statement接口提供了三种执行SQL语句的方法:executeQuery 、executeUpdate 和execute

  • ResultSet executeQuery(String sqlString):执行查询数据库的SQL语句 ,返回一个结果集(ResultSet)对象。
  • int executeUpdate(String sqlString):用于执行INSERT、UPDATE或 DELETE语句以及SQL DDL语句,如:CREATE TABLE和DROP TABLE等
  • execute(sqlString):用于执行返回多个结果集、多个更新计数或二者组合的 语句。
public static ResultSet executeQuery(Statement stmt,String sql){
	ResultSet rs = null;//查询返回的结果集
	try {
		rs = stmt.executeQuery(sql);//执行SQL
	} catch (SQLException e) {
		e.printStackTrace();
	}
	return rs; 
}

处理结果

两种情况:

  • 执行更新返回的是本次操作影响到的记录数。
  • 执行查询返回的结果是一个ResultSet对象。

ResultSet包含符合SQL语句中条件的所有行,并且它通过一套get方法提供了对这些行中数据的访问。

使用结果集(ResultSet)对象的访问方法获取数据。

while(rs.next()){             
	String name = rs.getString("name") ;        
	String pass = rs.getString(1) ; // 此方法比较高效         
}

关闭资源

操作完成以后要把所有使用的JDBC对象全都关闭,以释放JDBC资源,关闭顺序和声明顺序相反:

  • 关闭记录集
  • 关闭声明
  • 关闭连接对象
//关闭结果集
public static void closeRs(ResultSet rs){
	try {
		if(rs != null ){
			rs.close();
			rs = null;
		}
	} catch (SQLException e) {
		e.printStackTrace();
	} 
}
//关闭执行方法
public static void closeStmt(Statement stmt){
	try {
		if(stmt != null ){
			stmt.close();
			stmt = null;
		}
	} catch (SQLException e) {
		e.printStackTrace();
	} 
}
//关闭连接
public static void closeConn(Connection conn){
	try {
		if(conn != null ){
			conn.close();
			conn = null;
		}
	} catch (SQLException e) {
		e.printStackTrace();
	}
}

JDBC连接数据库实例:

package com.jdbc;
 
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
 
public class TransactionDemo {
 
    // 定义数据库的用户名
    private static final String USERNAME = "root";
    // 定义数据库的密码
    private static final String PASSWORD = "123456";
    // 定义数据库的驱动信息
    private static final String DRIVER = "com.mysql.jdbc.Driver";
    // 定义访问数据库的地址
    private static final String URL = "jdbc:mysql://localhost:3306/mydb";
 
    // 定义访问数据库的连接
    private static Connection connection;
    // 定义sql语句的执行对象
    private static PreparedStatement pstmt;
    // 定义查询返回的结果集合
    private static ResultSet resultSet;     
         
    public static void main(String[] args) throws SQLException {
             
    	try {
    		//加载数据库驱动
         	Class.forName(DRIVER);
         	//1、获取JDBC连接,连接到数据库
         	connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            //用事务,必须设置setAutoCommit false,表示手动提交
            connection.setAutoCommit(false);
            //设置事务的隔离级别。
            connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
            //2、声明SQL
            String sql1 = "insert into userinfo(username,pswd) values(?,?)";
            String sql2 = "update userinfo set pswd=? where username = ?";
            //3、预编译SQL(创建一个statement)
            pstmt = connection.prepareStatement(sql1);
            pstmt.setString(1, "CAROL");
            pstmt.setString(2, "123");
            //4、执行SQL
            pstmt.executeUpdate();
                 
            pstmt = connection.prepareStatement(sql2);
            pstmt.setString(1, "123456");
            pstmt.setString(2, "nicole");               
            pstmt.executeUpdate();
            //提交事务
            connection.commit();
        } catch (Exception e) {
                 
            // 若事务执行有异常,则事务回滚
            connection.rollback();
        }finally{  
            if (pstmt!=null) {
                pstmt.close();
            }
            if (connection!=null) {
                connection.close();
            }
        }
    }
}

JDBC和JTA事务的区别

简单来说,JDBC是单库事务,JTA是多库事务。

  • JDBC事务由Connnection对象控制管理,也就是说,事务管理实际上是在JDBC Connection中实现。事务周期限于Connection的生命周期。JDBC Connection 接口( java.sql.Connection )提供了两种事务模式:自动提交和手工提交。
    自动提交:缺省是自动提交。一条对数据库的更新(增/删/改)代表一项事务操作,操作成功后,系统将自动调用commit()来提交,否则将调用rollback()来回滚。
    手工提交:通过调用setAutoCommit(false)来禁止自动提交。这样就可把多个数据库操作的表达式作为一个事务,在操作完成后调 用commit()来进行整体提交,其中任何一个操作失败,都不会执行到commit(),并产生异常;此时可在异常捕获时调用rollback()进行回滚,以保持多次更新操作后,相关数据的一致性。
  • JDBC 事务的一个缺点是事务的范围局限于一个数据库连接。一个 JDBC 事务不能跨越多个数据库。
  • JTA(Java Transaction API)提供了跨数据库连接(或其他JTA资源)的事务管理能力。JTA事务管理则由JTA容器实现。
  • JTA提供了分布式事务的解决方案,严格的ACID。但是,标准的JTA方式的事务管理在日常开发中并不常用,因为他本身就是一个很笨重的API,通常只能在应用服务器环境下使用,因此会限制代码的复用性。

为什么要用连接池?

用户每次请求都需要向数据库获取连接,而数据库创建连接需要耗费较大的资源。
所以可以设置一个连接池,在程序启动时就初始化一批连接,在程序中共享,需要连接时从连接池获取,用完之后再还给连接池,这样就大大减少了连接的创建和销毁次数,提高程序的效率。

更多数据库连接池内容可以查看另一篇文章

参考:
https://blog.csdn.net/guanmao4322/article/details/84846647

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值