2021-04-06

<div id="article_content" class="article_content clearfix">
        <link rel="stylesheet" href="https://csdnimg.cn/release/blogv2/dist/mdeditor/css/editerView/ck_htmledit_views-b5506197d8.css">
                <div id="content_views" class="htmledit_views">
                    <p id="main-toc"><strong>目录</strong></p> 
<p id="" style="margin-left:40px;"><a href="#t0" target="_self">一、事务</a></p> 
<p id="" style="margin-left:40px;"><a href="#t1" target="_self">二、@Transactional介绍</a></p> 
<p id="" style="margin-left:40px;"><a href="#t2" target="_self">三、@Transactional失效场景</a></p> 
<p id="" style="margin-left:80px;"><a href="#t3" target="_self">1、@Transactional 应用在非 public 修饰的方法上</a></p> 
<p id="" style="margin-left:80px;"><a href="#t4" target="_self">2、@Transactional 注解属性 propagation 设置错误</a></p> 
<p id="" style="margin-left:80px;"><a href="#t5" target="_self">3、@Transactional 注解属性 rollbackFor 设置错误</a></p> 
<p id="" style="margin-left:80px;"><a href="#t6" target="_self">4、同一个类中方法调用,导致@Transactional失效</a></p> 
<p id="" style="margin-left:80px;"><a href="#t7" target="_self">5、异常被你的 catch“吃了”导致@Transactional失效</a></p> 
<p id="" style="margin-left:80px;"><a href="#t8" target="_self">6、数据库引擎不支持事务</a></p> 
<p id="" style="margin-left:40px;"><a href="#t9" target="_self">总结</a></p> 
<hr id="hr-toc">
<p><code>@Transactional</code>&nbsp;注解相信大家并不陌生,平时开发中很常用的一个注解,它能保证方法内多个数据库操作要么同时成功、要么同时失败。使用<code>@Transactional</code>注解时需要注意许多的细节,不然你会发现<code>@Transactional</code>总是莫名其妙的就失效了</p> 
<h2 id="%E4%B8%80%E3%80%81%E4%BA%8B%E5%8A%A1"><a name="t0"></a><a name="t0"></a><strong>一、事务</strong></h2> 
<p>事务管理在系统开发中是不可缺少的一部分,<code>Spring</code>提供了很好事务管理机制,主要分为<code>编程式事务</code>和<code>声明式事务</code>两种。</p> 
<p><strong>编程式事务</strong>:是指在代码中手动的管理事务的提交、回滚等操作,代码侵入性比较强,如下示例:</p> 
<pre><code class="language-java hljs"><ol class="hljs-ln"><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="1"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-keyword">try</span>&nbsp;{</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="2"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">&nbsp;&nbsp;&nbsp;<span class="hljs-comment">//TODO&nbsp;something</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="3"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;transactionManager.commit(status);</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="4"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">}&nbsp;<span class="hljs-keyword">catch</span>&nbsp;(Exception&nbsp;e)&nbsp;{</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="5"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">&nbsp;&nbsp;&nbsp;&nbsp;transactionManager.rollback(status);</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="6"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">throw</span>&nbsp;<span class="hljs-keyword">new</span>&nbsp;InvoiceApplyException(<span class="hljs-string">"异常失败"</span>);</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="7"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">}</div></div></li></ol></code><div class="hljs-button {2}" data-title="复制" data-report-click="{&quot;spm&quot;:&quot;1001.2101.3001.4259&quot;}" onclick="hljs.copyCode(event)"></div></pre> 
<p><strong>声明式事务</strong>:基于<code>AOP</code>面向切面的,它将具体业务与事务处理部分解耦,代码侵入性很低,所以在实际开发中声明式事务用的比较多。声明式事务也有两种实现方式,一是基于<code>TX</code>和<code>AOP</code>的xml配置文件方式,二种就是基于@Transactional注解了。</p> 
<pre><code class="language-java hljs"><ol class="hljs-ln"><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="1"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-meta">@Transactional</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="2"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-meta">@GetMapping("/test")</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="3"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">test</span><span class="hljs-params">()</span> </span>{</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="4"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> </div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="5"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        <span class="hljs-keyword">int</span> insert = cityInfoDictMapper.insert(cityInfoDict);</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="6"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    }</div></div></li></ol></code><div class="hljs-button {2}" data-title="复制" data-report-click="{&quot;spm&quot;:&quot;1001.2101.3001.4259&quot;}" onclick="hljs.copyCode(event)"></div></pre> 
<h2 id="%E4%BA%8C%E3%80%81%40Transactional%E4%BB%8B%E7%BB%8D"><a name="t1"></a><a name="t1"></a>二、@Transactional介绍</h2> 
<p>1、@Transactional注解可以作用于哪些地方?</p> 
<p>@Transactional 可以作用在<code>接口</code>、<code>类</code>、<code>类方法</code>。</p> 
<ul><li> <p><strong>作用于类</strong>:当把@Transactional 注解放在类上时,表示所有该类的<code>public</code>方法都配置相同的事务属性信息。</p> </li><li> <p><strong>作用于方法</strong>:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。</p> </li><li> <p><strong>作用于接口</strong>:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效</p> </li></ul>
<pre><code class="language-java hljs"><ol class="hljs-ln"><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="1"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-meta">@Transactional</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="2"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-meta">@RestController</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="3"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-meta">@RequestMapping</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="4"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MybatisPlusController</span> </span>{</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="5"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-meta">@Autowired</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="6"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-keyword">private</span> CityInfoDictMapper cityInfoDictMapper;</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="7"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> </div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="8"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-meta">@Transactional(rollbackFor = Exception.class)</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="9"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-meta">@GetMapping("/test")</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="10"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">test</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>{</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="11"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        CityInfoDict cityInfoDict = <span class="hljs-keyword">new</span> CityInfoDict();</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="12"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        cityInfoDict.setParentCityId(<span class="hljs-number">2</span>);</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="13"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        cityInfoDict.setCityName(<span class="hljs-string">"2"</span>);</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="14"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        cityInfoDict.setCityLevel(<span class="hljs-string">"2"</span>);</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="15"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        cityInfoDict.setCityCode(<span class="hljs-string">"2"</span>);</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="16"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        <span class="hljs-keyword">int</span> insert = cityInfoDictMapper.insert(cityInfoDict);</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="17"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        <span class="hljs-keyword">return</span> insert + <span class="hljs-string">""</span>;</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="18"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    }</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="19"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">}</div></div></li></ol></code><div class="hljs-button {2}" data-title="复制" data-report-click="{&quot;spm&quot;:&quot;1001.2101.3001.4259&quot;}" onclick="hljs.copyCode(event)"></div></pre> 
<p>2、@Transactional注有哪些属性?</p> 
<p>propagation属性</p> 
<p><code>propagation</code>&nbsp;代表事务的传播行为,默认值为&nbsp;<code>Propagation.REQUIRED</code>,其他的属性信息如下:</p> 
<ul><li> <p><code>Propagation.REQUIRED</code>:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。<strong>(</strong>&nbsp;也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务&nbsp;<strong>)</strong></p> </li><li> <p><code>Propagation.SUPPORTS</code>:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。</p> </li><li> <p><code>Propagation.MANDATORY</code>:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。</p> </li><li> <p><code>Propagation.REQUIRES_NEW</code>:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。<strong>(</strong>&nbsp;当类A中的 a 方法用默认<code>Propagation.REQUIRED</code>模式,类B中的 b方法加上采用&nbsp;<code>Propagation.REQUIRES_NEW</code>模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为<code>Propagation.REQUIRES_NEW</code>会暂停 a方法的事务&nbsp;<strong>)</strong></p> </li><li> <p><code>Propagation.NOT_SUPPORTED</code>:以非事务的方式运行,如果当前存在事务,暂停当前的事务。</p> </li><li> <p><code>Propagation.NEVER</code>:以非事务的方式运行,如果当前存在事务,则抛出异常。</p> </li><li> <p><code>Propagation.NESTED</code>&nbsp;:和 Propagation.REQUIRED 效果一样。</p> </li></ul>
<p>isolation 属性</p> 
<p><code>isolation</code>&nbsp;:事务的隔离级别,默认值为&nbsp;<code>Isolation.DEFAULT</code>。</p> 
<ul><li> <p>Isolation.DEFAULT:使用底层数据库默认的隔离级别。</p> </li><li> <p>Isolation.READ_UNCOMMITTED</p> </li><li> <p>Isolation.READ_COMMITTED</p> </li><li> <p>Isolation.REPEATABLE_READ</p> </li><li> <p>Isolation.SERIALIZABLE</p> </li></ul>
<p>timeout 属性</p> 
<p><code>timeout</code>&nbsp;:事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。</p> 
<p>readOnly 属性</p> 
<p><code>readOnly</code>&nbsp;:指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。</p> 
<p>rollbackFor 属性</p> 
<p><code>rollbackFor</code>&nbsp;:用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。</p> 
<p><strong>noRollbackFor</strong>属性**</p> 
<p><code>noRollbackFor</code>:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。</p> 
<h2 id="%E4%BA%8C%E3%80%81%40Transactional%E5%A4%B1%E6%95%88%E5%9C%BA%E6%99%AF"><a name="t2"></a><a name="t2"></a><strong>三、@Transactional失效场景</strong></h2> 
<p>接下来我们结合具体的代码分析一下哪些场景下,@Transactional 注解会失效。</p> 
<h3 id="1%E3%80%81%40Transactional%20%E5%BA%94%E7%94%A8%E5%9C%A8%E9%9D%9E%20public%20%E4%BF%AE%E9%A5%B0%E7%9A%84%E6%96%B9%E6%B3%95%E4%B8%8A"><a name="t3"></a><a name="t3"></a>1、@Transactional 应用在非 public 修饰的方法上</h3> 
<p>如果<code>Transactional</code>注解应用在非<code>public</code>&nbsp;修饰的方法上,Transactional将会失效。</p> 
<p><img alt="" height="434" src="https://img-blog.csdnimg.cn/img_convert/f5a9b29d1ea434fd588fef8ecf375a65.png" width="671"></p> 
<p>之所以会失效是因为在Spring AOP 代理时,如上图所示&nbsp;<code>TransactionInterceptor</code>&nbsp;(事务拦截器)在目标方法执行前后进行拦截,<code>DynamicAdvisedInterceptor</code>(CglibAopProxy 的内部类)的 intercept 方法或&nbsp;<code>JdkDynamicAopProxy</code>&nbsp;的 invoke 方法会间接调用&nbsp;<code>AbstractFallbackTransactionAttributeSource</code>的&nbsp;<code>computeTransactionAttribute</code>&nbsp;方法,获取Transactional 注解的事务配置信息。</p> 
<pre><code class="language-java hljs"><ol class="hljs-ln" style="width:721px"><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="1"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-function"><span class="hljs-keyword">protected</span> TransactionAttribute <span class="hljs-title">computeTransactionAttribute</span><span class="hljs-params"><span class="hljs-params">(Method method,</span></span></span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="2"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-params">    Class&lt;?&gt; targetClass)</span> {</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="3"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        <span class="hljs-comment">// Don't allow no-public methods as required.</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="4"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        <span class="hljs-keyword">if</span> (allowPublicMethodsOnly() &amp;&amp; !Modifier.isPublic(method.getModifiers())) {</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="5"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="6"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">}</div></div></li></ol></code><div class="hljs-button {2}" data-title="复制" data-report-click="{&quot;spm&quot;:&quot;1001.2101.3001.4259&quot;}" onclick="hljs.copyCode(event)"></div></pre> 
<p>此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。</p> 
<p><strong>注意:</strong><strong><code>protected</code>、<code>private</code>&nbsp;修饰的方法上使用&nbsp;<code>@Transactional</code>&nbsp;注解,虽然事务无效,但不会有任何报错,这是我们很容犯错的一点。</strong></p> 
<h3 id="2%E3%80%81%40Transactional%20%E6%B3%A8%E8%A7%A3%E5%B1%9E%E6%80%A7%20propagation%20%E8%AE%BE%E7%BD%AE%E9%94%99%E8%AF%AF"><a name="t4"></a><a name="t4"></a>2、@Transactional 注解属性 propagation 设置错误</h3> 
<p>这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发生回滚。</p> 
<p><code>TransactionDefinition.PROPAGATION_SUPPORTS</code>:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。<br><code>TransactionDefinition.PROPAGATION_NOT_SUPPORTED</code>:以非事务方式运行,如果当前存在事务,则把当前事务挂起。<br><code>TransactionDefinition.PROPAGATION_NEVER</code>:以非事务方式运行,如果当前存在事务,则抛出异常。</p> 
<h3 id="3%E3%80%81%40Transactional%20%E6%B3%A8%E8%A7%A3%E5%B1%9E%E6%80%A7%20rollbackFor%20%E8%AE%BE%E7%BD%AE%E9%94%99%E8%AF%AF"><a name="t5"></a><a name="t5"></a>3、@Transactional 注解属性 rollbackFor 设置错误</h3> 
<p><code>rollbackFor</code>&nbsp;可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查<code>unchecked</code>异常(继承自&nbsp;<code>RuntimeException</code>的异常)或者&nbsp;<code>Error</code>才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定&nbsp;<strong>rollbackFor</strong>属性。</p> 
<p><img alt="" src="https://img-blog.csdnimg.cn/img_convert/f8821439d68d03e39a392b3e5fb870ab.png"></p> 
<pre><code class="hljs groovy"><ol class="hljs-ln"><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="1"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-number">1</span><span class="hljs-comment">// 希望自定义的异常可以进行回滚</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="2"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-number">2</span><span class="hljs-meta">@Transactional</span>(propagation= Propagation.REQUIRED,rollbackFor= MyException.<span class="hljs-keyword">class</span></div></div></li></ol></code><div class="hljs-button {2}" data-title="复制" data-report-click="{&quot;spm&quot;:&quot;1001.2101.3001.4259&quot;}" onclick="hljs.copyCode(event)"></div></pre> 
<p>若在目标方法中抛出的异常是&nbsp;<code>rollbackFor</code>&nbsp;指定的异常的子类,事务同样会回滚。Spring源码如下:</p> 
<pre><code class="language-java hljs"><ol class="hljs-ln"><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="1"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-number">1</span><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getDepth</span><span class="hljs-params">(Class&lt;?&gt; exceptionClass, <span class="hljs-keyword">int</span> depth)</span> </span>{</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="2"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-number">2</span>        <span class="hljs-keyword">if</span> (exceptionClass.getName().contains(<span class="hljs-keyword">this</span>.exceptionName)) {</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="3"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-number">3</span>            <span class="hljs-comment">// Found it!</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="4"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-number">4</span>            <span class="hljs-keyword">return</span> depth;</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="5"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-number">5</span>}</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="6"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-number">6</span>        <span class="hljs-comment">// If we've gone as far as we can go and haven't found it...</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="7"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-number">7</span>        <span class="hljs-keyword">if</span> (exceptionClass == Throwable.class) {</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="8"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-number">8</span>            <span class="hljs-keyword">return</span> -<span class="hljs-number">1</span>;</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="9"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-number">9</span>}</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="10"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-number">10</span><span class="hljs-function">return <span class="hljs-title">getDepth</span><span class="hljs-params">(exceptionClass.getSuperclass()</span>, depth + 1)</span>;</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="11"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-number">11</span>}</div></div></li></ol></code><div class="hljs-button {2}" data-title="复制" data-report-click="{&quot;spm&quot;:&quot;1001.2101.3001.4259&quot;}" onclick="hljs.copyCode(event)"></div></pre> 
<h3 id="4%E3%80%81%E5%90%8C%E4%B8%80%E4%B8%AA%E7%B1%BB%E4%B8%AD%E6%96%B9%E6%B3%95%E8%B0%83%E7%94%A8%EF%BC%8C%E5%AF%BC%E8%87%B4%40Transactional%E5%A4%B1%E6%95%88"><a name="t6"></a><a name="t6"></a>4、同一个类中方法调用,导致@Transactional失效</h3> 
<p>开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,<span style="color:#f33b45;">它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。</span></p> 
<p>那为啥会出现这种情况?其实这还是由于使用<code>Spring AOP</code>代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由<code>Spring</code>生成的代理对象来管理。</p> 
<pre><code class="language-java hljs"><ol class="hljs-ln"><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="1"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-comment">//@Transactional</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="2"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-meta">@GetMapping("/test")</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="3"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-function"><span class="hljs-keyword">private</span> Integer <span class="hljs-title">A</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>{</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="4"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        CityInfoDict cityInfoDict = <span class="hljs-keyword">new</span> CityInfoDict();</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="5"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        cityInfoDict.setCityName(<span class="hljs-string">"2"</span>);</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="6"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        <span class="hljs-comment"><span class="hljs-comment">/**</span></span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="7"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-comment">         * B 插入字段为 3的数据</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="8"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-comment">         */</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="9"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        <span class="hljs-keyword">int</span> insert=insertB();</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="10"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        <span class="hljs-comment"><span class="hljs-comment">/**</span></span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="11"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-comment">         * A 插入字段为 2的数据</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="12"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-comment">         */</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="13"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        <span class="hljs-keyword">int</span> insert = cityInfoDictMapper.insert(cityInfoDict);</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="14"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> </div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="15"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        <span class="hljs-keyword">return</span> insert;</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="16"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    }</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="17"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> </div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="18"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-meta">@Transactional()</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="19"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-function"><span class="hljs-keyword">public</span> Integer <span class="hljs-title">insertB</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>{</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="20"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        CityInfoDict cityInfoDict = <span class="hljs-keyword">new</span> CityInfoDict();</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="21"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        cityInfoDict.setCityName(<span class="hljs-string">"3"</span>);</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="22"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        cityInfoDict.setParentCityId(<span class="hljs-number">3</span>);</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="23"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> </div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="24"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        <span class="hljs-keyword">return</span> cityInfoDictMapper.insert(cityInfoDict);</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="25"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    }</div></div></li></ol></code><div class="hljs-button {2}" data-title="复制" data-report-click="{&quot;spm&quot;:&quot;1001.2101.3001.4259&quot;}" onclick="hljs.copyCode(event)"></div></pre> 
<h3 id="5%E3%80%81%E5%BC%82%E5%B8%B8%E8%A2%AB%E4%BD%A0%E7%9A%84%20catch%E2%80%9C%E5%90%83%E4%BA%86%E2%80%9D%E5%AF%BC%E8%87%B4%40Transactional%E5%A4%B1%E6%95%88"><a name="t7"></a><a name="t7"></a>5、异常被你的 catch“吃了”导致@Transactional失效</h3> 
<p>这种情况是最常见的一种@Transactional注解失效场景</p> 
<pre><code class="hljs php"><ol class="hljs-ln"><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="1"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> @Transactional</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="2"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-keyword">private</span> <span class="hljs-keyword">Integer</span> A() throws <span class="hljs-built_in">Exception</span> {</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="3"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        <span class="hljs-keyword">int</span> insert = <span class="hljs-number">0</span>;</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="4"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        <span class="hljs-keyword">try</span> {</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="5"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">            CityInfoDict cityInfoDict = <span class="hljs-keyword">new</span> CityInfoDict();</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="6"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">            cityInfoDict.setCityName(<span class="hljs-string">"2"</span>);</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="7"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">            cityInfoDict.setParentCityId(<span class="hljs-number">2</span>);</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="8"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">            <span class="hljs-comment"><span class="hljs-comment">/**</span></span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="9"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-comment">             * A 插入字段为 2的数据</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="10"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-comment">             */</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="11"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">            insert = cityInfoDictMapper.insert(cityInfoDict);</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="12"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">            <span class="hljs-comment"><span class="hljs-comment">/**</span></span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="13"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-comment">             * B 插入字段为 3的数据</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="14"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-comment">             */</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="15"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">            b.insertB();</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="16"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        } <span class="hljs-keyword">catch</span> (<span class="hljs-built_in">Exception</span> e) {</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="17"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">            e.printStackTrace();</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="18"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        }</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="19"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    }</div></div></li></ol></code><div class="hljs-button {2}" data-title="复制" data-report-click="{&quot;spm&quot;:&quot;1001.2101.3001.4259&quot;}" onclick="hljs.copyCode(event)"></div></pre> 
<p>如果B方法内部抛了异常,而A方法此时try catch了B方法的异常,那这个事务还能正常回滚吗?</p> 
<p>答案:不能!</p> 
<p>会抛出异常:</p> 
<p>因为当<code>ServiceB</code>中抛出了一个异常以后,<code>ServiceB</code>标识当前事务需要<code>rollback</code>。但是<code>ServiceA</code>中由于你手动的捕获这个异常并进行处理,<code>ServiceA</code>认为当前事务应该正常<code>commit</code>。此时就出现了前后不一致,也就是因为这样,抛出了前面的<code>UnexpectedRollbackException</code>异常。</p> 
<p><code>spring</code>的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行<code>commit</code>&nbsp;or&nbsp;<code>rollback</code>,事务是否执行取决于是否抛出<code>runtime异常</code>。如果抛出<code>runtime exception</code>&nbsp;并在你的业务方法中没有catch到的话,事务会回滚。</p> 
<p>在业务方法中一般不需要catch异常,如果非要catch一定要抛出<code>throw new RuntimeException()</code>,或者注解中指定抛异常类型<code>@Transactional(rollbackFor=Exception.class)</code>,否则会导致事务失效,数据commit造成数据不一致,所以有些时候try catch反倒会画蛇添足。</p> 
<h3 id="6%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93%E5%BC%95%E6%93%8E%E4%B8%8D%E6%94%AF%E6%8C%81%E4%BA%8B%E5%8A%A1"><a name="t8"></a><a name="t8"></a>6、数据库引擎不支持事务</h3> 
<p>这种情况出现的概率并不高,事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的<code>innodb</code>引擎。一旦数据库引擎切换成不支持事务的<code>myisam</code>,那事务就从根本上失效了。</p> 
<h2 id="%E6%80%BB%E7%BB%93"><a name="t9"></a><a name="t9"></a>总结</h2> 
<p>@Transactional 注解的看似简单易用,但如果对它的用法一知半解,还是会踩到很多坑的。</p>
                </div><div><div></div></div>
        </div>

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值