数据库事务及SPRING中使用注意事项

Posted 懒熊商城

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据库事务及SPRING中使用注意事项相关的知识,希望对你有一定的参考价值。

        村里小芳曾在某东从事电商行业,目前就职于某的金融行业。本文主要讲解事务、SPRING中的使用。


  1. 什么是事务?事务有哪些特性?

  2. 事务有哪些传播行为?

  3. 事务的隔离级别与脏读、幻读、不可重复读

  4. SPRING中事务的使用的注意事项。


1. 什么是事务?事务有哪些特性?

       事务(Transaction)是构成需求的多个非原子性动作,要么都完成,要么都回滚撤销。Spring事务管理模块是基于底层数据库本身的事务处理机制。掌握数据库的事务处理是用好Spring事务的基础。

      事务具有ACID特性。即,Atomic(原子性)、Consistency(一致性)、Isolation(隔离性)和Durability(持久性)。

        (1)原子性

        原子性是事务最基本的操作单元,组成事务的各原子操作要么全部成功动作,要么全部失败。不存在某些原子操作成功,某些失败的情况。当事务在执行过程中发生错误,会通过mysql的undo log实现事务的回滚,保证事务的原子性。

       (2)一致性

        一致性是指事务的执行前后,数据库的状态都都必须处于一致性状态。即,如果事务成功完成,则所有变化都将按正常变化,如果失败,所有变化都回滚到最初的状态。

     (3)隔离性(Isolation)

        隔离性是指在并发环境下,不同事物同时操作相同的数据时,每个事务都有各自的完整数据空间(每个线程的空间或Session的空间)。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,隔离性高的事务不会查看到中间状态的数据。不同事务隔离级别,可能会看到中间状态,即幻读。

      (4)持久性(Durability)

       指的事务成功提交后,它对数据库的更新就要永久保存下来。即使发生系统崩溃、重启数据库系统,数据库还能恢复到事务成功结束时的状态。MySQL是通过redo log和操作系统的磁盘与高速缓存的fsync刷盘行为实现的。

        事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。


  2.   事务有哪些传播行为?

        事务的传播行为是指多个事务方法调用时,如何定义方法间事务的传播。Spring定义了7种带事务方法的传播行为:

PROPAGATION_REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。也就是说业务方法需要在一个事务中运行,如果
业务方法被调用时,调用业务方法的行为(方法)已经处在一个事务中,那么就加入到该事务,否则为自己创建一个新的事务。
(默认传播属性)

PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。也就是说如果业务方法在某个事务范围内被调用,
则该方法成为该事务的一部分。如果业务方法在事务范围外被调用,则该方法在没有事务的环境下执行。

PROPAGATION_MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。也就是说业务方法只能在一个已经存在的事务中执行,
业务方法不能发起自己的事务。如果业务方法在没有事务的环境下被调用,容器就会抛出例外。

PROPAGATION_REQUIRESNEW:新建事务,如果当前存在事务,把当前事务挂起。也就是说业务方法被调用时,不管是否已经存在事务,
业务方法总会为自己发起一个新的事务。如果调用业务方法的行为(方法)已经运行在一个事务中,则原有事务会被挂起,新的事务
会被创建,直到业务方法执行结束,新事务才算结束,原先的事务才会恢复执行。

PROPAGATION_NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,就把当前事务挂起。也就是说业务方法不需要事务。如果
方法没有被关联到一个事务中,容器不会为它开启事务。如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,
原先的事务便会恢复执行。

PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。也就是说业务方法绝对不能在事务范围内执行。如果业务
方法在某个事务中执行,容器会抛出例外,只有业务方法没有关联到任何事务,才能正常执行。

PROPAGATION_NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务, 则按REQUIRED属性执行。
它使用了一个单独的事务, 这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对
DataSourceTransactionManager事务管理器起效。


3.  事务的隔离级别

        根据不同的事务隔离级别会出现不同的查询现象,大概包括三类。

脏读:一个事务读取到另一事务未提交的更新数据。当一个事务对数据进行了修改,但是该修改没有提交到数据库中,这时,另外一个事务访问到该数据,并使用了该数据。因为这个数据是还没有提交的数据, 那么另
外一个事务读到的这个数据是脏数据,依据脏数据所做的操作也可能是不正确的。

不可重复读:在同一事务中,多次读取同一数据返回的结果存在不同。也就是说,后续读取可以读到另一事务已提交的更新数据。“可重复读”在同一事务中多次读取数据时,能够保证所读数据一样,也就是说,后续读取不能读取到另一事务已提交的更新数据。

幻读:一个事务先后读取一个范围的记录,但两次读取的纪录数不同,我们称之为幻象读(两次执行同一条 select 语句会出现不同的结果,第二次读会增加一数据行,并没有说这两次执行是在同一个事务中)。

        数据库系统事务的隔离级别有:

(1)read uncommited:是最低的事务隔离级别,它允许另外一个事务可以看到这个事务未提交的数据,即出现脏读

(2)read commited:保证一个事物提交后才能被另外一个事务读取。另外一个事务不能读取该事物未提交的数据。即解决了脏读,但出现不可重复读

(3)repeatable read:这种事务隔离级别可以防止脏读,不可重复读。但是可能会出现幻读。它除了保证一个事务不能被另外一个事务读取未提交的数据之外还避免了以下情况产生(不可重复读)。MySQL通过间隙锁解决了不可重复读。

(4)serializable:这是花费最高代价但最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读之外,还避免了幻读。


4.  SPRING中事务的使用的注意事项。

Spring有两种事务操作方式

(1)采用编程式事务。

(2)采用注解声明式事务。

(1)编程式事务依赖于Spring事务管理的两个核心类。分别式:

        a. PlatformTransactionManager

        b.TransactionTemplate(推荐使用)

           a. PlatformTransactionManager的使用  

1. 先注入DataSource到事务管理器中。

<!--DataSourceTransactionManager位于org.springframework.jdbc.datasource包下,数据源事务管理类,提供对单个javax.sql.DataSource数据源的事务管理,主要用于JDBC,Mybatis框架事务管理。--><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource" /></bean> 


2. 使用

@Resource private PlatformTransactionManager txManager; ...... //定义事务隔离级别,传播行为, DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); //事务状态类,通过PlatformTransactionManager的getTransaction方法根据事务定义获取;获取事务状态后,Spring根据传播行为来决定如何开启事务 TransactionStatus status = txManager.getTransaction(def); jdbcTemplate = new JdbcTemplate(dataSource); int i = jdbcTemplate.queryForInt(COUNT_SQL); System.out.println("表中记录总数:"+i); try {  jdbcTemplate.update(INSERT_SQL, "1");  txManager.commit(status); //提交status中绑定的事务  } catch (RuntimeException e) {  txManager.rollback(status);  //回滚  }

        b.TransactionTemplate(推荐使用)

        TransactionTemplate模板类用于简化事务管理,事务管理由模板类定义,而具体操作需要通过TransactionCallback回调接口或TransactionCallbackWithoutResult回调接口指定,通过调用模板类的参数类型为TransactionCallback或TransactionCallbackWithoutResult的execute方法来自动享受事务管理。
TransactionTemplate模板类使用的回调接口:
TransactionCallback:通过实现该接口的“T doInTransaction(TransactionStatus status) ”方法来定义需要事务管理的操作代码;
TransactionCallbackWithoutResult:继承TransactionCallback接口,提供“void doInTransactionWithoutResult(TransactionStatus status)”便利接口用于方便那些不需要返回值的事务操作代码。

 public void testTransactionTemplate(){ TransactionTemplate transactionTemplate = new TransactionTemplate(txManager); transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus arg0) { jdbcTemplate.execute(CREATE_TABLE_SQL); jdbcTemplate.update(INSERT_SQL, "test"); } }); String COUNT_ALL = "select count(*) from test";  Number count = jdbcTemplate.queryForInt(COUNT_ALL); Assert.assertEquals(1, count.intValue());  jdbcTemplate.execute(DROP_TABLE_SQL); }


(2)采用注解声明式事务。

        声明式事务声明式事务操作简单,无侵入式,与业务代码分开。声明式事务也有两种实现方式。

1. 采用Spring的<tx:advice>定义事务通知与AOP配置实现。

2. 通过@Transactional实现。

1. 采用Spring的<tx:advice>定义事务通知与AOP配置实现


<!-- <tx:advice>定义事务通知,用于指定事务属性,其中“transaction-manager”属性指定事务管理器,并通过<tx:attributes>指定具体需要拦截的方法 <tx:method>拦截方法,其中参数有: name:方法名称,将匹配的方法注入事务管理,可用通配符 propagation:事务传播行为, isolation:事务隔离级别定义;默认为“DEFAULT” timeout:事务超时时间设置,单位为秒,默认-1,表示事务超时将依赖于底层事务系统; read-only:事务只读设置,默认为false,表示不是只读; rollback-for:需要触发回滚的异常定义,可定义多个,以“,”分割,默认任何RuntimeException都将导致事务回滚,而任何Checked Exception将不导致事务回滚; no-rollback-for:不被触发进行回滚的 Exception(s);可定义多个,以“,”分割; --><tx:advice id="advice" transaction-manager="transactionManager"> <tx:attributes> <!-- 拦截save开头的方法,事务传播行为为:REQUIRED:必须要有事务, 如果没有就在上下文创建一个 --> <tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED" timeout="" read-only="false" no-rollback-for="" rollback-for=""/> <!-- 支持,如果有就有,没有就没有 --> <tx:method name="*" propagation="SUPPORTS"/> </tx:attributes></tx:advice>

<!-- 定义切入点,expression为切人点表达式,如下是指定impl包下的所有方法,具体以自身实际要求自定义 --><aop:config> <aop:pointcut expression="execution(* com.kaizhi.*.service.impl.*.*(..))" id="pointcut"/> <!--<aop:advisor>定义切入点,与通知,把tx与aop的配置关联,才是完整的声明事务配置 --> <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/></aop:config>


2.  采用@Transactional 注意点

1.@Transactional 注解由于原理决定了他只能作用于public方法中,(当然可以在类上注解),而这里改为private,将不会读取该注解。

2.必须要将方法写到另一个类中或者通过生成代理类的方式,而且要通过spring的注入方式进行调用才可以。

3.<tx:annotation-driven>一共有四个属性如下,平时这个我们并没注意

mode:指定Spring事务管理框架创建通知bean的方式。可用的值有proxy和aspectj。前者是默认值,表示通知对象是个JDK代理;后者表示Spring AOP会使用AspectJ创建代理。

proxy-target-class:如果为true,Spring将创建子类来代理业务类;如果为false,则使用基于接口的代理。(如果使用子类代理,需要在类路径中添加CGLib.jar类库) 

order:如果业务类除事务切面外,还需要织入其他的切面,通过该属性可以控制事务切面在目标连接点的织入顺序。 

transaction-manager:指定到现有的PlatformTransaction Manager bean的引用,通知会使用该引用 。

4. Spring团队的建议是你在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。你当然可以在接口上使用 @Transactional 注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是不能继承的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。因此,请接受Spring团队的建议并且在具体的类上使用 @Transactional 注解。

        当我们采用容器事务时(容器事务大都基于JTA),容器会为我们初始化一个会话并维持该会话到事务结束。下面对MySQL实例,进程,线程,Session与Connection、事务之间的关系如下图所示。

1.  实例(进程or线程?)

(1)MySQL是单进程多线程(而Oracle等是多进程),也就是说MySQL实例在系统上表现就是一个服务进程,即进程(通过多种方法可以创建多实例,再安装一个端口号不同的mysql,或者通过workbench来新建一个端口号不同的 服务器实例等),该架构类似于SQL Server和Windows版本的Oracle;

(2)MySQL实例是线程和内存组成,实例才是真正用于操作数据库文件的(MySQL数据库是由一些列物理文件组成,类似于frm、MYD、MYI、ibd结尾的文件);

(3)一般情况下一个实例操作一个或多个数据库(Oracle一个实例对应一个数据库);集群情况下多个实例操作一个或多个数据库。

注:在实例启动的时候MySQL会读取配置文件,类似于Oracle的spfile文件,不同的是Oracle如果找不到参数文件会启动失败,MySQL如果找不到配置文件则会按照默认参数设置启动实例。

2.  MySQL中的database、instance、session关系

    Mysql中建立一个会话,不是和具体的数据库相连接,而是跟某个instance建立会话(每个会话可以使用不同的用户身份)。

     而一个实例可以操作多个数据库,故一个会话(在操作系统概念里,会话即是线程)可以操作一个实例上的多个数据库。

3.  connection和session的定义和区别:

(1)连接(connection)是一个物理的概念,它指的是一个通过网络建立的客户端和专有服务器(Dedicated Server)或调度器(Shared Server)的一个网络连接。

(2)会话(session)是一个逻辑的概念,它是存在于实例中。

      注:创建一个连接(connection)实际上是在某个实例(instance,或者说是进程)中创建一个或多个线程。

      两者关系:

(1)一个连接可以拥有多个会话也可以没有会话(实际上,一条连接上的各个会话可以使用不同的用户身份),同一个连接上的不同会话之间不会相互影响。

4.  会话、事务、线程之间的关系

(1)会话可以创建多个事务

        比如:使用客端连接数据库,这样你就可以执行很多个事务了

(2)一个事务只能由一个会话产生

        在数据库里的事务,如果在执行的SQL都是由会话发起的,哪怕是自动执行的JOB也是由系统会话发起的。

(3)一个事务可能会产生一个或多个线程

        比如RMAN备份,是可以创建多个线程可加快备份速度

(4)一个线程在同一时间内只能执行一个事务

      而一个线程,在没结束当前事务是无法释放资源来执行第二个事务。




以上是关于数据库事务及SPRING中使用注意事项的主要内容,如果未能解决你的问题,请参考以下文章

Spring 事务管理Transactional使用注意事项

spring annotation(事务)使用注意事项

Spring针对事务处理提供哪两种事务编程模式。

JDBC的架构设计

Spring事务源码分析专题JdbcTemplate使用及源码分析

Spring事务使用注意事项