spring transaction 事务try catch后还是进行的回滚

IT_Debug 2018-05-31 11:27:13
今天在做项目的时候遇到一个问题,和之前的理论知识不符,之前在学习spring transaction事务的时候,如果自己对方法抛出的异常进行了捕获了,事务是不会回滚的,但是今天我测试后发现并不是这样,那个大佬能帮我解决下,万分感谢!
下面是我的业务场景,一个类A加了事务管理,其中一个方法methedA调用了另个一个类B的方法methedB,类B也加了事务管理,类B的methedB抛出了一个异常,methedA对异常进行了捕获,但是数据还是没有插入到数据库,具体代码如下:
类A:
@Service
@Transactional(rollbackFor = Exception.class)
public class InvoiceLoggingService implements IInvoiceLoggingService {

private static final Logger logger = LoggerFactory.getLogger(InvoiceLoggingService.class);

@Autowired
private InvoiceBatchInputService batchService;
@Autowired
private InvoiceInputRecordDao dao;

/**
* 发票录入
*
* @param record
* @return
*/
@Override
public InvoiceBatchLoggingResponse invoiceLogging(InvoiceInputRecord record) {
InvoiceBatchLoggingResponse response = new InvoiceBatchLoggingResponse();
//根据发票代码和发票号码判断该发票是否重复录入,如果重复录入就标记为重复
try {
String invoiceCode = record.getInvoiceCode();
String invoiceNumber = record.getInvoiceNumber();
InvoiceInputRecord checkRecord = this.dao.findByInvoiceCodeAndInvoiceNum(invoiceCode, invoiceNumber);

record.setCreateTime(new Date());
record.setUpdateTime(new Date());
if (checkRecord != null) {
logger.debug("发票代码:{},发票号码:{}已经存在,本次为重复录入", invoiceCode, invoiceNumber);
record.setDuplicateState((byte) 1);
}

//插入记录
record.setInvoiceInputRecordId(this.idGenHelper.getLongId());
this.dao.insertRecord(record);

//调用后续接口
com.kingxunlian.caf.modules.logging.dto.InvoiceLoggingResponse res = this.batchService.invoiceLogging(record);
CommonUtils.beanCopy(res, response);

} catch (Exception e) {
e.printStackTrace();
logger.error("发票查验异常,信息为:{}", e);

}
return response;
}
}

类B:
@Service
@Transactional(rollbackFor = Exception.class)
public class InvoiceBatchInputService implements IInvoiceBatchInputService {

private static final Logger logger = LoggerFactory.getLogger(InvoiceBatchInputService.class);

/**
* 发票批次录入dao
*/
@Resource
private InvoiceInputRecordDao invoiceInputRecordDao;

/**
* 发票批次录入
*
* @param invoiceInputRecord
* @return
*/
@Override
public InvoiceLoggingResponse invoiceLogging(InvoiceInputRecord invoiceInputRecord) {

logger.debug("进入发票批次录入");
InvoiceLoggingResponse loggingResponse = new InvoiceLoggingResponse();

CommonUtils.beanCopy(invoiceInputRecord, loggingResponse);
String userId = invoiceInputRecord.getUserId();
// 根据userId获取用户名
UserInfoResponse userResponse = this.userFeignClient.getUserById(userId).getBody();
if (userResponse == null) {
logger.error("根据userId获取用户信息失败!");
throw new Exception("当前用户不存在!");
}

logger.debug("根据用户id:{}获取用户名:{}", userId, userResponse.getNickName());
invoiceInputRecord.setUserName(userResponse.getNickName());

this.invoiceInputRecordDao.updateByInvoiceInputRecordId(invoiceInputRecord.getInvoiceInputRecordId(), invoiceInputRecord);

// 1.真假验证
this.validateReal(invoiceInputRecord);

loggingResponse.setSuccess(true);
loggingResponse.setDescription("发票录入操作成功");


return loggingResponse;
}

当B类方法中userResponse 为null的时候,类A中的方法insertRecord()并没有保存数据到数据库,不知道这个是为什么?但是我将类B的@Transactional(rollbackFor = Exception.class)去掉后就可以保存到数据库中
...全文
4556 14 打赏 收藏 转发到动态 举报
写回复
用AI写文章
14 条回复
切换为时间正序
请发表友善的回复…
发表回复
四月天五月雨 2020-01-16
  • 打赏
  • 举报
回复 2
A方法加@Transactional, B方法加@Transactional 此时B的传播特性为:Propagation.REQUIRED, 如果在A方法中通过SpringBean的方式使用B方法, 则B方法会使用A的事务,当B方法throw时,会把A事务标记为回滚,因此A全部回滚,catch没用,结束 解决: B方法的传播特性可以为:NESTED, 嵌套事务即可;这样B的事务会独立起来;A的事务不会跟着回滚;
千荨乐 2019-10-14
  • 打赏
  • 举报
回复
B类已经标记事务回滚,A类标记事务提交,导致产生了冲突;但是如果取消掉B类事务,B类自然将异常抛出,A类将异常捕获,就不出现事务回滚这种情况
呆囧囧呆 2018-10-29
  • 打赏
  • 举报
回复
我是这样理解的
当B类上的事务注解没去掉的时候,A事务方法调用B事务方法,当B抛出异常后,虽然catch住了,但是在A中会出现事务重复提交异常,所以事务都会回滚的。
LS1firesoar 2018-06-11
  • 打赏
  • 举报
回复 2
上面说的很多了,不懂就先背下来。 然后想知道为什么,就是补补。 spring事务怎么实现的,怎么管理的,就知道了。 简而言之为什么会这样: a方法调用b方法(不同类),会在b方法的时候启动代理,然后在执行b的方法。代理发现已经有事务了就把b的事务标记为当前同一个事务。b抛出异常了就把事务标记为异常,然后抛出异常。a的try catch这个时候捕获了异常,也没有用应为事务已经是异常了。 如果去掉注解,就不会有这个代理,try catch就是你想要的效果了。当然你可以b上面每次new一个事务也是可以的。,
maradona1984 2018-06-05
  • 打赏
  • 举报
回复 1
引用 7 楼 u012789986 的回复:
那为什么有的地方说对异常进行捕获后就不会回滚,我这里也进行了捕获,为什么事务还是进行了回滚,麻烦您帮我讲解下,实在是没有理解
我感觉我上面白说了,我已经给你解释了,为啥B会回滚的问题 这个东西的确不是很容易理解,你需要了解spring如何实现事务控制 如果这个代码还看不懂,那就真没法说了

package cn.diege.facade;

public class TransactionTest {

	static ThreadLocal<TransactionManager> t = new ThreadLocal<>();

	public static void main(String[] args) {
		//这个简单的模拟aop控制事务的过程,实际上更复杂
		A a = new ProxyA();
		a.a();
	}

	public static class A {
		B b = new ProxyB();

		public void a() {
			try{
				b.b();
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}

	public static class B {
		public void b() {
			throw new RuntimeException("抛出异常了");
		}
	}

	public static class ProxyA extends A {
		A a = new A();

		public void a() {
			getManager().begin();
			try {
				a.a();
				getManager().commit();
			} catch (Exception e) {
				getManager().rollback();
			}
		}
	}

	public static class ProxyB extends B {
		B b = new B();

		public void b() {
			getManager().begin();
			try {
				b.b();
			} catch (Exception e) {
				//实际代码并非这么简单,他要判断当前是否事务最外层,这里简单的标记回滚
				getManager().setRollbackOnly();
				throw e;
			}
		}
	}

	public static class TransactionManager {

		private boolean flag = false;

		public void begin() {

		};

		public void commit() {
			if(!flag){
				System.out.println("事务提交了");
			}else{
				//实际此时会抛出异常,你可以尝试在你的a方法里加一个增删改操作,估计也会抛异常
				System.out.println("事务已经标记回滚");
			}
		};

		public void rollback() {
			System.out.println("事务回滚了");
		};

		public void setRollbackOnly() {
			this.flag = true;
		}
	}

	public static TransactionManager getManager() {
		TransactionManager manager = t.get();
		if (manager == null) {
			t.set(new TransactionManager());
		}
		return t.get();
	}
}
IT_Debug 2018-06-05
  • 打赏
  • 举报
回复
引用 5 楼 dotnetstudio 的回复:
当B类方法中userResponse 为null的时候,类A中的方法insertRecord()并没有保存数据到数据库,不知道这个是为什么?但是我将类B的@Transactional(rollbackFor = Exception.class)去掉后就可以保存到数据库中 你这个结果是正确的,刚开始两个方法都加了事务,那么会根据@Transactional传播行为默认是Propagation.REQUIRED 处于同一个事务中,所以你插入的数据被回滚到,因此没有保存到数据库中; 第二次你将类B的注解去掉,那么B不是一个事务方法,抛出的异常不会被A监听到,这其实和事务的传播属性没有关系,应该去看看Spring的事务管理器是怎么玩的,它是分管理器和拦截器两部分,你要理解为什么加了注解,就能保证事务。
我想请问下,就是我对异常进行了捕获,不是说对异常进行捕获后事务不会回滚吗?为什么我这里事务还是回滚了
IT_Debug 2018-06-05
  • 打赏
  • 举报
回复
引用 6 楼 maradona1984 的回复:
[quote=引用 4 楼 u012789986 的回复:] [quote=引用 3 楼 maradona1984 的回复:] 这个不是很正常? 事务是aop实现的,aop是基于动态代理,代理即代理模式,是基于类的,你B类方法执行完就会标记事务回滚,在A方法捕获异常毫无意义 你去掉@Transactional(rollbackFor = Exception.class)那B类方法没有代理事务咯,那就肯定是A来管事务咯 如果你在A捕获异常,然后再插入一条记录,会抛出异常的
@Transactional传播行为默认是Propagation.REQUIRED,相当于我在A类中调用B类的方法是是属于同一个事务,为什么A类中捕获了还是会回滚呢?[/quote] 我上面不是已经解释了么?B类方法抛出异常,只要出这个方法就会标记事务回滚(只是标记回滚,还没有回滚)[/quote] 那为什么有的地方说对异常进行捕获后就不会回滚,我这里也进行了捕获,为什么事务还是进行了回滚,麻烦您帮我讲解下,实在是没有理解
maradona1984 2018-06-01
  • 打赏
  • 举报
回复
这个不是很正常? 事务是aop实现的,aop是基于动态代理,代理即代理模式,是基于类的,你B类方法执行完就会标记事务回滚,在A方法捕获异常毫无意义 你去掉@Transactional(rollbackFor = Exception.class)那B类方法没有代理事务咯,那就肯定是A来管事务咯 如果你在A捕获异常,然后再插入一条记录,会抛出异常的
幽饮烛 2018-06-01
  • 打赏
  • 举报
回复 1
同一个事务,在里层标有 @Transactional 的方法如果抛异常了,异常状态会传播到外层的 @Transactional,try catch 没用。
maradona1984 2018-06-01
  • 打赏
  • 举报
回复
引用 4 楼 u012789986 的回复:
[quote=引用 3 楼 maradona1984 的回复:] 这个不是很正常? 事务是aop实现的,aop是基于动态代理,代理即代理模式,是基于类的,你B类方法执行完就会标记事务回滚,在A方法捕获异常毫无意义 你去掉@Transactional(rollbackFor = Exception.class)那B类方法没有代理事务咯,那就肯定是A来管事务咯 如果你在A捕获异常,然后再插入一条记录,会抛出异常的
@Transactional传播行为默认是Propagation.REQUIRED,相当于我在A类中调用B类的方法是是属于同一个事务,为什么A类中捕获了还是会回滚呢?[/quote] 我上面不是已经解释了么?B类方法抛出异常,只要出这个方法就会标记事务回滚(只是标记回滚,还没有回滚)
KeepSayingNo 2018-06-01
  • 打赏
  • 举报
回复
当B类方法中userResponse 为null的时候,类A中的方法insertRecord()并没有保存数据到数据库,不知道这个是为什么?但是我将类B的@Transactional(rollbackFor = Exception.class)去掉后就可以保存到数据库中 你这个结果是正确的,刚开始两个方法都加了事务,那么会根据@Transactional传播行为默认是Propagation.REQUIRED 处于同一个事务中,所以你插入的数据被回滚到,因此没有保存到数据库中; 第二次你将类B的注解去掉,那么B不是一个事务方法,抛出的异常不会被A监听到,这其实和事务的传播属性没有关系,应该去看看Spring的事务管理器是怎么玩的,它是分管理器和拦截器两部分,你要理解为什么加了注解,就能保证事务。
IT_Debug 2018-06-01
  • 打赏
  • 举报
回复
引用 3 楼 maradona1984 的回复:
这个不是很正常? 事务是aop实现的,aop是基于动态代理,代理即代理模式,是基于类的,你B类方法执行完就会标记事务回滚,在A方法捕获异常毫无意义 你去掉@Transactional(rollbackFor = Exception.class)那B类方法没有代理事务咯,那就肯定是A来管事务咯 如果你在A捕获异常,然后再插入一条记录,会抛出异常的
@Transactional传播行为默认是Propagation.REQUIRED,相当于我在A类中调用B类的方法是是属于同一个事务,为什么A类中捕获了还是会回滚呢?
沁海棠 2018-05-31
  • 打赏
  • 举报
回复
可能和事务的合并有关系,试试传播行为改成 require_new

67,513

社区成员

发帖
与我相关
我的任务
社区描述
J2EE只是Java企业应用。我们需要一个跨J2SE/WEB/EJB的微容器,保护我们的业务核心组件(中间件),以延续它的生命力,而不是依赖J2SE/J2EE版本。
社区管理员
  • Java EE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧