• 12
  • 12
分享
  • Spring事务和MySQL事务详解面试——软件测试圈
  • 恬恬圈 2021-04-14 11:50:29 字数 8009 阅读 1742 收藏 12

数据库事务

事务是什么

是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合。

事务的四大特性

  • 原子性:事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做

  • 一致性:事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。

  • 隔离性:一个事务的执行不能被其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。

  • 持续性:也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。

MySQL事务隔离级别

隔离级别隔离级别的值导致的问题
Read-Uncommitted0导致脏读
Read-Committed1避免脏读,允许不可重复读和幻读
Repeatable-Read2MySQL默认的隔离级别。避免脏读,不可重复读,允许幻读
Serializable3串行化读,事务只能一个一个执行,避免了脏读、不可重复读、幻读。执行效率慢,使用时慎重
  • 脏读

一个事务对数据进行了增删改查,但是未提交事务。另一个事物可以读取到未提交的数据,如果第一个事务进行了回滚,那么第二个事务就读到了脏数据。

例子:

领导给张三发工资,10000元已打到张三账户,但该事务还未提交,正好这时候张三去查询工资,发现10000元已到账。这时领导发现张三工资算多了5000元,于是回滚了事务,修改了金额后将事务提交。最后张三实际到账的只有5000元。

  • 不可重复度

一次事务发生了两次读操作,两个读操作之间发生了另一个事务对数据修改操作,这时候第一次和第二次读到的数据不一致。

不可重复度关注点在数据更新和删除,通过行级锁可以实现可重复读的隔离级别。

例子:

张三需要转正1000元,系统读到卡余额有2000元,此时张三老婆正好需要转正2000元,并且在张三提交事务前把2000元转走了,当张三提交转账是系统提示余额不足。

  • 幻读

幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行。

相对于不可重复读,幻读更关注其它事务的新增数据。通过行级锁可以避免不可重复读,但无法解决幻读的问题,想要解决幻读,只能通过Serializable隔离级别来实现。

例子:

张三老婆准备打印张三这个月的信用卡消费记录,经查询发现消费了两次共1000元,而这时张三刚按摩完准备结账,消费了1000元,这时银行记录新增了一条1000元的消费记录。当张三老婆将消费记录打印出来时,发现总额变为了2000元,这让张三老婆很诧异。

  • 串行化读

Serializable是最高的隔离级别,性能很低,一般很少用。在这级别下,事务是串行顺序执行的,不仅避免了脏读、不可重复读,还避免了幻读。

查看MySQL当前事务隔离级别

MySQLInnoDB默认的事务隔离级别为REPEATABLE-READ

mysql>select@@tx_isolation;
+-----------------+
|@@tx_isolation|
+-----------------+
|REPEATABLE-READ|
+-----------------+

MySQL默认操作模式为自动提交模式

除非显示的开启一个事务,否则每个查询都被当成一个单独的事务自动执行。可以通脱设置autocommit的值改变默认的提交模式。

  • 查看当前提交模式

mysql>showvariableslike'autocommit';
+---------------+-------+
|Variable_name|Value|
+---------------+-------+
|autocommit|ON|
+---------------+-------+
  • 关闭自动提交。0代表关闭,1代表开启。

mysql>setautocommit=0;
QueryOK,0rowsaffected(0.00sec)
mysql>showvariableslike'autocommit';
+---------------+-------+
|Variable_name|Value|
+---------------+-------+
|autocommit|OFF|
+---------------+-------+

JDBC处理事务

Connectionconnection=null;
PreparedStatementpstmt=null;
ResultSetresultSet=null;
try{
Class.forName("com.mysql.jdbc.Driver");
connection=DriverManager.getConnection("jdbc:mysql://localhost:3306/dbname?characterEncoding=utf-8","username","password");
connection.setAutoCommit(false);
//others......
connection.commit();
}catch(Exceptione){
connection.rollback();
}finally{
connection.setAutoCommit(true);
//closeconnection
}

Spring事务

Spring事务本质是对数据库事务的支持,如果数据库不支持事务(例如MySQL的MyISAM引擎不支持事务),则Spring事务也不会生效。

Spring的事务传播

事务传播行为是指一个事务方法A被另一个事务方法B调用时,这个事务A应该如何处理。事务A应该在事务B中运行还是另起一个事务,这个有事务A的传播行为决定。

事务传播属性定义TransactionDefinition

intPROPAGATION_REQUIRED=0;
intPROPAGATION_SUPPORTS=1;
intPROPAGATION_MANDATORY=2;
intPROPAGATION_REQUIRES_NEW=3;
intPROPAGATION_NOT_SUPPORTED=4;
intPROPAGATION_NEVER=5;
intPROPAGATION_NESTED=6;
常量名称常量解释
PROPAGATION_REQUIRED支持当前事务,如果当前没有事务,就新建一个事务。这是Spring默认的事务的传播。
PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。新建的事务将和被挂起的事务没有任何关系,是两个独立的事务,外层事务失败回滚之后,不能回滚内层事务执行的结果,内层事务失败抛出异常,外层事务捕获,也可以不处理回滚操作。使用JtaTransactionManager作为事务管理器
PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。使用JtaTransactionManager作为事务管理器
PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。

PROPAGATION_REQUIRED

如果存在一个事务,则支持当前事务,如果没有事务则开启事务。

1.png

如下例子,单独调用methodB时,当前上下文没有事务,所以会开启一个新的事务。

调用methodA方法时,因为当前上下文不存在事务,所以会开启一个新的事务。当执行到methodB时,methodB发现当前上下文有事务,因此就加入到当前事务A中来。

@Transactional(propagation=Propagation.REQUIRED)
publicvoidmethodA(){
methodB();
//dosomething
}
@Transactional(propagation=Propagation.REQUIRED)
publicvoidmethodB(){
//dosomething
}

PROPAGATION_SUPPORTS

如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。

2.png

单独的调用methodB时,methodB方法是非事务的执行的。当调用methdA时,methodB则加入了methodA的事务中,事务地执行。

@Transactional(propagation=Propagation.REQUIRED)
publicvoidmethodA(){
methodB();
//dosomething
}
//事务属性为SUPPORTS
@Transactional(propagation=Propagation.SUPPORTS)
publicvoidmethodB(){
//dosomething
}

PROPAGATION_MANDATORY

如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。

3.png

当单独调用methodB时,因为当前没有一个活动的事务,则会抛出异常thrownewIllegalTransactionStateException(“Transactionpropagation‘mandatory’butnoexistingtransactionfound”)

当调用methodA时,methodB则加入到methodA的事务中,以事务方式执行。

@Transactional(propagation=Propagation.REQUIRED)
publicvoidmethodA(){
methodB();
//dosomething
}
@Transactional(propagation=Propagation.MANDATORY)
publicvoidmethodB(){
//dosomething
}

PROPAGATION_REQUIRES_NEW

使用PROPAGATION_REQUIRES_NEW,需要使用JtaTransactionManager作为事务管理器。

它会开启一个新的事务。如果一个事务已经存在,则先将这个存在的事务挂起。

4.png

从下面代码可以看出,事务B与事务A是两个独立的事务,互不相干。事务B是否成功并不依赖于事务A。如果methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所做的结果依然被提交。而除了methodB之外的其它代码导致的结果却被回滚了

@Transactional(propagation=Propagation.REQUIRED)
publicvoidmethodA(){
doSomeThingA();
methodB();
doSomeThingB();
//dosomethingelse
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
publicvoidmethodB(){
//dosomething
}

当调用methodA(),相当于

publicstaticvoidmain(){
TransactionManagertm=null;
try{
//获得一个JTA事务管理器
tm=getTransactionManager();
tm.begin();//开启一个新的事务
Transactionts1=tm.getTransaction();
doSomeThing();
tm.suspend();//挂起当前事务
try{
tm.begin();//重新开启第二个事务
Transactionts2=tm.getTransaction();
methodB();
ts2.commit();//提交第二个事务
}Catch(RunTimeExceptionex){
ts2.rollback();//回滚第二个事务
}finally{
//释放资源
}
//methodB执行完后,恢复第一个事务
tm.resume(ts1);
doSomeThingB();
ts1.commit();//提交第一个事务
}catch(RunTimeExceptionex){
ts1.rollback();//回滚第一个事务
}finally{
//释放资源
}
}

PROPAGATION_NOT_SUPPORTED

总是非事务地执行,并挂起任何存在的事务。

使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作为事务管理器。

5.png

PROPAGATION_NEVER

总是非事务地执行,如果存在一个活动事务,则抛出异常。

PROPAGATION_NESTED

如果一个活动的事务存在,则运行在一个嵌套的事务中。

如果没有活动事务,则按TransactionDefinition.PROPAGATION_REQUIRED属性执行。

这是一个嵌套事务,使用JDBC3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。需要JDBC驱动的java.sql.Savepoint类。使用PROPAGATION_NESTED,还需要把PlatformTransactionManager的nestedTransactionAllowed属性设为true(属性值默认为false)。

6.png

@Transactional(propagation=Propagation.REQUIRED)
methodA(){
doSomeThingA();
methodB();
doSomeThingB();
}
@Transactional(propagation=Propagation.NEWSTED)
methodB(){
//dosomething
}

单独调用methodB方法,则按REQUIRED属性执行。如果调用methodA方法,则相当于:

main(){
Connectioncon=null;
Savepointsavepoint=null;
try{
con=getConnection();
con.setAutoCommit(false);
doSomeThingA();
savepoint=con2.setSavepoint();
try{
methodB();
}catch(RuntimeExceptionex){
con.rollback(savepoint);
}finally{
//释放资源
}
doSomeThingB();
con.commit();
}catch(RuntimeExceptionex){
con.rollback();
}finally{
//释放资源
}
}

当methodB方法调用之前,调用setSavepoint方法,保存当前的状态到savepoint。如果methodB方法调用失败,则恢复到之前保存的状态。

需要注意的是,这时的事务并没有进行提交,如果后续的代码(doSomeThingB()方法)调用失败,则回滚包括methodB方法的所有操作。嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。

Spring事务的隔离级别

事务隔离级别定义TransactionDefinition

intISOLATION_DEFAULT=-1;
intISOLATION_READ_UNCOMMITTED=1;
intISOLATION_READ_COMMITTED=2;
intISOLATION_REPEATABLE_READ=4;
intISOLATION_SERIALIZABLE=8;
隔离级别解释
ISOLATION_DEFAULT这是个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应。
ISOLATION_READ_UNCOMMITTED这是事务最低的隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
ISOLATION_READ_COMMITTED保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。ISOLATION_REPEATABLE_READ
ISOLATION_SERIALIZABLE这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。

Spring事务基本配置样例

<aop:aspectj-autoproxyproxy-target-class="true"/>
<beanid="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<propertyname="dataSource"ref="dataSource"/>
</bean>
<tx:adviceid="transactionAdvice"transaction-manager="transactionManager">
<tx:attributes>
<tx:methodname="add*"propagation="REQUIRED"rollback-for="Exception,RuntimeException,SQLException"/>
<tx:methodname="remove*"propagation="REQUIRED"rollback-for="Exception,RuntimeException,SQLException"/>
<tx:methodname="edit*"propagation="REQUIRED"rollback-for="Exception,RuntimeException,SQLException"/>
<tx:methodname="login"propagation="NOT_SUPPORTED"/>
<tx:methodname="query*"read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:advisoradvice-ref="transactionAdvice"pointcut-ref="transactionPointcut"/>
<aop:aspectref="dataSource">
<aop:pointcutid="transactionPointcut"expression="execution(public*com.gupaoedu..*.service..*Service.*(..))"/>
</aop:aspect>
</aop:config>


作者:南方淮竹

原文链接:https://blog.csdn.net/weixin_48272905/article/details/108525283


  • 【留下美好印记】
    赞赏支持
登录 后发表评论
+ 关注

热门文章

    最新讲堂

      • 推荐阅读
      • 换一换
          •   死锁就是有一天你回家,拿着一把钥匙使劲往锁眼里面捅,结果钥匙断里面了,所以你就叫开锁师傅要开锁,结果锁给开死了,这就是死锁了。以上仅仅是玩笑话,以下步入正题。  什么是死锁?  要了解什么是死锁,要首先明白一点,锁是用来做什么?Java中的锁说白了,就是为了保证资源安全,确保一次仅有一个线程对共享资源进行修改。(以上仅为个人理解,如有问题,请评论讨论。)那死锁的概念就好理解了,就是有两个及以上的线程对同一个资源进行争夺,结果两个线程没有一个让步,并且没有任何的外力进行协调导致的一种僵局。  例1:马路上就只有一条道,刚好两辆车都到了,两司机开始吵架说,是我先到的,没有一个让的,而刚好这条路...
            0 0 1093
            分享
          • 1.1. Web技术演化 1.1.1. 静态页面在互联网最初开始的时候,Web网站的主要内容是静态的,由文字和图片组成,制作和表现形式也是以表格为主。当时的用户行为也非常简单,仅仅是浏览网页。1.1.2. 多媒体阶段随着技术的不断发展,音频、视频、Flash等多媒体技术诞生了。多媒体的加入使得网页变得更加生动形象,网页上的交互也给用户带来了更好的体验。1.1.3. CGI阶段渐渐的,多媒体已经不能满足人们的请求,于是CGI(Common Gateway Interface)应运而生。CGI定义了Web服务器与外部应用程序之间的通信接口标准,因此Web服务器可以通过CGI执行外部程序,...
            13 13 1628
            分享
          •   一直以来,在整个IT行业中,一说起软件测试这个工作,人们脑子中浮现的都是一群软件测试工程师用双手在键盘上或在手机上“点点点”的场景,所以很长一段时间,软件测试工程师都被戏称为“点点点”工程师。不过,现在0202年都过了这么久了,如果还抱着这种态度来看待“软件测试”这个职业,未免有点太过时了。这就跟前几年台湾人民觉得祖国大陆人民吃不起茶叶蛋一样,“Out的妈妈给Out开门,Out到家了”。  况且,现在的软件测试跟传统的软件测试相比已经发生了非常大的变化,不管是测试范围还是测试手段,都有很大的不同。所以,现在我们更专业的叫法是“测试开发工程师”,从这个名字就可以看出来,传统的点点点已经没有市...
            0 0 1016
            分享
          • 下载地址:http://jmeter.apache.org/download_jmeter.cgiJMeter基于Java开发,需要系统有安装JDK环境。解压后进入bin目录,点击jmeter.bat1、添加线程组:测试计划 -> 添加 -> Threads(User) -> 线程组,添加后进入如下界面:线程数:表示将模拟多少个用户进行测试。 Ramp-Up Period(in seconds):线程启动间隔,所有线程将在这个时间内依次启动。 循环次数:所有线程执行一次为一次循环。 Rame-Up Period(in seconds):...
            0 1 1866
            分享
          • 读者提问:冒烟测试怎么做?阿常回答:这个问题我从三方面来回答:1、什么是冒烟测试;2、为何做冒烟测试;3、怎么做冒烟测试。 一、什么是冒烟测试「冒烟测试」这一术语源自硬件行业。对一个硬件或硬件组件进行更改或修复后,直接给设备加电。如果没有冒烟,则该组件就通过了测试。在软件中,「冒烟测试」是一种针对软件版本包的快速基本功能验证策略,它是对软件基本功能进行确认验证的手段,并非对软件版本包的深入测试。冒烟测试是针对软件版本包进行详细测试之前的预测试,如果冒烟测试用例不能通过,则不必做进一步的测试。二、为何做冒烟测试提升软件测试效率。快速确认软件是否具备测试准入条件,避免正式测试阶段全面开展...
            0 0 1245
            分享
      • 51testing软件测试圈微信