Java 事务总结

Java 事务

概念:

通常观念认为,事务仅与数据库相关。

事务必须服从 ISO/IEC 所制定的 ACID 原则。

事务特性

  1. 原子性:事务执行过程中的任何失败都将导致事务所做的修改失效

    对于数据修改,要么全部执行,要么全部不执行
    
  2. 一致性:当事务执行失败时,所有被该事务影响的数据都应该恢复到事务执行前的状态。

    事务执行前后,数据状态保持一致性(例如转账,转账之后两人总金额不变)
    
  3. 隔离性:在事务执行过程中对数据的修改,在事务提交之前对其他事务不可见

    一个事务的处理不能影响另一个事务的处理
    
  4. 持久性:事务处理结束,其效果在数据库中持久化

    即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作
    

通俗理解,事务是一组原子操作单元,从数据库角度说,就是一组 SQL 指令。

事务是为了解决数据安全操作提出的,事务控制实际上就是控制数据的安全访问。

事务的类型:

JDBC 事务

用 Connection 对象控制,JDBC 的 Connection 接口提供了两种事务模式:自动提交、手工提交。

Connection : java.sql.Connection

JDBC 为使用 Java 进行数据库的事务操作提供了最基本的支持。可以将多个 SQL 语句放到同一个事务中,保证其 ACID 特性。

优点: API 比较简单,可以实现最基本的事务操作,性能也相对较好。

缺点:事务的范围局限于一个数据库连接。一个 JDBC 事务不能跨越多个数据库

涉及到多数据库的操作或者分布式场景,JDBC事务就无能为力了

简单代码实现:

public void JdbcTransfer() { 
    java.sql.Connection conn = null;
     try{ 
         String url = "jdbc:mysql://localhost:3306/test?user=root&password=root";  //定义连接数据库的url
         conn = DriverManager.getConnection(url); 
         // 将自动提交设置为 false,
         //若设置为 true 则数据库将会把每一次数据更新认定为一个事务并自动提交
         conn.setAutoCommit(false);

         // 获取一个执行、发送 SQL 语句的对象
         stmt = conn.createStatement(); 
         // 将 A 账户中的金额减少 500 
         stmt.execute("\
         update t_account set amount = amount - 500 where account_id = 'A'");
         // 将 B 账户中的金额增加 500 
         stmt.execute("\
         update t_account set amount = amount + 500 where account_id = 'B'");

         // 提交事务
         conn.commit();
         // 事务提交:转账的两步操作同时成功
     } catch(SQLException sqle){            
         try{ 
             // 发生异常,回滚在本事务中的操作
            conn.rollback();
             // 事务回滚:转账的两步操作完全撤销
             stmt.close(); 
             conn.close(); 
         }catch(Exception ignore){ 
			ignore.printStackTrace(); 
         } 
         sqle.printStackTrace(); 
     } 
}

JTA (Java Transaction API)事务

JTA 是一种高层的,与实现无关的,与协议无关的 API,应用程序和应用服务器可以使用 JTA来访问事务。

可以理解为 JTA 是我们使用事务必要的工具,可以帮我们解决分布式事务的问题。

如果计划用 JTA 界定事务,需要有一个实现 javax.sql.XADataSourcejavax.sql.XAConnectionjavax.sql.XAResource 接口的 JDBC 驱动程序。

这个实现这些接口的驱动程序可以参与 JTA 事务。

一个 XADataSource 对象就是一个 XAConnection 对象的工厂,XAConnection 是参与 JTA 事务的 JDBC 连接。

J2EE 应用程序使用 JNDI 查询数据源。

JNDI:是 J2EE 里面一个重要的规范,使用 JNDI 不用代码写一长串去连接数据库,只需要引入使用数据库的配置文件,便于解耦合。

使用 JTA 事务必须使用 XADataSource 来产生数据库连接,产生的连接为 XA 连接。

符合 XA 规范的相关接口类才能使用 JTA 事务。

XA(javax.sql.XAConnection)和非XA(javax.sql.Connection)连接的区别在于:XA可以参与JTA事务,而且不支持自动提交

相关代码引用:

public void JtaTransfer() { 
        javax.transaction.UserTransaction tx = null;
        java.sql.Connection conn = null;
         try{ 
             tx = (javax.transaction.UserTransaction) context.lookup("java:comp/UserTransaction");  //取得JTA事务,本例中是由Jboss容器管理
             javax.sql.DataSource ds = (javax.sql.DataSource) context.lookup("java:/XAOracleDS");  //取得数据库连接池,必须有支持XA的数据库、驱动程序  
             tx.begin();
            conn = ds.getConnection();

             // 将自动提交设置为 false,
             //若设置为 true 则数据库将会把每一次数据更新认定为一个事务并自动提交
             conn.setAutoCommit(false);

             stmt = conn.createStatement(); 
             // 将 A 账户中的金额减少 500 
             stmt.execute("\
             update t_account set amount = amount - 500 where account_id = 'A'");
             // 将 B 账户中的金额增加 500 
             stmt.execute("\
             update t_account set amount = amount + 500 where account_id = 'B'");

             // 提交事务
             tx.commit();
             // 事务提交:转账的两步操作同时成功
         } catch(SQLException sqle){            
             try{ 
                 // 发生异常,回滚在本事务中的操做
              tx.rollback();
                 // 事务回滚:转账的两步操作完全撤销
                 stmt.close(); 
                 conn.close(); 
             }catch(Exception ignore){ 

             } 
             sqle.printStackTrace(); 
         } 
     }

JTA 提供了UserTransaction用来处理事务,不过不光依靠这个类就能将 JDBC 操作转换为 JTA 操作,还是需要符合 XA 协议的接口。

JTA 优点很明显,可以解决分布式事务,可是有很多缺点,实现复杂、笨重、限制代码复用性。

容器事务

容器事务主要是 J2EE 应用服务器提供的,容器事务大多是基于 JTA 完成,这是一个基于 JNDI 的,相当复杂的 API 实现。

通过 EJB 容器提供的容器事务管理机制(CMT)完成功能。

ETJ: 将Java Beans的运行正式从客户端领域扩展到服务器领域

声明式事务

通过 AOP(面向切面)方式在方法前使用编程式事务的方法开启事务,在方法后提交或回滚。用配置文件的方法或注解方法(如:@Transactional)控制事务。

注解在方法上是方法自动启动事务,在类上是整个类中的方法都使用事务

Spring 开启事务注解

<tx:annotation-driven transaction-manager="transactionManager"/>

SpringBoot 事务处理

只需要简单的 @Transactional 注解可实现

简单代码实现:

@Service
public class PersonService {
    @Resource
    private PersonMapper personMapper;

    @Resource
    private CompanyMapper companyMapper;
	//rollbackFor:触发回滚的异常,默认是RuntimeException和Error
    //isolation: 事务的隔离级别,默认是Isolation.DEFAULT也就是数据库自身的默认隔离级别
    @Transactional(rollbackFor = {RuntimeException.class, Error.class})
    public void saveOne(Person person) {
        Company company = new Company();
        company.setName("tenmao:" + person.getName());
        companyMapper.insertOne(company);
        personMapper.insertOne(person);
    }
}
编程式事务

手动开启、提交、回滚事务。

Spring 编程式事务实现需要在配置文件中配置相应的事务处理器,用 AOP、事务标签、注解方式实现事务。

例如 JDBC 事务处理器

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
     <property name="dataSource" ref="dataSource" />
</bean>

由于我们使用较多的是 SpringBoot 开发,这里就不多对 Spring 配置文件进行更多的阐述了。

无论是 Spring 还是 SpringBoot 实现编程式事务都需要在实体类中获取事务处理器对象,手动进行开启事务和提交事务各种操作。

SpringBoot 简单代码实现:

TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
        try {
            for (int i = 0; i < 10; i++) {
                Post post = new Post();
                if (i == 5) { post.setContent("dddddddddddddddddddddddddddddddddddddddddddd");
                } else
                post.setContent("post" + i);
                post.setWeight(i);
                postService.save(post);

            }
            transactionManager.commit(status);
        } catch (Exception e) {
            transactionManager.rollback(status);
            e.printStackTrace();
        }

事务的属性

事务的隔离级别 (Transaction Isolation Levels)

JDBC 对事务的支持

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

TRANSACTION_NONE JDBC    		驱动不支持事务
TRANSACTION_READ_UNCOMMITTED 	允许脏读、不可重复读和幻读。
TRANSACTION_READ_COMMITTED 		禁止脏读,但允许不可重复读和幻读。
TRANSACTION_REPEATABLE_READ 	禁止脏读和不可重复读,单运行幻读。
TRANSACTION_SERIALIZABLE 		禁止脏读、不可重复读和幻读。

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

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

当 auto-commit 为 false 时,每个事务都必须显示调用 commit 方法进行提交,或者显示调用 rollback 方法进行回滚。auto-commit 默认为 true。

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

Connection接口的 setSavepointreleaseSavepoint 方法可以设置和释放保存点。

JDBC 规范虽然定义了事务的以上支持行为,但是各个 JDBC 驱动,数据库厂商对事务的支持程度可能各不相同。

事务的传播行为

PROPAGATION_REQUIRED:	如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
PROPAGATION_SUPPORTS:	支持当前事务,如果当前没有事务,就以非事务方式执行。

PROPAGATION_MANDATORY:	支持当前事务,如果当前没有事务,就抛出异常。

PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。

PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

PROPAGATION_NEVER:		以非事务方式执行,如果当前存在事务,则抛出异常。 

虽然有7种,但是常用的就第一种REQUIRED和第四种REQUIRES_NEW

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

脏读(dirty read):一个事务读取了另一个事务尚未提交的数据

不可重复读(non-repeatable read):一个事务的操作导致另一个事务前后两次读取到不同的数据

幻读(phantom read):一个事务的操作导致另一个事务前后两次查询的结果数据量不同

不可重复读是同一数据不一样,幻读是数据量不一样

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值