三.数据库事物

160 阅读8分钟

一.什么叫数据库事务?

  • 事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
  • 一组逻辑操作单元:一个或多个DML操作。

二.事务处理的原则:

保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存 下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。

数据一旦提交,就不可回滚(以下都是为了解决再一次事物处理中数据过早提交的问题)

三.哪些操作会导致数据的自动提交

  • DDL操作一旦执行,都会自动提交
    • 解决方法: 不适用
  • DML默认情况下,一旦执行,就会自动提交。
    • 解决方法: set autocommit = false
  • 默认在关闭连接时,会自动的提交数据
    • 解决方法:等所有事物都执行完毕在关闭连接

四.案例分析

  • 分析1:将增删改的通用方法中,获取连接和关闭连接的操作删除,在外部获取连接和关闭连接,并把获取到的连接作为参数传入通用方法中;

作用:这样避免了每执行一次sql语句就关闭一次连接,即避免过早的提交数据,造成无法回滚数据的情况。(对应第三种解决方法)

//增删改操作的通用方法2(考虑事物)
public static int update(Connection conn,String sql,Object ...args){//sql中占位符的个数与可变形参的长度相同!
    //Connection conn = null;
    PreparedStatement ps = null;
    try {

        //conn = getConnection();
        //1.预编译sql语句,返回PreparedStatement的实例
        ps = conn.prepareStatement(sql);
        //2.填充占位符
        for(int i = 0;i < args.length;i++){
            ps.setObject(i + 1, args[i]);//小心参数声明错误!!
        }
        //3.执行
        return ps.executeUpdate();
    } catch (Exception e) {
        e.printStackTrace();
    }finally{
        //4.资源的关闭
        closeConnection(null, ps);
    }
    return 0;
}
  • 分析2:在外部获取连接并调用通用方法,使用 conn.setAutoCommit(false)来避免执行DML操作后提交数据。

作用:保证最后执行完成所有SQL语句后,再提交所有数据。若中途遇到异常(案例中模拟了网络异常),则可以做到数据回滚,避免了错误的产生。(对应第二种解决方法)

说明:再使用 set autocommit = false 之后一定要再改回去,因为若该连接为数据库连接池中获取的连接可能被重复使用。

@Test
public void testUpdateWithTx() {
   Connection conn = null;
   try {
      conn = JDBCUtils.getConnection();
      System.out.println(conn.getAutoCommit());//true
      //1.取消数据的自动提交
      conn.setAutoCommit(false);
      
      String sql1 = "update user_table set balance = balance - 100 where user = ?";
      update(conn,sql1, "AA");
      
      //模拟网络异常
      System.out.println(10 / 0);
      
      String sql2 = "update user_table set balance = balance + 100 where user = ?";
      update(conn,sql2, "BB");
      
      System.out.println("转账成功");
      
      //2.提交数据
      conn.commit();
      
   } catch (Exception e) {
      e.printStackTrace();
      //3.回滚数据
      try {
         conn.rollback();
      } catch (SQLException e1) {
         e1.printStackTrace();
      }
   }finally{
      try {
         conn.setAutoCommit(true);
      } catch (SQLException throwables) {
         throwables.printStackTrace();
      }
      JDBCUtils.closeConnection(conn, null);
   }
   
}

五.事务的ACID属性

  • 原子性(Atomicity) 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
  • 一致性(Consistency) 事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
  • 隔离性(Isolation) 事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的 数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰
  • 持久性(Durability) 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其 他操作和数据库故障不应该对其有任何影响。

5.1 数据库的并发问题

  • 对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种 并发问题:
    • 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的 内容就是临时且无效的。
    • 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字 段, 值就不同了。
    • 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如 果 T1 再次读取同一个表, 就会多出几行。
  • 数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。
  • 一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰 程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。

5.2 四种隔离级别

image.png

  • Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE。 Oracle 默认的事务隔离级别为: READ COMMITED 。
  • Mysql 支持 4 种事务隔离级别。Mysql 默认的事务隔离级别为: REPEATABLE READ。

5.3 在MySql中设置隔离级别

  • 每启动一个 mysql 程序, 就会获得一个单独的数据库连接. 每个数据库连接都有一个全局变量 @@tx_isolation, 表示当前的事务隔离级别。
  • 查看当前的隔离级别:
SELECT @@tx_isolation;
  • 设置当前 mySQL 连接的隔离级别:
set transaction isolation level read committed
  • 设置数据库系统的全局的隔离级别:
set global transaction isolation level read committed;

六.数据库连接池

6.1 数据库连接池技术的优点

  • 资源重用 由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一 方面也增加了系统运行环境的平稳性。
  • 更快的系统反应速度 数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均 已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销, 从而减少了系统的响应时间
  • 新的资源分配手段 对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库 连接数的限制,避免某一应用独占所有的数据库资源
  • 统一的连接管理,避免数据库连接泄漏 在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据 库连接操作中可能出现的资源泄露

6.2 多种开源的数据库连接池

  • JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由服务器 (Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:
    • DBCP 是Apache提供的数据库连接池。tomcat 服务器自带dbcp数据库连接池。速度相对c3p0较快,但因 自身存在BUG,Hibernate3已不再提供支持。
    • C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。hibernate官方推荐使用
    • Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一 点
    • BoneCP 是一个开源组织提供的数据库连接池,速度快
    • Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是 速度不确定是否有BoneCP快
  • DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池
  • DataSource用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速

数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个 数据源即可。

注意:当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但conn.close()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池

6.3 Druid(德鲁伊)数据库连接池

Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了 日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的 连接池之一。

  • 实现代码:
mport java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;

import javax.sql.DataSource;

import org.junit.Test;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;

public class DruidTest {

	public Connection getConnection() throws Exception{
		Properties pros = new Properties();
		
		InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");//读取配置文件
		
		pros.load(is);
		//创建连接池
		DataSource source = DruidDataSourceFactory.createDataSource(pros);
		//获取连接
                Connection conn = source.getConnection();
		return conn;	
	}
}

  • 配置文件
url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true 
username=root 
password=123456 
driverClassName=com.mysql.jdbc.Driver 

initialSize=10 
maxActive=20 
maxWait=1000 
filters=wall
  • 详细配置参数

image.png