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