Spring WebFlux -自定義ReactiveTransactionTemplete實現事務

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;
	}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章