Spring 多线程的事务处理
问题起因
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);
}
}
通过对 doBegin
和 doGetTransaction
方法的分析,我们可以知道,只要 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 多线程的事务处理的更多相关文章
- spring 多线程 注入 服务层 问题
在用多线程的时候,里面要用到Spring注入服务层,或者是逻辑层的时候,一般是注入不进去的.具体原因应该是线程启动时没有用到Spring实例不池.所以注入的变量值都为null. 详细:http://h ...
- spring多线程与定时任务
本篇主要描述一下spring的多线程的使用与定时任务的使用. 1.spring多线程任务的使用 spring通过任务执行器TaskExecutor来实现多线程与并发编程.通常使用ThreadPoolT ...
- hibernate整合进spring后的事务处理
单独使用hibernate处理事务 本来只用hibernate开发,从而可以省了DAO层实现数据库访问和跨数据库,也可以对代码进行更好的封装,当我们web中单独使用hibernate时,我们需要单独的 ...
- spring多线程初探
6月14号 晴 最高温度37 今天很热的一天啊,开发的任务现在正在测试阶段,手头没有什么工作任务,忙里偷闲,丰富一下我的blog. 前两天有个需求:调用第三方接口,这个接口的响应时间有点长,需 ...
- Spring多线程批量发送邮件(ThreadPoolTaskExecutor)
1,需求:使用多线程批量发送邮件 需要批量发送邮件大概400封左右,但是因为发送邮件受网络限制,所以经常导致等待超时.所以就想到了使用多线程来发邮件,因为是异步的所以返回结果不受发邮件影响. 2,思路 ...
- 解决spring多线程不共享事务的问题
在一个事务中使用多线程操作数据库时,若同时存在对数据库的读写操作,可能出现数据读取的不准确,因为多线程将不会共享同一个事务(也就是说子线程和主线程的事务不一样),为了解决这个问题,可以使用spring ...
- spring多线程
Spring4.x高级话题(二):多线程 一. 点睛 Spring通过任务执行器(TaskExecutor)来实现多线程和并发编程.使用ThreadPoolTaskExecutor可实现一个基于线程池 ...
- Spring多线程编程
在Spring框架下如何保证线程安全,如何很happy顺畅地并发编程. 常见的如下面的这坨代码,在Spring中,默认是单例的,也就是说,在同一时刻只有一个SpringOrdinaryClass的单实 ...
- 补充---spring多线程任务调度
在spring任务调度的基础上增加多线程 三种方式: (1)使用OpenSymphony Quartz 调度器 (2)使用JDK Timer支持类 (3)SpringTaskExecutor抽象 sp ...
- spring 多线程 写入数据库 和 写入 xml文件
最近工作中遇到一个需求 多线程先爬取页面 然后将爬取的结果持久化到数据库中 ,一些大文本的内容需要持久化到 xml文件中; 下面是运行后的结果: xml 文件写入结果: 数据库写入结果: 再来张项目结 ...
随机推荐
- gitbook生成静态页面不跳转
gitbook页面不跳转 现在可以在localhost:4000/查看自己的网页了.而且生成的网页存在_book文件夹中,下次点击 _book文件夹中的index.html就能打开网页,内容无更新,就 ...
- C语言指针函数和函数指针区别(转)
C语言函数指针和指针函数的区别C和C++中经常会用到指针,和数据项一样,函数也是有地址的,函数的地址是存储其机器语言代码的内存的开始地址. 指针函数和函数指针经常会混淆,一个是返回指针的函数,另一个是 ...
- MySQL系列之——错误日志(log_error)、二进制日志(binary logs)、慢日志(slow_log)
文章目录 1.错误日志(log_error) 1.1 作用 1.2 错误日志配置 1.3 日志内容查看 2. binlog(binary logs):二进制日志 ***** 2.1 作用 2.2 bi ...
- 11g编译bbed
报错如下: make -f ins_rdbms.mk $ORACLE_HOME/rdbms/lib/bbed Linking BBED utility (bbed) rm -f /u01/app/or ...
- Amazon MSK 可靠性最佳实践
1. Amazon MSK介绍 Kafka作为老牌的开源分布式事件流平台,已经广泛用于如数据集成,流处理,数据管道等各种应用中. 亚马逊云科技也于2019年2月推出了Apache Kafka的云托管版 ...
- [ABC208E] Digit Products 题解
Digit Products 题目大意 求有多少个不大于 \(n\) 的正整数,使得该正整数各位乘积不大于 \(k\). 思路分析 观察数据范围,首先考虑数位 DP. 考虑设计记忆化搜索函数 dfs( ...
- Java初始化顺序及使用Spring情况下初始化
Java初始化顺序 1 无继承情况下的Java初始化顺序: class Sample { Sample(String s) { System.out. ...
- C语言中strchr()和index()
一. C标准的一些问题 在ubuntu下rindex()函数使用没有问题,在minGW下会报错,undefined reference to `rindex',warning: implicit de ...
- Codeforces Round #702 (Div. 3) 题解
写在前边 链接:Codeforces Round #702 (Div. 3) 比较简单,但是总是感觉脑子有点转不过弯来. A. Dense Array 链接:A题链接 题目大意: 在数组中插入若干个数 ...
- 一个NET8 AOT编译的辅助项目,让你的任何可执行应用快速部署为服务
不知道大家有没有和小编一样,很多时候想将自己的一些应用转为服务运行,比如一些控制台应用或者.NET Core应用,但是又不想重新编码,把他们转为服务,本文将给大家提供些我使用过的方法,并提供一个基于N ...