Spring 事物异常[Transaction rolled back because it has been marked as rollback-only]

本文详细介绍了如何使用Spring框架中的TransactionInterceptor和AbstractPlatformTransactionManager实现数据库事务管理,包括配置文件、服务类实现以及异常处理机制。特别关注了当数据库表字段长度超出限制时,事务处理的细节和解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[size=large]spring通过TransactionInterceptor和AbstractPlatformTransactionManager的实现类控制数据库事务。[/size]

[size=medium]创建数据库
Animal表[/size]

create table Animal(
name nvarchar(20)
)

[size=medium]Person表[/size]

create table Person(
name nvarchar(20)
)


[size=medium]一、配置文件[/size]
[size=small]1.applicationContext.xml[/size]

<import resource="applicationContext-common.xml"/>

<import resource="applicationContext-service.xml"/>

[size=small]2.applicationContext-service.xml[/size]

<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>

[size=small]3.applicationContext-common.xml[/size]

<!-- 配置属性配置文件 -->
<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>

[size=small]4.jdbc.properties[/size]

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

[size=medium]二、service类[/size]
[size=small]service.AllService[/size]

public interface AllService {
public void addAll(String personName,String animalName) throws Exception;
}

[size=small]service.impl.AllServiceImpl[/size]

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;
}
}

[size=small]service.AnimalService[/size]

public interface AnimalService {
public void addAnimal(String animalName) throws Exception;
}

[size=small]service.impl.AnimalServiceImpl[/size]

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});
}

}

[size=small]service.PersonService[/size]

public interface PersonService {
public void addPerson(String personName)throws Exception;
}

[size=small]service.impl.PersonServiceImpl[/size]

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});
}

}

[size=medium]三、测试代码[/size]

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);
}
}

[size=medium]执行以上代码,在数据库中可以成功插入数据。
可是如果把personName赋值为一个很长的字符串(超过20位,超过数据库指定长度),就会抛出如下错误[/size]

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)

[size=medium]已经在AllServiceImpl中的addAll中捕获了personService.addPerson这个抛出异常的方法调用。从控制台的打印中,可以发现错误的源是插入的Person表中的name字段太长。虽然在AllServiceImpl.addAll中已经捕获了异常,AllServiceImpl.addAll并未抛出异常,可是事务还是没有提交成功。[/size]

[color=red][size=medium]经过调试,发现spring处理普通的jdbc事物大致如此:每当调用事物管理的方法时(这里就是aop:pointcut expression匹配的方法),首先会调用TransactionInterceptor的invoke方法,在方法中spring会检测是否要开启事物(根据具体的配置文件而定),然后具体在调用具体的那个真正要处理的方法。如果这个真正处理的方法抛出了异常(如果在这个真正执行的方法内部有异常,但是自我捕获了,那么对于spring而言,这个方法并没有抛出异常),spring会根据配置判断这个异常是否要使得事物回滚,如果要回滚,那么就是设置一个标记,标记有关这一个connection的所有事物都要回滚。[/size][/color]

[size=medium]所以,虽然AllServiceImpl.addAll中没有抛出异常,但是personService.addPerson这个调用抛出了异常,并且这个方法spring是要做事物处理的,一旦personService.addPerson抛出异常,TransactionInterceptor中invoke方法会捕获这个异常,然后标记有关这个connection的事务做回滚,就算在AllServiceImpl.addAll捕获了这个异常,但是animalService.addAnimal是无法插入数据的[/size]
<think>好的,我现在需要帮助用户解决关于“Transaction rolled back because it has been marked as rollback-only异常的问题。首先,我得回顾一下用户提供的引用资料,整理出可能的原因和解决方案。 根据引用[1],这个异常Spring事务的传播行为有关,特别是REQUIRED、REQUIRES_NEW和NESTED的区别。可能当内层事务回滚时,外层事务被标记为rollback-only,导致提交时整体回滚。引用[2]提到,当内层事务抛出异常并被捕获后,外层事务在提交时检测到需要回滚,从而抛出异常。这说明异常处理不当可能是原因之一。 引用[3]指出该异常常发生在多业务场景中使用try-catch,导致事务嵌套混乱。解决方法是使用NESTED传播模式,让内层事务作为外层事务的嵌套子事务,这样内层的回滚不会强制外层回滚。引用[4]则建议避免随意捕获异常,确保只有能处理的异常才捕获,否则应抛出RuntimeException。 接下来,我需要将这些信息组织成结构清晰的回答,分点说明原因和解决方案。要注意使用中文,正确应用LaTeX格式,比如事务传播行为的类型可能需要用行内公式,如`REQUIRED`。同时,生成的相关问题要符合用户需求,可能涉及事务传播行为、异常处理、具体应用场景等。 需要确保回答正确引用每个来源,比如在解释原因时引用[^1][^2],解决方案引用[^3][^4]。另外,用户可能对事务传播行为的具体差异感兴趣,所以相关问题可以包括不同传播类型的区别、如何选择合适的事务传播行为等。 最后,检查是否符合系统级指令,如行内数学表达式是否正确使用$...$,独立公式是否用$$...$$,避免任何格式错误。同时确保回答真实可靠,引用来源准确。</think>### Transaction rolled back because it has been marked as rollback-only异常解析 #### 异常原因 1. **事务传播行为冲突** 当内层事务(如使用`REQUIRED`传播行为)抛出异常并被标记为回滚时,外层事务会被标记为`rollback-only`。此时即使外层事务尝试提交,也会因该标记强制回滚,导致异常。 2. **不恰当的异常捕获** 若内层事务的异常被`try-catch`捕获但未重新抛出`RuntimeException`,外层事务无法感知异常,继续提交时会触发回滚。 3. **事务嵌套层级混乱** 使用默认的`REQUIRED`传播行为时,内外层事务共享同一事务上下文。若内层事务回滚,外层事务无法独立提交,导致矛盾。 #### 解决方案 1. **调整事务传播行为** - 使用`NESTED`传播模式:允许内层事务作为嵌套子事务独立回滚,不影响外层事务。 - 使用`REQUIRES_NEW`传播模式:创建新事务,完全隔离内外层事务(需注意锁和性能问题)。 2. **规范异常处理** - 仅捕获可处理的异常,否则重新抛出`RuntimeException`或其子类。 - 示例代码: ```java try { innerTransaction(); } catch (BusinessException e) { // 明确可处理的异常 // 处理逻辑 } catch (Exception e) { throw new RuntimeException(e); // 强制触发回滚 } ``` 3. **事务范围最小化** 避免在包含多个业务操作的代码块上使用事务,改为对原子操作单独控制事务。 --- $$ \text{事务传播行为选择公式} = \begin{cases} \text{REQUIRED} & \text{默认共享事务} \\ \text{NESTED} & \text{嵌套子事务} \\ \text{REQUIRES\_NEW} & \text{独立事务} \end{cases} $$ ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值