Spring07

事务

事务指逻辑上的⼀组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功。从⽽确保了数据的准确与安全。

事务的四⼤特性
1、原⼦性Atomicity:原⼦性是指事务是⼀个不可分割的⼯作单位,事务中的操作要么都发⽣,要么都不发⽣。从操作的⻆度来描述,事务中的各个操作要么都成功要么都失败。
2、⼀致性Consistency:事务必须使数据库从⼀个⼀致性状态变换到另外⼀个⼀致性状态。
3、隔离性Isolation:事务的隔离性是多个⽤户并发访问数据库时,数据库为每⼀个⽤户开启的事务,每个事务不能被其他事务的操作数据所⼲扰,多个并发事务之间要相互隔离。
4、持久性Durability:持久性是指⼀个事务⼀旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发⽣故障也不应该对其有任何影响。

事务的并发问题
1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
2、不可重复读:一个事务中两次读取同一行数据,但是这两次读取到的数据不一致
3、幻读:事务A读取数据时,未把事务B新增的数据读取到,像产生了幻觉一样
4、丢失更新:事务A覆盖了事务B已提交更新的数据,导致事务B的更新数据好像丢失了一样

数据库事务的底层实现:
依赖日志[undo log和redo log]和锁机制实现

事务的隔离级别
1、Read uncommitted读未提交。如果一个事务已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读此行数据,该隔离级别可以通过排他写锁,但是不排斥读线程实现。这样就避免了更新丢失,却可能出现脏读,也就是说事务B读取到了事务A未提交的数据,解决了更新丢失,但还是可能会出现脏读
2、Read committed读提交。如果是一个读事务,则允许其他事务读写,如果是写事务将会禁止其他事务访问该行数据,该隔离级别避免了脏读,但是可能出现不可重复读。事务A事先读取了数据,事务B紧接着更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。解决了更新丢失和脏读问题
3、Repeatable read可重复读。可重复读是指在一个事务内,多次读同一个数据,在这个事务还没结束时,其他事务不能访问该数据,这样就可以在同一个事务内两次读到的数据是一样的,因此称为是可重复读隔离级别,读取数据的事务将会禁止写事务,但允许读事务,写事务则禁止任何其他事务,这样避免了不可重复读和脏读,但是有时可能会出现幻读。读取数据的事务可以通过共享读锁和排他写锁实现。解决了更新丢失、脏读、不可重复读、但是还会出现幻读
4、Serializable可序化。提供严格的事务隔离,它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行,如果仅仅通过行级锁是无法实现序列化的,必须通过其他机制保证新插入的数据不会被执行查询操作的事务访问到。序列化是最高的事务隔离级别,同时代价也是最高的,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻读.解决了更新丢失、脏读、不可重复读、幻读

1、JDBC编程的事务实现
Connection conn=…;//获取数据库链接
try{
conn.setAutoCommit(false); //一般情况下使用JDBC是单语句单事务
//多条SQL语句
conn.commit();//手动提交事务
} catch(Exception e){
conn.rollback(); //回滚撤销操作
}

事务是应该添加在业务层还是添加在持久层?
事务是应该由业务所导致的,但是使用JDBC的事务需要依赖于Connection,同时Connection对象线程不安全,必须依赖于ThreadLocal实现层和层之间的传输问题

2、Spring的AOP可以将事务管理从业务层剥离出来—环绕通知
//模拟事务管理的原理

@Component
@Aspect
public class TransactionManagerAspect {
    private static ThreadLocal<Connection> ts=new ThreadLocal<>();
    @Autowired
    private DataSource ds;
    @Around("execution(* com.yan.biz.*.*(..))")
    public Object transaction(ProceedingJoinPoint pjp)throws Throwable{
        Object res=null;
        boolean bb=false;
        Connection connection=null;
        try{
            connection=ts.get();
            if(connection==null) {
                connection = ds.getConnection();
                ts.set(connection);  //将Connection绑定到ThreadLocal中,其它地方需要使用Connection时,必须从ThreadLocal中获取,不允许新建
                bb = connection.getAutoCommit();
                connection.setAutoCommit(false);
            }
            res=pjp.proceed();//继续执行
            connection.commit(); //手动提交
        }catch (Exception e){
            connection.rollback();  // 异常回滚
            throw e;
        }finally{
            if(connection!=null) {
                ts.remove();
                connection.setAutoCommit(bb);
                connection.close();
            }
        }
        return res;
    }
}

3、Spring tx就是Spring事务管理,是Spring为DB事务管理提供过的一种便捷的接入方式

Spring事务管理分为编程式和声明式两种。编程式事务指的是通过编码方式实现事务;声明式事务基于AOP,将具体的逻辑与事务处理解耦。

编程式事务: 事务管理代码由程序员自己编写。
声明式事务: 事务管理代码由第三方直接提供,程序员直接将其组装到功能中即可。

编程式事务管理
优点:控制粒度较细,事务的边界可见即所得,不存在类内部调用失效的暗坑。
缺点:侵入业务逻辑代码,可读性和维护性会有少许下降
可以使用事务管理器PlatformTransactionManager或者使用transactionTemplate调用execute或者executeWithoutResult方法两种编程方法

TransactionTemplate模板类用于简化事务管理,事务管理由模板类定义,而具体操作需要通过TransactionCallback回调接口或TransactionCallbackWithoutResult回调接口指定,通过调用模板类的参数类型为TransactionCallback或TransactionCallbackWithoutResult的execute方法来自动享受事务管理。

<!--为数据添加事务管理-->
<bean id = "txManager" class = "org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name = "dataSource" ref = "dataSource"/>
</bean>
<!--为事务管理器txManager创建transactionTemplate-->
<bean id = "transactionTemplate" class = "org.springframework.transaction.support.TransactionTemplate">
    <property name = "transactionManager" ref = "txManager"/>
</bean>

以匿名内部类的方式实现TransactionCallback接口,使用默认的事务提交和回流规则在业务代码中不需要显示调用任何事务处理的API

transactionTemplate.execute(new TransactionCallback<Object>() {
    public Object doInTransaction(TransactionStatus transactionStatus) {
        //删除表中的数据
        String sql = "delete from user" ;
        //添加数据
        String sql1 = "insert into user value(?,?,?)" ;
        Object [] param1 = {1,"yan",true} ;
        jdbcTemplate.update(sql) ;  先删除数据
        jdbcTemplate.update(sql1,param1) ;  添加数据
        //执行成功则没有事务回滚;如果出现异常则事务回滚!
        return null;
    }
}) ;

事务回滚有2种方式
1、在execute或者executeWithoutResult内部执行transactionStatus.setRollbackOnly();将事务状态标注为回滚状态,spring会自动让事务回滚
2、execute方法或者executeWithoutResult方法内部抛出任意异常即可回滚

事务提交方式
方法没有异常 并且 未调用过transactionStatus.setRollbackOnly();

TransactionTemplate类的实例是线程安全的,任何状态都不会被保存。TransactionTemplate实例的确会维护配置状态,所以当一些类选择共享一个单独的TransactionTemplate实例时,如果一个类需要使用不同配置的TransactionTemplate,如不同的隔离等级,那就需要创建和使用两个不同的TransactionTemplate。

声明式事务管理
优点:简单易用,不侵入业务逻辑代码,不会造成可读性和维护性的下降。
缺点:控制粒度较粗,容易造成大事务;类内部调用无法开启事务,容易掉坑。

声明式事务中属性

<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
        <tx:method name="*" propagation="REQUIRED" isolation="READ_COMMITTED" read-only="true"/>
    </tx:attributes>
</tx:advice>
<aop:config>
       <aop:pointcut id="txPointcut" expression="execution(* com.yan..*.*(..))" />
       <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" />
</aop:config>

name=””哪些方法需要有事务控制,支持*通配符

readonly=”boolean” 是否是只读事务.
如果为true,告诉数据库此事务为只读事务.数据化优化,会对性能有一定提升,所以只要是查询的方法,建议使用此数据.
如果为false(默认值),事务需要提交的事务.建议新增,删除,修改.

propagation 控制事务传播行为.
当一个具有事务控制的方法被另一个有事务控制的方法调用后,需要如何管理事务(新建事务?在事务中执行?把事务挂起?报异常?)
REQUIRED (默认值): 如果当前有事务,就在事务中执行,如果当前没有事务,新建一个事务.
SUPPORTS:如果当前有事务就在事务中执行,如果当前没有事务,就在非事务状态下执行.
MANDATORY:必须在事务内部执行,如果当前有事务,就在事务中执行,如果没有事务,报错.
REQUIRES_NEW:必须在事务中执行,如果当前没有事务,新建事务,如果当前有事务,把当前事务挂起.
NOT_SUPPORTED:必须在非事务下执行,如果当前没有事务,正常执行,如果当前有事务,把当前事务挂起.
NEVER:必须在非事务状态下执行,如果当前没有事务,正常执行,如果当前有事务,报错.
NESTED:必须在事务状态下执行.如果没有事务,新建事务,如果当前有事务,创建一个嵌套事务.

isolation=”” 事务隔离级别
在多线程或并发访问下如何保证访问到的数据具有完整性的.

并发访问的三个问题:
脏读:一个事务(A)读取到另一个事务(B)中未提交的数据,另一个事务中数据可能进行了改变,此时A事务读取的数据可能和数据库中数据是不一致的,此时认为数据是脏数据,读取脏数据过程叫做脏读.
不可重复读:要针对的是某行数据或行中某列,当事务A第一次读取事务后,事务B对事务A读取的数据进行修改,事务A中再次读取的数据和之前读取的数据不一致,过程不可重复读.
幻读:主要针对的操作是新增或删除。事务A按照特定条件查询出结果,事务B新增了一条符合条件的数据.事务A中查询的数据和数据库中的数据不一致的,事务A好像出现了幻觉,这种情况称为幻读.

DEFAULT: 默认值,由底层数据库自动判断应该使用什么隔离界别
READ_UNCOMMITTED: 可以读取未提交数据,可能出现脏读,不重复读,幻读.效率最高.
READ_COMMITTED:只能读取其他事务已提交数据.可以防止脏读,可能出现不可重复读和幻读.
REPEATABLE_READ: 读取的数据被添加锁,防止其他事务修改此数据,可以防止不可重复读.脏读,可能出现幻读.
SERIALIZABLE: 排队操作,对整个表添加锁.一个事务在操作数据时,另一个事务等待事务操作完成后才能操作这个表.最安全的,效率最低的.

Oracle默认READ_COMMITTED,MySQL默认REPEATABLE_READ。如果隔离等级不足可以考虑引入乐观锁
所谓的乐观锁就是在执行时并不直接使用锁,而是引入一些特殊的列,例如版本号时间戳
update tb_account set balance=balance-1 where id=12 条件后还要附加一个额外的列,用于存储数据的版本号或者时间戳
update tb_account set balance=balance-1,version=version+1 where id=12 and version=3 受影响行数为0

rollback-for=”异常类型全限定路径” 当出现什么异常时需要进行回滚
no-rollback-for=”” 当出现什么异常时不滚回事务

事务管理注解
@EnableTransactionManagement:开启spring事务管理功能
@Transaction:将其加在需要spring管理事务的类、方法、接口上,只会对public方法有效。

@Service
@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
public class AccountServImpl implements IAccountServ{
    ....
    @Transactional(readOnly = false,propagation = Propagation.REQUIRED)
    public void zhuan(Long sourceId, Long targetId, Double num) {
        ...}
}

xml配置

<bean id="tm" class="org.springframework.jdbc.support.JdbcTransactionManager"
    p:dataSource-ref="dataSource"/>
<tx:annotation-driven transaction-manager="tm"/>

Spring提供的@Transaction注解事务管理内部利用环绕通知TransactionInterceptor实现事务的开启及关闭。
@Transactional实现原理
1、事务开始时,通过AOP机制,生成一个代理connection对象,并将其放入DataSource实例的某个与DataSourceTransactionManager相关的某处容器中。
在接下来的整个事务中,客户代码都应该使用该connection连接数据库,执行所有数据库命令[不使用该connection连接数据库执行的数据库命令,在本事务回滚的时候得不到回滚](物理连接connection逻辑上新建一个会话session;DataSource与TransactionManager配置相同的数据源)
2、事务结束时,回滚在第1步骤中得到的代理connection对象上执行的数据库命令,然后关闭该代理connection对象(事务结束后,回滚操作不会对已执行完毕的SQL操作命令起作用)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值