首页 > 学院 > 开发设计 > 正文

spring注解事务及事务回滚失败的原因

2019-11-06 06:05:56
字体:
来源:转载
供稿:网友

背景

sPRing支持编程式事务管理和声明式事务管理两种方式。

编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。

声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

声明式事务管理也有两种常用的方式,一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解。显然基于注解的方式更简单易用,更清爽。

自动提交(AutoCommit)与连接关闭时的是否自动提交

自动提交

默认情况下,数据库处于自动提交模式。每一条语句处于一个单独的事务中,在这条语句执行完毕时,如果执行成功则隐式的提交事务,如果 执行失败则隐式的回滚事务。

spring注解事务的实现

maven创建一个普通项目,当然web项目也可以,我这里是一个web项目 pom.xm如下,本身是用不到这么多jar,直接从项目中拷贝的,懒得删。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.bamboo</groupId> <artifactId>anoTest</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>anoTest Maven Webapp</name> <url>http://maven.apache.org</url> <repositories> <repository> <id>nexus_public</id> <name>nexus_public</name> <url>http://repo.maven.apache.org/maven2/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>nexus_public</id> <name>nexus_public</name> <!-- <url>http://repository.jboss.org/nexus/content/repositories/root_repository/maven2/</url> --> <url>http://repo.maven.apache.org/maven2/</url> <layout>default</layout> <snapshots> <enabled>false</enabled> </snapshots> <releases> <updatePolicy>never</updatePolicy> </releases> </pluginRepository> </pluginRepositories> <!-- 项目属性 --> <properties> <!-- jar 版本设置 --> <spring.version>4.1.0.RELEASE</spring.version> <shiro.version>1.2.3</shiro.version> <mybatis.version>3.2.8</mybatis.version> <mybatis-spring.version>1.2.2</mybatis-spring.version> <MySQL.version>5.1.26</mysql.version> <druid.version>1.0.9</druid.version> <junit.version>3.8.1</junit.version> <log4j.version>1.2.17</log4j.version> <slf4j.version>1.7.5</slf4j.version> <poi.version>3.10-FINAL</poi.version> <commons-lang3.version>3.1</commons-lang3.version> <commons-io.version>2.4</commons-io.version> <commons-codec.version>1.8</commons-codec.version> <commons-fileupload.version>1.3.1</commons-fileupload.version> <commons-beanutils.version>1.8.3</commons-beanutils.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <jdk.version>1.7</jdk.version> </properties><dependencies> <!-- spring相关包 --> <!-- Spring --> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>servlet-api</artifactId> <version>6.0.37</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <!-- 切面jar --> <dependency> <groupId>aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.5.4</version> </dependency> <!-- javax提供的annotation,注入的注解jar --> <dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency> <!-- 日志架包 --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.16</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-mapper-asl</artifactId> <version>1.9.13</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <!--springMVC 依赖json解析jar --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.1.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.1.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.1.0</version> </dependency> <!--Apache Shiro所需的jar包 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>${shiro.version}</version> </dependency> <!-- <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>${shiro.version}</version> </dependency> --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>${shiro.version}</version> </dependency> <!-- <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-quartz</artifactId> <version>${shiro.version}</version> </dependency> --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${shiro.version}</version> </dependency> <!-- 数据库连接dbcp --> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.4</version> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>${mybatis-spring.version}</version> </dependency> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <!-- fileupload的jar --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>${commons-io.version}</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>${commons-fileupload.version}</version> </dependency> <!-- apache 常用包 --> <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.8.3</version> <type>jar</type> <scope>compile</scope> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.1</version> <type>jar</type> <scope>compile</scope> </dependency> <!-- 页面装饰 --> <dependency> <groupId>opensymphony</groupId> <artifactId>sitemesh</artifactId> <version>2.4.2</version> </dependency> <!-- jstl --> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- 缓存ehcache --> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> <version>2.8.3</version> </dependency> <!-- ehcache缓存jar --> <!-- <dependency> <groupId>com.googlecode.ehcache-spring-annotations</groupId> <artifactId>ehcache-spring-annotations</artifactId> <version>1.2.0</version> <type>jar</type> <scope>compile</scope> </dependency> --> </dependencies> <build> <finalName>onedollar</finalName> </build></project>

spring.xml

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:ehcache="http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring" xmlns:cache="http://www.springframework.org/schema/cache" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring/ehcache-spring-1.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd"> <!-- 自动扫描 --> <context:component-scan base-package="anotest" /> <!-- 定义一个数据源 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://127.0.0.1/shiro" /> <property name="username" value="root" /> <property name="passWord" value="root" /> <property name="initialSize" value="3" /><!-- initial connections --> <property name="maxActive" value="10" /><!-- MAX connections --> <property name="maxIdle" value="5" /><!-- MAX idle connections --> <property name="minIdle" value="2" /><!-- MIN idle connections --> <property name="testWhileIdle" value="true" /> <property name="testOnBorrow" value="false" /> <property name="testOnReturn" value="false" /> <property name="validationQuery" value="select 1" /> <property name="timeBetweenEvictionRunsMillis" value="20000" /> <property name="numTestsPerEvictionRun" value="100" /> </bean> <!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 --> <bean id="sqlsessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- 自动扫描mapping.xml文件 --> <property name="mapperLocations" value="classpath:anotest/mapper/*.xml"></property> <property name="typeAliasesPackage" value="anotest" /> </bean> <!-- DAO接口所在包名,Spring会自动查找其下的类 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="anotest.dao" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> </bean> <!-- 配置事务管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource"> </bean> <!-- enables scanning for @Transactional annotations --> <tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" /> <!-- 配置事物的拦截方式 --> <!-- <tx:advice id="transAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="delete*" propagation="REQUIRED" /> <tx:method name="insert*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="modify*" propagation="REQUIRED" /> <tx:method name="remove*" propagation="REQUIRED" /> <tx:method name="find*" propagation="SUPPORTS" /> <tx:method name="get*" propagation="SUPPORTS" /> <tx:method name="select*" propagation="SUPPORTS" /> <tx:method name="query*" propagation="SUPPORTS" /> <tx:method name="*" propagation="SUPPORTS" /> </tx:attributes> </tx:advice> --> <!-- 事物配置扫描包 --> <!-- <aop:config proxy-target-class="true"> <aop:pointcut expression="execution(* anotest.*impl.*(..))" id="transcationPointcut" /> <aop:advisor advice-ref="transAdvice" pointcut-ref="transcationPointcut" /> </aop:config> --></beans>

代码片段

创建包名 anotest.dao anotest.mapper anotest.service.impl

LogDao.java

package anotest.dao;import anotest.Log;/** * log页面表 数据库处理类 */public interface LogDao { void add(Log log);}

LogMapper.xml

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="anotest.dao.LogDao"><resultMap type="Log" id="Log"> <id column="LOG_ID" property="logId" javaType="java.lang.Long" jdbcType="BIGINT"/> <result column="IMEI_CODE" property="imeiCode" javaType="java.lang.String" jdbcType="VARCHAR"/> <result column="DAY_TIME" property="dayTime" javaType="java.lang.String" jdbcType="VARCHAR"/></resultMap><!-- 全部字段,一般用于明细查询 --><sql id="AllColumnlist">LOG_ID,IMEI_CODE,DAY_TIME</sql><!-- 常见字段,一般用于列表查询,可能不包含备注之类的字段 --><sql id="CommonColumnlist">LOG_ID,IMEI_CODE,DAY_TIME</sql><insert id="add" parameterType="Log" useGeneratedKeys="true" keyProperty="LogId"> insert into T_LOG ( <if test="logId!=null"> LOG_ID, </if> IMEI_CODE, DAY_TIME ) values ( <if test="logId!=null"> #{logId,jdbcType = BIGINT}, </if> #{imeiCode,jdbcType = VARCHAR}, #{dayTime,jdbcType = VARCHAR} )</insert><delete id="delete"> delete from T_LOG <where> Log_ID=#{0} </where></delete><select id="get" resultMap="Log"> select <include refid="AllColumnlist"/> from T_LOG <where> LOG_ID=#{0} </where></select><select id="getLogByCode" parameterType="java.lang.String" resultMap="Log"> select * from T_LOG <where> IMEI_CODE=#{0} </where></select></mapper>

Log.java

package anotest;public class Log { private static final long serialVersionUID = 1l; /*Auto generated properties start*/ //log-id private Long logId; //IMEI编号 private String imeiCode; //年月日 private String dayTime; /*Auto generated methods start*/ public Log() { } /** * @param imeiCode * @param dayTime */ public Log(String imeiCode, String dayTime) { super(); this.imeiCode = imeiCode; this.dayTime = dayTime; } /*Auto generated methods end*/ /*Customized methods start*/ /*Customized methods end*/ public Long getLogId() { return logId; } public void setLogId(Long logId) { this.logId = logId; } public String getImeiCode() { return imeiCode; } public void setImeiCode(String imeiCode) { this.imeiCode = imeiCode; } public String getDayTime() { return dayTime; } public void setDayTime(String dayTime) { this.dayTime = dayTime; } @Override public String toString() { return "Log [logId=" + logId + ", imeiCode=" + imeiCode + ", dayTime=" + dayTime + "]"; }}

TestService.java

package anotest.service;import anotest.Log;/** * @ClassName: TestService * @Description: TODO(这里用一句话描述这个类的作用) * @author bamboo <a href="mailto:zjcjava@163.com?subject=hello,bamboo&body=Dear Bamboo:%0d%0a描述你的问题:">Bamboo</a> * @date 2017年3月7日 上午10:17:06 * @since */public interface TestService { public void addCatch() throws Exception; public void addTest();}

TestServiceImpl.java

package anotest.service.impl;import java.text.SimpleDateFormat;import java.util.Date;import javax.annotation.Resource;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import anotest.Log;import anotest.dao.LogDao;import anotest.service.TestService;/** * @ClassName: TestServiceImpl * @Description: TODO(这里用一句话描述这个类的作用) * @author bamboo <a href="mailto:zjcjava@163.com?subject=hello,bamboo&body=Dear Bamboo:%0d%0a描述你的问题:">Bamboo</a> * @date 2017年3月7日 上午10:38:42 * @since */@Service//默认将类中的所有函数纳入事务管理. @Transactionalpublic class TestServiceImpl implements TestService{ @Resource private LogDao logDao; /*** * 捕获异常后但是不抛出去是无法执行回滚事务的 * 建议:用本方式写,使上层方法能够检测到数据插入失败,因此必须抛出异常,然后在调用它的方法中捕获异常并提示用户数据提交失败 * @throws Exception */ @Transactional public void addCatch() throws Exception { // TODO Auto-generated method stub try { SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd"); String dayTime=format.format(new Date()); Log log =new Log("ABCD", dayTime); log.setLogId(System.currentTimeMillis()); System.out.println(log.toString()); // 调用Dao执行插入操作 logDao.add(log);//正常常数据 log.setDayTime(dayTime+"yyyyyyyyyyyyyyyyyyy"); // 调用Dao执行插入操作 logDao.add(log);//异常数据 } catch (Exception e) { // TODO Auto-generated catch block System.out.println("插入失败"); //throw new Exception(e);//注意:正常环境中必须去掉本行注释,当把这行去掉注释事务回滚就会起作用 } } /****** * 运行时异常不作捕获则则能够正常执行回滚事务 */ @Transactional public void addTest() { // TODO Auto-generated method stub SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd"); String dayTime=format.format(new Date()); Log log =new Log("ABCD", dayTime); log.setLogId(System.currentTimeMillis()); System.out.println(log.toString()); // 调用Dao执行插入操作 logDao.add(log); log.setDayTime(dayTime+"yyyyyyyyyyyyyyyyyyy"); // 调用Dao执行插入操作 logDao.add(log);//异常数据 }}

测试类

ContextConfig.java

import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; //指定bean注入的配置文件 @ContextConfiguration(locations = { "classpath:spring.xml" }) //使用标准的JUnit @RunWith注释来告诉JUnit使用Spring TestRunner @RunWith(SpringJUnit4ClassRunner.class) public class ContextConfig extends AbstractJUnit4SpringContextTests {}

TestCase.java

import org.junit.Test;import org.springframework.beans.factory.annotation.Autowired;import anotest.service.TestService;public class TestCase extends ContextConfig { @Autowired private TestService testService; @Test public void getTimestampTest() throws Exception{ testService.addCatch();//1捕获异常的方法,注释掉异常抛出则不会回滚 testService.addTest();//2没有任何捕获,能够正常回滚 } }

测试结果

当注释掉注释//2所在的行只执行addCatch()方法时

log4j:WARN No appenders could be found for logger (org.springframework.test.context.junit4.SpringJUnit4ClassRunner).log4j:WARN Please initialize the log4j system properly.log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.Log [logId=1488871162669, imeiCode=ABCD, dayTime=20170307]插入失败

插入失败,但是我们看数据库,依然会有数据被插入了

mysql> select * from t_log;+---------------+-----------+------------+| LOG_ID | IMEI_CODE | DAY_TIME |+---------------+-----------+------------+| 1488871162669 | ABCD | 2017-03-07 |+---------------+-----------+------------+1 row in set (0.00 sec)

当注释掉注释//1所在的行只执行addTest()方法时 OUTPUT:

log4j:WARN No appenders could be found for logger (org.springframework.test.context.junit4.SpringJUnit4ClassRunner).log4j:WARN Please initialize the log4j system properly.log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.Log [logId=1488871460315, imeiCode=ABCD, dayTime=20170307]

看数据库,显然数据没有被插入,而是发生了回滚

mysql> select * from t_log;+---------------+-----------+------------+| LOG_ID | IMEI_CODE | DAY_TIME |+---------------+-----------+------------+| 1488871162669 | ABCD | 2017-03-07 |+---------------+-----------+------------+1 row in set (0.00 sec)

我们在回到addCatch()方法,把下面这行的注释去掉

//throw new Exception(e);//注意:正常环境中必须去掉本行注释,当把这行去掉注释事务回滚就会起作用

运行测试

OUTPUT:

log4j:WARN No appenders could be found for logger (org.springframework.test.context.junit4.SpringJUnit4ClassRunner).log4j:WARN Please initialize the log4j system properly.log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.Log [logId=1488871709355, imeiCode=ABCD, dayTime=20170307]插入失败

查看表数,可以看到这次数据并没有被插入

mysql> select * from t_log;+---------------+-----------+------------+| LOG_ID | IMEI_CODE | DAY_TIME |+---------------+-----------+------------+| 1488871162669 | ABCD | 2017-03-07 |+---------------+-----------+------------+1 row in set (0.00 sec)

结论

spring的事务只有在触发运行时异常时才会发生回滚,并且这个异常是在切面中是可以被事务管理器捕获到的,如果我们自己在方法体中捕获而不把这个异常抛出(不通知事务管理器)它就不会除非事务回滚。因此在dao层基本不处理数据库插入异常。

其他原因

mysql数据库引擎的问题 mysql数据库引擎分为两种 innodb:支持事务和索引 myisam:不支持事务和索引,因此这种类型的引擎即使你的代码写的是正确的但是依然不会触发事务回滚。

配置文件扫描的问题 因为spring的容器(applicationContext)和springMVC的(applicationContext)是不同的。

spring容器加载得时候,优先加载ServletContextListener(对应spring.xml)产生的父容器,而springMVC(对应springMVC.xml)产生的是子容器。子容器Controller进行扫描装配时装配的@Service注解的实例是没有经过事务加强处理,

即没有事务处理能力的Service。而父容器进行初始化的Service是保证事务的增强处理能力的。如果不在子容器中将Service除去掉,此时得到的将是原样的无事务处理能力的Service。

所以,我们应把扫描Service的工作放在spring.xml中。让Service和事务注解存在于同一个容器中,这样配置的事务注解就能起作用了。也就是说把这个配置从

springMVC.xml中移到spring.xml的配置中。事务不回滚的问题就能解决了。

本demo下载

http://download.csdn.net/detail/zjcjava/9773010


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表