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