Spring 事物错误[Transaction rolled back because it has been marked as rollback-only]
Spring 事物异常[Transaction rolled back because it has been marked as rollback-only]
spring通过TransactionInterceptor和AbstractPlatformTransactionManager的实现类控制数据库事务。
创建数据库
Animal表
Person表
一、配置文件
1.applicationContext.xml
2.applicationContext-service.xml
3.applicationContext-common.xml
4.jdbc.properties
二、service类
service.AllService
service.impl.AllServiceImpl
service.AnimalService
service.impl.AnimalServiceImpl
service.PersonService
service.impl.PersonServiceImpl
三、测试代码
执行以上代码,在数据库中可以成功插入数据。
可是如果把personName赋值为一个很长的字符串(超过20位,超过数据库指定长度),就会抛出如下错误
已经在AllServiceImpl中的addAll中捕获了personService.addPerson这个抛出异常的方法调用。从控制台的打印中,可以发现错误的源是插入的Person表中的name字段太长。虽然在AllServiceImpl.addAll中已经捕获了异常,AllServiceImpl.addAll并未抛出异常,可是事务还是没有提交成功。
经过调试,发现spring处理普通的jdbc事物大致如此:每当调用事物管理的方法时(这里就是aop:pointcut expression匹配的方法),首先会调用TransactionInterceptor的invoke方法,在方法中spring会检测是否要开启事物(根据具体的配置文件而定),然后具体在调用具体的那个真正要处理的方法。如果这个真正处理的方法抛出了异常(如果在这个真正执行的方法内部有异常,但是自我捕获了,那么对于spring而言,这个方法并没有抛出异常),spring会根据配置判断这个异常是否要使得事物回滚,如果要回滚,那么就是设置一个标记,标记有关这一个connection的所有事物都要回滚。
所以,虽然AllServiceImpl.addAll中没有抛出异常,但是personService.addPerson这个调用抛出了异常,并且这个方法spring是要做事物处理的,一旦personService.addPerson抛出异常,TransactionInterceptor中invoke方法会捕获这个异常,然后标记有关这个connection的事务做回滚,就算在AllServiceImpl.addAll捕获了这个异常,但是animalService.addAnimal是无法插入数据的
spring通过TransactionInterceptor和AbstractPlatformTransactionManager的实现类控制数据库事务。
创建数据库
Animal表
create table Animal( name nvarchar(20) )
Person表
create table Person( name nvarchar(20) )
一、配置文件
1.applicationContext.xml
<import resource="applicationContext-common.xml"/> <import resource="applicationContext-service.xml"/>
2.applicationContext-service.xml
<bean id="baseService" abstract="true"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="animalService" class="service.impl.AnimalServiceImpl" parent="baseService"/> <bean id="personService" class="service.impl.PersonServiceImpl" parent="baseService"/> <bean id="allService" class="service.impl.AllServiceImpl" parent="baseService"> <property name="animalService" ref="animalService"/> <property name="personService" ref="personService"/> </bean>
3.applicationContext-common.xml
<!-- 配置属性配置文件 --> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:jdbc.properties</value> </list> </property> </bean> <!-- 配置数据源 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.database.driverClassName}"/> <property name="url" value="${jdbc.database.url}"/> <property name="username" value="${jdbc.database.username}"/> <property name="password" value="${jdbc.database.password}"/> </bean> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 具体事务配置 --> <aop:config> <aop:pointcut expression="execution(* service.*.*(..))" id="allManagerMethods" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="allManagerMethods" /> </aop:config> <!-- 事务传播特性的配置 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*" propagation="REQUIRED" /> </tx:attributes> </tx:advice>
4.jdbc.properties
jdbc.database.driverClassName=oracle.jdbc.driver.OracleDriver jdbc.database.url=jdbc:oracle:thin:@127.0.0.1:1521:oracle jdbc.database.username=scott jdbc.database.password=tiger
二、service类
service.AllService
public interface AllService { public void addAll(String personName,String animalName) throws Exception; }
service.impl.AllServiceImpl
public class AllServiceImpl extends JdbcDaoSupport implements AllService { private PersonService personService; private AnimalService animalService; @Override public void addAll(String personName, String animalName) throws Exception { try{ personService.addPerson(personName); }catch (Exception e) { System.out.println(e.toString()); } animalService.addAnimal(animalName); } public void setPersonService(PersonService personService) { this.personService = personService; } public void setAnimalService(AnimalService animalService) { this.animalService = animalService; } }
service.AnimalService
public interface AnimalService { public void addAnimal(String animalName) throws Exception; }
service.impl.AnimalServiceImpl
public class AnimalServiceImpl extends JdbcDaoSupport implements AnimalService{ @Override public void addAnimal(String animalName) throws Exception { this.getJdbcTemplate().update("insert into ANIMAL values(?)",new Object[]{animalName}); } }
service.PersonService
public interface PersonService { public void addPerson(String personName)throws Exception; }
service.impl.PersonServiceImpl
public class PersonServiceImpl extends JdbcDaoSupport implements PersonService { @Override public void addPerson(String personName) throws Exception { this.getJdbcTemplate().update("insert into PERSON values(?)",new Object[]{personName}); } }
三、测试代码
public class SpringTest { private static ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "applicationContext.xml"); public static void main(String[] args) throws Exception{ AllService allService=(AllService) applicationContext.getBean("allService"); String personName="will"; String animalName="小狗"; allService.addAll(personName,animalName); } }
执行以上代码,在数据库中可以成功插入数据。
可是如果把personName赋值为一个很长的字符串(超过20位,超过数据库指定长度),就会抛出如下错误
org.springframework.jdbc.UncategorizedSQLException: PreparedStatementCallback; uncategorized SQLException for SQL [insert into PERSON values(?)]; SQL state [72000]; error code [12899]; ORA-12899: 列 "SCOTT"."PERSON"."NAME" 的值太大 (实际值: 32, 最大值: 20) ; nested exception is java.sql.SQLException: ORA-12899: 列 "SCOTT"."PERSON"."NAME" 的值太大 (实际值: 32, 最大值: 20) Exception in thread "main" org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:695) at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:321) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:116) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:89) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204) at $Proxy2.addAll(Unknown Source) at test.SpringTest.main(SpringTest.java:16)
已经在AllServiceImpl中的addAll中捕获了personService.addPerson这个抛出异常的方法调用。从控制台的打印中,可以发现错误的源是插入的Person表中的name字段太长。虽然在AllServiceImpl.addAll中已经捕获了异常,AllServiceImpl.addAll并未抛出异常,可是事务还是没有提交成功。
经过调试,发现spring处理普通的jdbc事物大致如此:每当调用事物管理的方法时(这里就是aop:pointcut expression匹配的方法),首先会调用TransactionInterceptor的invoke方法,在方法中spring会检测是否要开启事物(根据具体的配置文件而定),然后具体在调用具体的那个真正要处理的方法。如果这个真正处理的方法抛出了异常(如果在这个真正执行的方法内部有异常,但是自我捕获了,那么对于spring而言,这个方法并没有抛出异常),spring会根据配置判断这个异常是否要使得事物回滚,如果要回滚,那么就是设置一个标记,标记有关这一个connection的所有事物都要回滚。
所以,虽然AllServiceImpl.addAll中没有抛出异常,但是personService.addPerson这个调用抛出了异常,并且这个方法spring是要做事物处理的,一旦personService.addPerson抛出异常,TransactionInterceptor中invoke方法会捕获这个异常,然后标记有关这个connection的事务做回滚,就算在AllServiceImpl.addAll捕获了这个异常,但是animalService.addAnimal是无法插入数据的