Mono里是不支持注解事务的。
比如
@Transactional
public Mono<CommonOutput> save(RecordFileSaveReq req) {
return Mono.just(true)
.filter(b -> saveLog(req))
.filter(b -> copyFile(req))
.filter(b -> removeTempFile(req))
.map(b -> success());
}
这样是不行的。
只能回到老办法:
@Transactional
public CommonOutput logProcess(RecordFileSaveReq req) {
//保存日志
repository.save(req);
String name = storeService.generateName();
//转储
storeService.copyFile(req.getVoiceAddress(), "");
//删除临时文件
removeTempFile(req);
return success();
}
其中,repository.save方法和storeService.copyFile方法都会抛运行时异常。
Controller里的方法就会很罗嗦,要截获异常:
@PostMapping(name = "/recordFile/save")
@ResponseBody
public Mono<CommonOutput> save(@Valid @RequestBody Mono<RecordFileSaveReq> req) {
return Mono.create(sink ->
req.doOnError(WebExchangeBindException.class, throwable ->
sink.success(fail(throwable))
).doOnNext(r -> Mono.just(true)
.map(b -> service.logProcess(r))
.onErrorReturn(RecordObjectException.class, recordObjError())
.onErrorReturn(SessionIDTypeException.class, sessionAndTypeError())
.onErrorReturn(DataAccessException.class, otherError())
.onErrorReturn(IvcFileException.class, fileError())
.subscribe(sink::success)
).subscribe());
}
分析源码可以发现,类TransactionAspectSupport中,prepareTransactionInfo方法要准备一个TransactionInfo:
// We always bind the TransactionInfo to the thread, even if we didn't create
// a new transaction here. This guarantees that the TransactionInfo stack
// will be managed correctly even if no transaction was created by this aspect.
txInfo.bindToThread();
其中,TransactionInfo绑定到了当前线程中。
事务的执行见invokeWithinTransaction方法:
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
如果捕获到异常,就执行completeTransactionAfterThrowing方法:
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
}
else {
// We don't roll back on this exception.
// Will still roll back if TransactionStatus.isRollbackOnly() is true.
try {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by commit exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by commit exception", ex);
throw ex2;
}
}
如果判断需要回滚(rollbackOn方法),就执行DataSourceTransactionManager类的回滚动作:
protected void doRollback(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
}
try {
con.rollback();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
}
}
WebFlux配合同步操作,性能太差了。
做优化,继续修改事务部分。
目前是这样写的,简单测试一下,资源占用减少,性能大幅提升:
public Mono<CommonOutput> logProcess(RecordFileSaveReq req) {
return transactionTemplate.execute(transactionStatus ->
//客户是否正确
Mono.just(customerRepository.get(req.getChargeNbr()))
//session是否正确
.zipWith(Mono.just(callRepository.get(req.getSessionid())))
//保存日志
.doOnNext(z -> repository.save(req))
//获取存储信息
.zipWhen(z -> Mono.just(storageRepository.getStorageOfEnt(z.getT1().getId())))
.flatMap(z -> fileEventProcess(req, z))
.doOnNext(z -> LOG.debug("file process {} end.", req.getVoiceAddress()))
.thenReturn(success())
);
}
其中,TransactionTemplate使用构造器注入:
private final ReactiveTransactionTemplete transactionTemplate;
public RecordFileSaveService(PlatformTransactionManager transactionTemplate) {
this.transactionTemplate = new ReactiveTransactionTemplete(transactionTemplate);
}
ReactiveTransactionTemplete是参考Spring的TransactionTemplete自己实现的。
首先,定义接口ReactiveTransactionCallback:
public interface ReactiveTransactionCallback<T> {
@NonNull
Mono<T> doInTransaction(TransactionStatus status);
}
定义接口ReactiveTransactionOperations:
public interface ReactiveTransactionOperations {
@NonNull
<T> Mono<T> execute(ReactiveTransactionCallback<T> action);
}
ReactiveTransactionTemplete的实现如下:
public class ReactiveTransactionTemplete extends DefaultTransactionDefinition
implements ReactiveTransactionOperations {
private Logger LOG = LoggerFactory.getLogger(ReactiveTransactionTemplete.class);
@NonNull
private final PlatformTransactionManager transactionManager;
public ReactiveTransactionTemplete(@NonNull PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
@Override
@NonNull
public <T> Mono<T> execute(@NonNull ReactiveTransactionCallback<T> action) {
Scheduler scheduler = Schedulers.newSingle(new DefaultThreadFactory("transaction"));
return Mono.just(true)
.publishOn(scheduler)
.map(b -> transactionManager.getTransaction(this))
.zipWhen(status -> doAndError(action, status, scheduler))
.publishOn(scheduler)
.doOnNext(z -> transactionManager.commit(z.getT1()))
.map(Tuple2::getT2)
.doFinally(t -> scheduler.dispose());
}
private <T> Mono<T> doAndError(@NonNull ReactiveTransactionCallback<T> action,
@NonNull TransactionStatus status,
@NonNull Scheduler scheduler) {
return action.doInTransaction(status)
.publishOn(scheduler)
.doOnError(e -> {
status.setRollbackOnly();
transactionManager.rollback(status);
});
}
}
每次调用execute方法,都会增加新的线程,在执行结束的时候,调用scheduler.dispose()释放线程资源。
execute方法的实现关键是,要确保getTransaction方法、commit方法和rollback方法被同一个线程调用。
这是因为,事务提交后,要清理资源,见TransactionSynchronizationManager类:
@Nullable
private static Object doUnbindResource(Object actualKey) {
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
Object value = map.remove(actualKey);
// Remove entire ThreadLocal if empty...
if (map.isEmpty()) {
resources.remove();
}
// Transparently suppress a ResourceHolder that was marked as void...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
value = null;
}
if (value != null && logger.isTraceEnabled()) {
logger.trace("Removed value [" + value + "] for key [" + actualKey + "] from thread [" +
Thread.currentThread().getName() + "]");
}
return value;
}