1. 程式人生 > >springJdbc(jdbcTemplate)事物攔截失效問題解決

springJdbc(jdbcTemplate)事物攔截失效問題解決

sender ict dispatch res wait parameter update pointcut frame

先貼上web.xml和spring-jdbc.xml代碼:

web.xml代碼:

 1   <context-param>
 2      <param-name>contextConfigLocation</param-name>
 3      <param-value>
 4                  classpath:config/spring-jdbc.xml,
 5                  classpath:config/spring-redis.xml
 6          </param-value>
7 </context-param> 8 9 <listener> 10 <description>spring監聽器</description> 11 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 12 </listener> 13 14 <servlet> 15 <servlet-name>SpringMvc</
servlet-name> 16 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 17 <init-param> 18 <param-name>contextConfigLocation</param-name> 19 <param-value> 20 classpath:config/spring-mvc.xml 21 </
param-value> 22 </init-param> 23 <load-on-startup>1</load-on-startup> 24 </servlet> 25 <servlet-mapping> 26 <servlet-name>SpringMvc</servlet-name> 27 <url-pattern>/</url-pattern> 28 </servlet-mapping> 29 <servlet>

spring-jdbc.xml代碼:

  1   <?xml version="1.0" encoding="UTF-8"?>
  2   <beans 
  3       xmlns="http://www.springframework.org/schema/beans" 
  4       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  5       xmlns:tx="http://www.springframework.org/schema/tx" 
  6       xmlns:aop="http://www.springframework.org/schema/aop"
  7       xmlns:context="http://www.springframework.org/schema/context"   
  8   xsi:schemaLocation="
  9   http://www.springframework.org/schema/beans 
 10   http://www.springframework.org/schema/beans/spring-beans-4.2.xsd 
 11   http://www.springframework.org/schema/tx 
 12   http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
 13   http://www.springframework.org/schema/aop 
 14   http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
 15   http://www.springframework.org/schema/context   
 16   http://www.springframework.org/schema/context/spring-context-4.2.xsd"    default-autowire="byName"  >
 17   
 18   <!-- 引入屬性文件 -->
 19       <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
 20           <property name="locations">
 21               <list>
 22                   <value>classpath:config/jdbc.properties</value>
 23                   <value>classpath:config/redis.properties</value>
 24                   <value>classpath:config/contract-mail.properties</value>
 25               </list>
 26           </property>
 27       </bean>
 28       
 29       <!-- 加載config.properties,為了使該註解生效:@Value("#{config[‘email.alarm.sender.email‘]}") -->
 30       <bean id="config" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
 31            <!-- false表示當沒找到這個配置文件時,應用程序應該報錯         -->
 32           <property name="ignoreResourceNotFound" value="false" />   
 33           <property name="locations">
 34               <list>
 35                   <value>classpath:config/config.properties</value>
 36               </list>
 37           </property>
 38       </bean>
 39     
 40       <!-- 配置數據源 -->
 41       <bean name="dataSourceSpied" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
 42           <property name="url" value="${jdbc_url}" />
 43           <property name="username" value="${jdbc_username}" />
 44           <property name="password" value="${jdbc_password}" />
 45           <property name="initialSize" value="${jdbc_initialSize}" />
 46           <property name="maxActive" value="${jdbc_maxActive}" />
 47           <property name="minIdle" value="${jdbc_minIdle}" />
 48           <property name="maxWait" value="${jdbc_maxWait}" />
 49           <property name="validationQuery" value="${validationQuery}" />
 50           <property name="testOnBorrow" value="false" />
 51           <property name="testOnReturn" value="false" />
 52           <property name="testWhileIdle" value="true" />
 53           <!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒 -->
 54           <property name="timeBetweenEvictionRunsMillis" value="60000" />
 55           <!-- 打開removeAbandoned功能 -->
 56           <property name="removeAbandoned" value="true" />
 57           <!-- 1800秒,也就是30分鐘 -->
 58           <property name="removeAbandonedTimeout" value="180" />
 59           <!-- 關閉abanded連接時輸出錯誤日誌 -->
 60           <property name="logAbandoned" value="true" />
 61           <!-- 監控數據庫 -->
 62           <property name="filters" value="mergeStat" />
 63       </bean>
 64       
 65       <bean id="dataSource" class="net.sf.log4jdbc.Log4jdbcProxyDataSource">
 66           <constructor-arg ref="dataSourceSpied"/>
 67       </bean>
 68       
 69       <!-- myBatis文件 -->
 70       <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
 71           <property name="dataSource" ref="dataSource" />
 72           <property name="mapperLocations" value="classpath*:mapper/*.xml" />
 73           <property name="plugins">
 74               <list>
 75                   <!-- 物理分頁 -->
 76                   <bean class="com.github.miemiedev.mybatis.paginator.OffsetLimitInterceptor">
 77                       <property name="dialectClass"  value="com.github.miemiedev.mybatis.paginator.dialect.OracleDialect"></property>
 78                   </bean>
 79               </list>
 80           </property>
 81       </bean>
 82   
 83       <!-- 配置事務管理器 -->
 84       <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
 85           <property name="dataSource" ref="dataSourceSpied" />
 86       </bean>
 87        <!-- 聲明式事務管理,攔截多個包下面事物       and execution(* com.xxx.service.*Service.*(..))  -->
 88        <aop:config>
 89            <aop:advisor pointcut="execution(* com.xxx.service.*Service.*(..))"
 90                 advice-ref="myAdvice"/>
 91        </aop:config>
 92        <tx:advice id="myAdvice" transaction-manager="transactionManager">
 93            <tx:attributes>
 94                <tx:method name="addCommandExec" propagation="REQUIRES_NEW"/> <!-- 這個名字的service方法使用新事物,不使用繼承事物 -->
 95                <tx:method name="*" propagation="REQUIRED" rollback-for="java.lang.RuntimeException" />
 96                <!-- <tx:method name="*" read-only="true"/> -->
 97                <!-- <tx:method name="*" read-only="true" rollback-for="com.smvc.util.DaoException"/> -->
 98            </tx:attributes>
 99        </tx:advice>
100        
101        <!-- 自動掃描組件,多個包用逗號隔開,需要把controller去掉,否則影響事務管理 -->
102        <context:component-scan base-package="com.xxx,com.xxx.command">
104            <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>  
105        </context:component-scan>
106        
107       <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">  
108           <property name = "dataSource" ref="dataSource" />  
109       </bean>  
110       
111       <bean id="namedJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">  
112           <constructor-arg ref="dataSource" />  
113       </bean>  
114   
115   </beans>

bug問題表現:

在Service方法中,事物不起作用,bug表現如下:
1) 期望:Service方法中save()方法執行前開啟事物,執行後提交事物,提交事物後才可以在數據庫裏看到那條新insert的數據。

2) 現象:事物不起作用,在save()方法未結束時,數據庫中已經可以看到那條新insert的數據。save()裏使用的是jdbcTemplate對象執行的SQL。

問題解決思路:

由於是springMVC項目,為了更快的debug,省去tomcat啟動時間,建立了Junit測試類模擬spring容器啟動,代碼貼上如下:

 1 package com.xxx.service;
 2 import org.apache.log4j.Logger;
 3 import org.junit.Test;
 4 import org.junit.runner.RunWith;
 5 import org.springframework.beans.factory.annotation.Autowired;
 6 import org.springframework.test.context.ContextConfiguration;
 7 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 8 import org.springframework.test.context.web.WebAppConfiguration;
 9 
11 @WebAppConfiguration
12 @RunWith(SpringJUnit4ClassRunner.class)
13 @ContextConfiguration({ "classpath:config/spring-mybatis.xml"
14     ,"classpath:config/spring-redis.xml","classpath:config/spring-mvc.xml"})
15 public class SystemUserServiceTest {
16     private final static Logger logger= Logger.getLogger(SystemUserServiceTest.class);
17 
18     @Autowired
19     private SystemUserService systemUserService;
20     @Test
21     public void test() {
22         int result2 = systemUserService.insertSystemUser();
23         logger.info("result2 == " + result2);
24     }
25 
26 }

第一次解決方法:百度了很多資料,都說spring-mvc.xml和spring-jdbc.xml的註解掃描器要註意互相排除,也就是controller層的要排除掃描service類,service層的要排除controller層,我仔細對比了兩個xml配置文件,與網上寫的解決方法一致。所以不是這個原因出現的問題。

第二次解決方法:我的spring-jdbc.xml配置文件裏面,使用的是transactionManager對象來做事物管理類,所以跟蹤spring事物類org.springframework.jdbc.datasource.DataSourceTransactionManager,看看到底有沒有進入事物管理。分別在DataSourceTransactionManager類裏的doBegin事物開啟和doCommit事物提交方法裏打上斷點,debug開始跟蹤。debug過程中發現,spring容器確實在save()方法之前執行的doBegin()事物開啟,確實在save()執行完畢之後執行的doCommit()事物提交。可是當save()方法還未徹底結束之前,也就是doCommit()方法也尚未執行之前,數據庫就有這條新insert記錄了!所以不是事物配置沒有起作用的原因。

第三次解決方法:這次換個思路,我在save()方法中執行sql語句的代碼是:jdbcTemplate.update(insertSql); 我在這個update方法裏打了斷點,一直跟蹤下去,終於發現了bug原因

bug原因:

jdbcTemplate.update(insertSql) 中的update方法裏的dataSource對象在debug過程中看到此對象ID是111,而在之前DataSourceTransactionManager類裏面的dataSource對象在debug過程中此對象ID是81,說明jdbcTemplate對象拿到的dataSource對象和事物管理器裏面的dataSource對象根本就不是同一個!

bug修正:

把spring-jdbc.xml裏的實際sql 執行對象所引用的dataSource換成和事物管理器的dataSource同一個對象就好了。

1     <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">  
2         <property name = "dataSource" ref="dataSourceSpied" />  
3     </bean>  
4     
5     <bean id="namedJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">  
6         <constructor-arg ref="dataSourceSpied" />  
7     </bean> 

springJdbc(jdbcTemplate)事物攔截失效問題解決