问题起因

Spring 的 JDBC 相关的依赖库已经提供了对 JDBC 类事务处理的统一解决方案,在正常情况下,我们只需要在需要添加事务的业务处理方法上加上 @Transactional 注解即可开启声明式的事务处理。这种方式在单线程的处理模式下都是可行的,这是因为 Spring 在对 @Transactional 注解的切面处理上通过一些 ThreaLocal 变量来绑定了事务的相关信息,因此在单线程的模式下能够很好地工作。

然而,由于与数据库的交互属于 IO 密集型的操作,在某些情况下,与数据库的交互次数可能会成为性能的瓶颈。在这种情况下,一种可行的优化方式便是通过多线程的方式来并行化与数据库的交互,因为在大部分的情况下,数据的查询之间属于相互独立的任务,因此使用多线程的方式来解决这一类性能问题在概念上来讲是可行的

如果通过多线程的方式来优化,随之而来的一个问题就是对于事务的处理,可能客户端希望在一个任务出现异常时就回滚整个事务,就像使用 @Transactional 注解的效果一样。但是很遗憾,在这种情况下,我们不能直接使用 @Transactional 来开启事务,因为多线程的处理导致预先绑定的事务信息无法被找到,因此需要寻找其它的解决方案

Spring 事务源码分析

本文将仅分析声明式的事务处理方式,同时不会分析有关切面的具体加载逻辑,因为我们的目标是通过分析源码来找到在多线程下事务的可行解决方案

@Transaction 对应的切面处理是在 org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction 方法中处理的,由于大部分情况下我们使用的都是声明式的阻塞式事务处理形式,因此我们也只关心这部分的处理逻辑:

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable { /*
获取事务的相关属性,如需要回滚的异常,事务的传播方式等
*/
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null); /*
根据当前所处的环境,决定具体要采用何种事务管理对象类型,对于 JDBC 事务来说,
该类型为 DataSourceTransactionManager
*/
final TransactionManager tm = determineTransactionManager(txAttr); // 省略响应式事务的处理逻辑。。。。。。。 PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// 检查是否需要开启事务,这里是我们重点分析的一个地方
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification); Object retVal;
try {
// 实际业务方法的处理
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// 事务的回滚处理
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
// 省略部分代码。。。。。 // 提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}
// 省略编程式的事务处理逻辑 。。。。。
}

接下来我们具体分析一下对于创建事务的有关处理:

protected TransactionInfo
createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
@Nullable TransactionAttribute txAttr,
final String joinpointIdentification) { TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
// 实际创建事务的处理
status = tm.getTransaction(txAttr);
}
else {
// 省略部分代码
}
}
// 针对事务的准备工作,目的是为了将事务信息绑定到当前的线程
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}

通过上面的分析,我们可以知道当前事务传里对象为 DataSourceTransactionManager,我们继续查看它对创建事务的处理过程:

public final TransactionStatus
getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException { // Use defaults if no transaction definition given.
TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults()); // 获取与当前线程绑定的事务信息
Object transaction = doGetTransaction();
boolean debugEnabled = logger.isDebugEnabled(); /*
检查当前执行过程中是否已经存在事务,如果存在,那么需要通过对 TransactionDefinition 的 propagationBehavior
来对当前的执行做出合适的事务处理形式
*/
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(def, transaction, debugEnabled);
} // Check definition settings for new transaction.
if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
} // No existing transaction found -> check propagation behavior to find out how to proceed.
if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
SuspendedResourcesHolder suspendedResources = suspend(null);
if (debugEnabled) {
logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
}
try {
/*
由于没有检测到事务,因此需要开启一个事务来支持事务的有关操作
*/
return startTransaction(def, transaction, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
}
else {
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
}
}

继续分析获取事务的代码,可以看到实际上最终是通过 TransactionSynchronizationManager 来检查绑定的事务信息的

// 获取当前绑定的事务
protected Object doGetTransaction() {
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
txObject.setSavepointAllowed(isNestedTransactionAllowed());
/*
获取与当前线程绑定的事务信息
*/
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
txObject.setConnectionHolder(conHolder, false);
return txObject;
} /*
检查当前线程持有的事务是否是有效的,如果在 doGetTransaction 方法中 TransactionSynchronizationManager
中能够检测到对应的 ConnectionHolder 并且是活跃的,则说明确实在方法执行前就已经存了事务
*/
protected boolean isExistingTransaction(Object transaction) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive());
} // JdbcTransactionObjectSupport
public boolean hasConnectionHolder() {
return (this.connectionHolder != null);
}

如果当前已经存在了事务,需要针对事务传播行为对当前的事务做出对应的行为:

private TransactionStatus handleExistingTransaction(
TransactionDefinition definition,
Object transaction,
boolean debugEnabled)
throws TransactionException {
/*
由于默认的是传播行为是 PROPAGATION_REQUIRED,即默认将当前的处理加入到当前已经存在的事务中,
我们主要关心这部分内容,因此省略了其它传播行为的处理
*/
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}

继续查看对于当前事务的处理:

// 简单地标记一下当前的事务状态
protected final DefaultTransactionStatus prepareTransactionStatus(
TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction,
boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) { DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, newTransaction, newSynchronization, debug, suspendedResources);
prepareSynchronization(status, definition);
return status;
} // TransactionSynchronizationManager 线程绑定对象的部分处理,不是特别重要
protected void prepareSynchronization(DefaultTransactionStatus status,
TransactionDefinition definition) {
if (status.isNewSynchronization()) {
TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction());
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(
definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT ?
definition.getIsolationLevel() : null);
TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());
TransactionSynchronizationManager.initSynchronization();
}
}

接下来我们继续查看对于启动事务时的有关处理:

// 开启一个新的事务
private TransactionStatus startTransaction(TransactionDefinition definition,
Object transaction,
boolean debugEnabled,
@Nullable SuspendedResourcesHolder suspendedResources) {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
} // 相当于开启事务的 BEGIN 语句
protected void doBegin(Object transaction, TransactionDefinition definition) {
/*
由上文的 doGetTransaction 方法可知当前的 transaction 一定为 DataSourceTransactionObject 类型
*/
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null; try {
/*
由于 TransactionSynchronizationManager.getResource(obtainDataSource()) 可能为 null,
因此需要对这种情况进行处理,避免为 null
*/
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
Connection newCon = obtainDataSource().getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
} // 省略一些不是特别重要的数据库连接和准备的相关代码 /*
到这里就比较关键了,结合上文 doGetTransaction 方法可知,这两者之间是对应的,只要 TransactionSynchronizationManager 绑定的资源对象一致,那么就能够检测到当前执行的事务信息
*/
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
} catch (Throwable ex) {
if (txObject.isNewConnectionHolder()) {
DataSourceUtils.releaseConnection(con, obtainDataSource());
txObject.setConnectionHolder(null, false);
}
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
}
}

通过对 doBegindoGetTransaction 方法的分析,我们可以知道,只要 TransactionSynchronizationManager#getResource(Object) 能够拿到同一个线程绑定的资源对象,那么我们就可以将这两个操作合并到一个事务中,并且能够直接复用 Spring 现有的事务处理逻辑!

具体解决方案

通过对 Spring 事务处理的源码分析,我们可以知道,如果希望当前线程执行上下文能够检测到事务的存在,我们只要通过 TransactionSynchronizationManager 绑定一致的事务资源对象即可。为了方便,我们可以直接将线程执行的任务封装到一个新的任务类中,在这个任务类中绑定相关的事务资源即可,对应的任务类如下:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionSynchronizationManager; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException; /**
* 用于 {@link DataSourceTransactionManager} 事务管理对应的任务线程,这个类的存在是为了使得
* Spring 中的事务管理能够在多线程的环境下依旧能够有效地工作.
* 对于要要执行的任务,可以将其封装成此任务对象,该任务对象在执行时将会绑定与 {@link DataSourceTransactionManager#getResourceFactory()}
* 对应的 {@link TransactionSynchronizationManager#getResourceMap()} 中关联的事务对象,以使得要执行的任务包含在已有的事务中
* (至少能保证存在一种可行的方式能够得到父线程的所处事务上下文),从而使得当前待执行的任务能够被现有统领事务进行管理
*
* @see DataSourceTransactionManager
* @see TransactionSynchronizationManager
*@author lxh
*/
public class DataSourceTransactionTask
implements Callable<TransactionStatus> { private final static Logger log = LoggerFactory.getLogger(DataSourceTransactionTask.class); /*
与 TransactionSynchronizationManager.resources 关联的事务属性对象的 Value 值,
在当前上下文中,为了保存与原有事务的完整性,这里的 resource 存储的是 DataSourceTransactionObject
*/
private final Object resource; // 当前 Spring 平台的事务管理对象
private final DataSourceTransactionManager txManager; // 实际需要运行的任务
private final Runnable runnable; // 与事务有关的描述信息
private final TransactionDefinition definition; public DataSourceTransactionTask(Object resource,
DataSourceTransactionManager txManager,
Runnable runnable,
TransactionDefinition definition) {
this.resource = resource;
this.txManager = txManager;
this.runnable = runnable;
this.definition = definition;
} @Override
public TransactionStatus call() {
// 通过源码分析可知,对于 JDBC 事务的处理,key 为对应的 DataSource 对象
Object key = txManager.getResourceFactory();
/*
resource 是在启动这个线程之前就已经被主线程开启的事务对象,
我们可以知道它实际上就是 DataSourceTransactionObject,我们将他绑定到
当前线程,即可使得当前线程能够感知到这个事务的存在
*/
TransactionSynchronizationManager.bindResource(key, resource);
TransactionStatus status = txManager.getTransaction(definition);
try {
runnable.run();
} catch (Throwable t) {
log.debug("任务执行出现异常", t);
status.setRollbackOnly(); // 出现了异常,需要将整个事务进行回滚
} finally {
// 移除与当前线程执行的关联关系,避免任务执行过程中的资源混乱
TransactionSynchronizationManager.unbindResource(key);
}
return status;
}
}

更进一步,我们可以使用线程池来进一步封装,从而避免自己手动创建线程或者其它的线程管理容器:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;
import reactor.util.function.Tuples; import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*; /**
* 用于需要执行并行任务的事务管理线程池,由于现有的 Spring 声明式事务在不同的线程中不能很好地工作,
* 而在某些情况下,可能需要考虑使用多个处理器的优势来提高方法的执行性能,因此定义了此类来提供一种类似
* 线程池的方式来执行可以并行执行的任务,并对将这些任务整合到 Spring 的事务管理中 <br />
* 具体的使用方式:
* <ol>
* <li>
* 如果你希望自己配置线程池的相关属性,可以手动创建一个 {@link ThreadPoolExecutor} 来作为构造参数通过
* {@link DataSourceTransactionExecutor#DataSourceTransactionExecutor(ThreadPoolExecutor,
* DataSourceTransactionManager, TransactionDefinition)} 进行构建,在构造时会复制这个 {@link ThreadPoolExecutor}
* 的相关属性来重新创建一个 {@link ThreadPoolExecutor} 进行任务的处理,因此不会影响到现有线程池的工作. 同时,值得注意的是,
* 对于传入的 {@link ThreadPoolExecutor} 参数中的 {@link ThreadPoolExecutor#getQueue()} 工作队列类型,必须保证提供一个
* 无参的构造函数来使得工作队列对象能够被重新创建,否则将会抛出异常 <br />
* 此外,除了任务执行者的参数外,至少还需要指定 {@link DataSourceTransactionManager} 用于任务执行时线程事务的统一管理,使得每个任务
* 执行时能够被 Spring 事务进行管理,由于 Spring 提供了对于事务的不同处理方式,因此也可以自定义传入 {@link TransactionDefinition}
* 来定义这些行为
* </li>
* <li>
* 对于需要执行的任务,只要将其作为 {@link Runnable} 参数通过 {@link #addTask(Runnable)} 的形式加入到当前的任务列表中,
* 在这个过程中实际的任务不会被执行.
* 当确定已经将任务加入完成后,通过调用 {@link #execute()} 方法来执行这些任务,这些任务的执行会被构造时传入的
* {@link DataSourceTransactionManager} 进行统一的事务管理,同时,任务执行完毕之后,当前的任务执行者将会被关闭,
* 并不能再继续添加任务
* </li>
* 以下面的例子为例,我们可以执行如下的几个业务处理:
* <pre>
* DataSourceTransactionExecutor executor = new DataSourceTransactionExecutor(txManager);
* executor.addTask(this::service1);
* executor.addTask(this::service2);
* executor.addTask(this::service3);
* executor.execute();
* </pre>
* </ol>
*
* @see DataSourceTransactionTask
* @see DataSourceTransactionManager
*@author lxh
*/
public class DataSourceTransactionExecutor { private final static Logger log = LoggerFactory.getLogger(DataSourceTransactionExecutor.class); private final List<Callable<TransactionStatus>> callableList = new ArrayList<>(); private final DataSourceTransactionManager txManager; private final TransactionStatus txStatus; private final Object txResource; private final ThreadPoolExecutor executor; public DataSourceTransactionExecutor(int coreSize,
int maxSize,
int keepTime,
TimeUnit timeUnit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler rejectHandler,
DataSourceTransactionManager txManager,
TransactionDefinition definition) {
this.txManager = txManager;
this.txStatus = txManager.getTransaction(definition);
this.txResource = TransactionSynchronizationManager.getResource(txManager.getResourceFactory());
executor = new ThreadPoolExecutor(coreSize, maxSize, keepTime,
timeUnit, workQueue, threadFactory, rejectHandler);
} public DataSourceTransactionExecutor(DataSourceTransactionManager txManager,
TransactionDefinition definition) {
this(Runtime.getRuntime().availableProcessors() * 2,
Runtime.getRuntime().availableProcessors() * 2,
60,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(),
Thread::new,
new ThreadPoolExecutor.AbortPolicy(),
txManager,
definition
);
} @SuppressWarnings("unchecked")
public DataSourceTransactionExecutor(ThreadPoolExecutor executor,
DataSourceTransactionManager txManager,
TransactionDefinition definition) {
// 复制一个线程池对象,避免一些线程问题
this.executor = new ThreadPoolExecutor(
executor.getCorePoolSize(),
executor.getMaximumPoolSize(),
executor.getKeepAliveTime(TimeUnit.SECONDS),
TimeUnit.SECONDS,
ReflectTool.createInstance(executor.getQueue().getClass()),
executor.getThreadFactory(),
ReflectTool.createInstance(executor.getRejectedExecutionHandler().getClass())
);
this.txManager = txManager;
this.txStatus = txManager.getTransaction(definition);
this.txResource = TransactionSynchronizationManager.getResource(txManager.getResourceFactory());
} public DataSourceTransactionExecutor(DataSourceTransactionManager txManager) {
this(txManager, new DefaultTransactionDefinition());
} public void addTask(Runnable task) {
callableList.add(DataSourceTransactionTask.Builder.aTask()
.runnable(task).txManager(txManager)
.resource(txResource).definition(new DefaultTransactionDefinition())
.build()
);
} public void addTask(Runnable task, TransactionDefinition def) {
callableList.add(DataSourceTransactionTask.Builder.aTask()
.runnable(task).txManager(txManager)
.resource(txResource).definition(def)
.build()
);
} public void execute() throws InterruptedException {
List<Future<TransactionStatus>> futures = new ArrayList<>();
for (Callable<TransactionStatus> callable : callableList) {
futures.add(executor.submit(callable));
}
executor.shutdown();
List<TransactionStatus> statusList = new ArrayList<>();
for (Future<TransactionStatus> future : futures) {
try {
statusList.add(future.get());
} catch (ExecutionException e) {
log.error("任务执行出现异常", e);
statusList.add(null);
}
}
Object[] statusArgs = new Object[statusList.size()];
statusList.toArray(statusArgs);
mergeTaskResult(statusArgs); // 合并每个任务的事务信息
} /**
* 以 Reactor 异步的方式执行这些任务,需要注意的是,当使用这个方法时,由于
* Reactor 的异步特性,如果业务方法使用了 @Transactional 注解修饰,Spring 的事务处理会发生在实际处理
* 事务之前,可能会导致数据库连接被释放,从而无法绑定对应的事务对象,使用时需要注意这一点
*/
public void asyncExecute() {
List<Mono<TransactionStatus>> monoList = new ArrayList<>(); Scheduler scheduler = Schedulers.fromExecutor(this.executor);
for (Callable<TransactionStatus> callable : callableList) {
monoList.add(Mono.fromCallable(callable)
.subscribeOn(scheduler));
}
Flux.zip(monoList, Tuples::fromArray)
.single()
.flatMap(tuple2 -> Mono.fromRunnable(() -> {
TransactionSynchronizationManager.bindResource(txManager.getResourceFactory(), txResource);
mergeTaskResult(tuple2.toArray());
}))
.subscribeOn(scheduler)
.doOnSubscribe(any -> log.info("开始执行事务的合并操作"))
.doFinally(any -> {
log.debug("合并事务处理执行完成");
scheduler.dispose();
executor.shutdown();
})
.subscribe();
} private void mergeTaskResult(Object... statusList) {
boolean exFlag = false;
for (Object obj : statusList) {
if (obj == null) {
exFlag = true;
continue;
}
// 在当前上下文中一定是 TransactionStatus 类型的对象
TransactionStatus status = (TransactionStatus) obj;
if (status.isRollbackOnly()) exFlag = true;
}
if (exFlag) {
log.debug("由于任务执行时出现异常,因此会将整个业务进操作进行回滚");
txManager.rollback(txStatus);
/*
这里抛出异常的原因是因为相关的业务方法可能被 @Transactional 修饰过,
从而导致提交只能回滚的事务而导致的提交异常,具体使用时可以考虑替换掉这个异常类型
*/
throw new RuntimeException("需要回滚的异常");
} else {
txManager.commit(txStatus);
}
}
}

Spring 多线程的事务处理的更多相关文章

  1. spring 多线程 注入 服务层 问题

    在用多线程的时候,里面要用到Spring注入服务层,或者是逻辑层的时候,一般是注入不进去的.具体原因应该是线程启动时没有用到Spring实例不池.所以注入的变量值都为null. 详细:http://h ...

  2. spring多线程与定时任务

    本篇主要描述一下spring的多线程的使用与定时任务的使用. 1.spring多线程任务的使用 spring通过任务执行器TaskExecutor来实现多线程与并发编程.通常使用ThreadPoolT ...

  3. hibernate整合进spring后的事务处理

    单独使用hibernate处理事务 本来只用hibernate开发,从而可以省了DAO层实现数据库访问和跨数据库,也可以对代码进行更好的封装,当我们web中单独使用hibernate时,我们需要单独的 ...

  4. spring多线程初探

    6月14号  晴  最高温度37   今天很热的一天啊,开发的任务现在正在测试阶段,手头没有什么工作任务,忙里偷闲,丰富一下我的blog. 前两天有个需求:调用第三方接口,这个接口的响应时间有点长,需 ...

  5. Spring多线程批量发送邮件(ThreadPoolTaskExecutor)

    1,需求:使用多线程批量发送邮件 需要批量发送邮件大概400封左右,但是因为发送邮件受网络限制,所以经常导致等待超时.所以就想到了使用多线程来发邮件,因为是异步的所以返回结果不受发邮件影响. 2,思路 ...

  6. 解决spring多线程不共享事务的问题

    在一个事务中使用多线程操作数据库时,若同时存在对数据库的读写操作,可能出现数据读取的不准确,因为多线程将不会共享同一个事务(也就是说子线程和主线程的事务不一样),为了解决这个问题,可以使用spring ...

  7. spring多线程

    Spring4.x高级话题(二):多线程 一. 点睛 Spring通过任务执行器(TaskExecutor)来实现多线程和并发编程.使用ThreadPoolTaskExecutor可实现一个基于线程池 ...

  8. Spring多线程编程

    在Spring框架下如何保证线程安全,如何很happy顺畅地并发编程. 常见的如下面的这坨代码,在Spring中,默认是单例的,也就是说,在同一时刻只有一个SpringOrdinaryClass的单实 ...

  9. 补充---spring多线程任务调度

    在spring任务调度的基础上增加多线程 三种方式: (1)使用OpenSymphony Quartz 调度器 (2)使用JDK Timer支持类 (3)SpringTaskExecutor抽象 sp ...

  10. spring 多线程 写入数据库 和 写入 xml文件

    最近工作中遇到一个需求 多线程先爬取页面 然后将爬取的结果持久化到数据库中 ,一些大文本的内容需要持久化到 xml文件中; 下面是运行后的结果: xml 文件写入结果: 数据库写入结果: 再来张项目结 ...

随机推荐

  1. 图形学、02 推导证明 | 任意一点经过透视投影后 z 坐标相对于之前有什么变化

    齐次坐标知识点: \(\begin{bmatrix} x \\ y \\ z \\ 1 \\\end{bmatrix} \Rightarrow\begin{bmatrix} nx \\ ny \\ n ...

  2. UVA10702 Travelling Salesman 题解

     UVA10702 Travelling Salesman 题解 题面: 有个旅行的商人,他每到一个的新城市,便卖掉所有东西再购买新东西,从而获得利润.从某城市 A 到某城市 B 有固定利润(B 到 ...

  3. 14.8 Socket 一收一发通信

    通常情况下我们在编写套接字通信程序时都会实现一收一发的通信模式,当客户端发送数据到服务端后,我们希望服务端处理请求后同样返回给我们一个状态值,并以此判断我们的请求是否被执行成功了,另外增加收发同步有助 ...

  4. 基于GPS定位和人脸识别的作业识别管理系统

    一.技术参数 mysql5.5 asp.net jquery 高德地图api 百度人脸识别api 二.功能简介 实现简单的施工项目管理,包括项目地点,工期,名称,编号等 实现作业人员的档案信息管理,包 ...

  5. 从零用VitePress搭建博客教程(4) – 如何自定义首页布局和主题样式修改?

    接上一节:从零用VitePress搭建博客教程(3) - VitePress页脚.标题logo.最后更新时间等相关细节配置 六.首页样式修改 有时候觉得自带的样式不好看,想自定义,首先我们在docs/ ...

  6. 『STAOI』G - Round 2 半个游记

    很刺激. 2023.3.2 23:17 第一次过审. 2023.3.5 00:02 第一次打回. 原因是背锅人的链接又双叒叕挂错了((( 2023.3.6 21:20 第二次过审. 2023.3.8 ...

  7. 【爬虫实战】用Python采集任意小红书笔记下的评论,爬了10000多条,含二级评论!

    目录 一.爬取目标 二.爬虫代码讲解 2.1 分析过程 2.2 爬虫代码 三.演示视频 一.爬取目标 您好!我是@马哥python说 ,一名10年程序猿. 我们继续分享Python爬虫的案例,今天爬取 ...

  8. 用结构化思维解一切BUG(2):实践原则

    背景 本文是系列文章<用结构化思维解一切BUG>的第二篇.本系列文章主要介绍一种「无需掌握技术细节,只需结构化思维和常识即可解一切BUG的方法」. 在前序文章<用结构化思维解一切BU ...

  9. Ansible自动化部署工具-组件及语法介绍

    大家好,我是蓝胖子,我认为自动化运维要做的事情就是把运维过程中的某些步骤流程化,代码化,这样在以后执行类似的操作的时候就可以解放双手了,让程序自动完成.避免出错,Ansible就是这方面非常好用的工具 ...

  10. MATLAB时间序列数据重建与平滑:HANTS滤波

      本文介绍在MATLAB中,实现基于HANTS算法(时间序列谐波分析法)的长时间序列数据去噪.重建.填补的详细方法.   HANTS(Harmonic Analysis of Time Series ...