1.事务概述
事务是一种机制,用以维护数据库确保数据的完整性和一致性。。事务是用户定义的一个数据库操作序列,这些操作要么全做要么全不做,是一个不可分割的工作单位。例如,在关系数据库中,一个事务可以是一条SQL语句、一组SQL语句或整个程序。MySQL中主要使用INNODB存储引擎来支持事务。
在SQL语言中,定义事务的语句有三条:
BEGIN | START TRANSACTION :开启事务
COMMIT :提交事务
ROLLBACK: 回滚事务
START TRANSACTION;UPDATE `tb_score`
SET `score` = '91'
WHERE`id` = '1';COMMIT;
2.事务的ACID特性:
原子性A,一致性C,隔离性I,持久性D。
原性性(Actomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以操持完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。
隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。
3.事务的隔离级别:
READ UNCOMMITTED:会出现脏读,不可重复读,幻读
READ COMMITTED:会出现不可重复读,幻读,解决脏读
REPEATABLE READ:会出现幻读(Mysql默认的隔离级别,但是Repeatable read配合gap锁不会出现幻读!) 解决不可重复读和解决脏读
SERIALIZABLE:串行,避免以上的情况
隔离粒度从上到下 加强。
//查看当前事物级别:
SELECT @@transaction_isolation;
//设置mysql的隔离级别:
set session transaction isolation level `设置事务隔离级别`
eg:
//设置read uncommitted级别:
set session transaction isolation level read uncommitted;
//设置read committed级别:
set session transaction isolation level read committed;
//设置repeatable read级别:
set session transaction isolation level repeatable read;
//设置serializable级别:
set session transaction isolation level serializable;
隔离级别不同引发的问题点:
脏读(Dirty Reads):一个事务正在对一条记录做修改,在这个事务并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”的数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做“脏读”。
简述:一个事务读取到了因为一个事务未最终提交的变更过程中的数据。
不可重复读(Non-Repeatable Reads):一个事务在读取某些数据已经发生了改变、或某些记录已经被删除了!这种现象叫做“不可重复读”。
简述:一个事务修改了另一个事务未最终提交的读对应的数据。
幻读(Phantom Reads):一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。
简述:一个事务A通过某些查询条件查出来了一些记录,在该事务未提交前,其他的事务写入或者删除了一些符合搜索条件的数据。导致事务A最终查询出的数据条数变多或者变少。
小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。
4.事务的ACID innodb底层的技术支持
原子性A:主要是通过mysql 中的undo.log 回滚日志保障。比如对某一行数据进行了INSERT语句操作,那么undo log就记录一条与之相反的DELETE操作。主要用于事务的每个修改操作的逆操作。
一致性C:通过原子性 持久性和隔离性最终保障。
隔离性I:mysql 的各种锁和mvcc机制保障。
持久性D: 通过mysql redo.log 重做日志保障。提供再写入操作,恢复提交事务修改的页操作,来保证事务的持久性
注:
redo log 和undo log都是引擎层(innodb)实现的日志。
- undo log(回滚日志) 是Innodb 存储引擎层生成的日志 实现了事务中的原子性,主要用于事务回滚和 MVCC
- redo log(重做日志) 是 Innodb 存储引擎层生成的日志,实现了事务中的持久性,主要用于故障恢复;
4.1 Redo log
MySQL 中的数据是存储在磁盘上的,但是如果每次读写数据都通过磁盘的话,读写的效率会非常低,所以 **InnoDB 在内存中设置了一个区域 Buffer Pool,可以直接通过内存来读取和修改数据,后续再将内存中的数据更新到磁盘中。**但是内存中的数据是易失性的,可能随着进程、系统崩溃等情况而丢失,所以 MySQL 设计了 redo log 来解决这类问题。
当有一条记录需要更新的时候,InnoDB 引擎就会先更新内存(修改 Buffer Pool 中的数据页,同时标记为脏页),然后将本次对这个页的修改以 redo log 的形式记录下来(持久化到磁盘),这个时候更新就算完成了。后续,InnoDB 引擎会在适当的时候,由后台线程将缓存在 Buffer Pool 的脏页刷新到磁盘里,这就是 WAL (Write-Ahead Logging)技术。WAL 技术指的是, MySQL 的写操作并不是立刻写到磁盘上,而是先写日志,然后在合适的时间再写到磁盘上。
优点:
1.redo日志占用的空间非常小: 存储表空间ID、页号、偏移量以及需要更新的值,所需的存储空间是很小的,刷盘快
2.redo日志是顺序写入磁盘的,顺序Io,效率比随机Io快。
3.redo log的写入并不是直接写入磁盘的,InnoDB引擎会在写redo log的时候先写redo log buffer,之后以 一 定的频率 刷入到真正的redo log file 中。
4.2 Undo log
作用1:异常情况回滚数据,保证数据的原子性。
作用2:MVCC 多版本并发控制里使用undo.log 中的roll_point回滚指针的属性来形成版本链。
5.Spring 事务
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。
5.1Spring的事务支持
Spring 支持两种事务方式,分别是编程式事务和声明式事务@Transactional。其中声明式事务是一个基于AOP的实现操作。
但是项目中还是推荐编程式事务,虽然有代码入侵,但是性能可能更好,声明式事务虽然理论上优于编程式事务,注解声明事务简单,但也有不足,声明式事务管理的粒度是方法级别,而编程式事务是可以精确到代码块级别的。而且不注意@Transactional事务的失效场景的话,所加的事务并不会生效。
编程式事务
编程式事务是指将事务管理代码嵌入嵌入到业务代码中,TransactionTemplate 模板方法模式来控制事务的提交和回滚。下面的伪代码实现如下:
@Autowired
private TransactionTemplate transactionTemplate;public void testTransaction() {transactionTemplate.execute(new TransactionCallbackWithoutResult() {@Overrideprotected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {try {// .... 业务代码} catch (Exception e){//回滚transactionStatus.setRollbackOnly();}}});
}
声明式事务 @Transactional注解方式
Spring声明式事务使用AOP 的环绕增强方式,在方法执行之前开启事务,在方法执行之后提交或回滚事务。对应的实现为 TransactionInterceptor ,其实现了 MethodInterceptor,即,通过AOP的环绕增强方式。
底层更具体的源码 后续的博客中补充。
TransactionInterceptor 事务拦截器 核心的 invoke(MethodInvocation invocation)方法。
TransactionInterceptor 类继承了TransactionAspectSupport类。实现了方法器拦截接口MethodInterceptor。核心方法为invoke方法中调用的invokeWithinTransaction方法。
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {/*** 无参构造方法* Create a new TransactionInterceptor.*/public TransactionInterceptor() {}/***有参构造方法* Create a new TransactionInterceptor.*/public TransactionInterceptor(TransactionManager ptm, TransactionAttributeSource tas) {setTransactionManager(ptm);setTransactionAttributeSource(tas);}@Override@Nullablepublic Object invoke(MethodInvocation invocation) throws Throwable {//获取需要事务增强逻辑的目标类Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);// Adapt to TransactionAspectSupport's invokeWithinTransaction...//事务织入增强return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {@Override@Nullablepublic Object proceedWithInvocation() throws Throwable {return invocation.proceed();}@Overridepublic Object getTarget() {return invocation.getThis();}@Overridepublic Object[] getArguments() {return invocation.getArguments();}});}
}
TransactionAspectSupport类中的核心事务增强方法invokeWithinTransaction,该方法中的createTransactionIfNecessary方法开启创建事务
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,final InvocationCallback invocation) throws Throwable {// If the transaction attribute is null, the method is non-transactional.//获取事务属性资源TransactionAttributeSource tas = getTransactionAttributeSource();//获取事务属性信息final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);//获取对应的事务管理器final TransactionManager tm = determineTransactionManager(txAttr);if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {boolean isSuspendingFunction = KotlinDetector.isSuspendingFunction(method);boolean hasSuspendingFlowReturnType = isSuspendingFunction &&COROUTINES_FLOW_CLASS_NAME.equals(new MethodParameter(method, -1).getParameterType().getName());if (isSuspendingFunction && !(invocation instanceof CoroutinesInvocationCallback)) {throw new IllegalStateException("Coroutines invocation not supported: " + method);}CoroutinesInvocationCallback corInv = (isSuspendingFunction ? (CoroutinesInvocationCallback) invocation : null);ReactiveTransactionSupport txSupport = this.transactionSupportCache.computeIfAbsent(method, key -> {Class<?> reactiveType =(isSuspendingFunction ? (hasSuspendingFlowReturnType ? Flux.class : Mono.class) : method.getReturnType());ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(reactiveType);if (adapter == null) {throw new IllegalStateException("Cannot apply reactive transaction to non-reactive return type: " +method.getReturnType());}return new ReactiveTransactionSupport(adapter);});InvocationCallback callback = invocation;if (corInv != null) {callback = () -> CoroutinesUtils.invokeSuspendingFunction(method, corInv.getTarget(), corInv.getArguments());}Object result = txSupport.invokeWithinTransaction(method, targetClass, callback, txAttr, (ReactiveTransactionManager) tm);if (corInv != null) {Publisher<?> pr = (Publisher<?>) result;return (hasSuspendingFlowReturnType ? KotlinDelegate.asFlow(pr) :KotlinDelegate.awaitSingleOrNull(pr, corInv.getContinuation()));}return result;}PlatformTransactionManager ptm = asPlatformTransactionManager(tm);final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {// Standard transaction demarcation with getTransaction and commit/rollback calls.//创建开启事务:封装成一个TransactionInfo,里面将事务属性绑定到了当前线程TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);Object retVal;try {// This is an around advice: Invoke the next interceptor in the chain.// This will normally result in a target object being invoked.//around advice 。调用实际的业务逻辑方法retVal = invocation.proceedWithInvocation();}catch (Throwable ex) {// target invocation exceptioncompleteTransactionAfterThrowing(txInfo, ex);throw ex;}finally {//清除事务信息cleanupTransactionInfo(txInfo);}if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {// Set rollback-only in case of Vavr failure matching our rollback rules...TransactionStatus status = txInfo.getTransactionStatus();if (status != null && txAttr != null) {retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);}}//提交事务commitTransactionAfterReturning(txInfo);return retVal;}else {Object result;final ThrowableHolder throwableHolder = new ThrowableHolder();// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.try {result = ((CallbackPreferringPlatformTransactionManager) ptm).execute(txAttr, status -> {TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status);try {Object retVal = invocation.proceedWithInvocation();if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {// Set rollback-only in case of Vavr failure matching our rollback rules...retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);}return retVal;}catch (Throwable ex) {if (txAttr.rollbackOn(ex)) {// A RuntimeException: will lead to a rollback.if (ex instanceof RuntimeException) {throw (RuntimeException) ex;}else {throw new ThrowableHolderException(ex);}}else {// A normal return value: will lead to a commit.throwableHolder.throwable = ex;return null;}}finally {cleanupTransactionInfo(txInfo);}});}catch (ThrowableHolderException ex) {throw ex.getCause();}catch (TransactionSystemException ex2) {if (throwableHolder.throwable != null) {logger.error("Application exception overridden by commit exception", throwableHolder.throwable);ex2.initApplicationException(throwableHolder.throwable);}throw ex2;}catch (Throwable ex2) {if (throwableHolder.throwable != null) {logger.error("Application exception overridden by commit exception", throwableHolder.throwable);}throw ex2;}// Check result state: It might indicate a Throwable to rethrow.if (throwableHolder.throwable != null) {throw throwableHolder.throwable;}return result;}
}
createTransactionIfNecessary方法 开启创建事务,
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {// If no name specified, apply method identification as transaction name.if (txAttr != null && txAttr.getName() == null) {txAttr = new DelegatingTransactionAttribute(txAttr) {@Overridepublic String getName() {return joinpointIdentification;}};}TransactionStatus status = null;if (txAttr != null) {if (tm != null) {//事务管理器开启事务,返回事务状态status = tm.getTransaction(txAttr);}else {if (logger.isDebugEnabled()) {logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +"] because no transaction manager has been configured");}}}//封装成一个TransactionInfo,里面将事务属性绑定到了当前线程return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
TransactionManager.getTransaction(transactionAttribute)依据事务隔离级别开启事务
@Override
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();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 {// Create "empty" transaction: no actual transaction, but potentially synchronization.if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {logger.warn("Custom isolation level specified but no actual transaction initiated; " +"isolation level will effectively be ignored: " + def);}boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);}
}
/**
*开启事务
*/
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;
}
doBegin方法的不同实现。
DataSourceTransactionManager中的实现
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;Connection con = null;try {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);}txObject.getConnectionHolder().setSynchronizedWithTransaction(true);con = txObject.getConnectionHolder().getConnection();Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);txObject.setPreviousIsolationLevel(previousIsolationLevel);txObject.setReadOnly(definition.isReadOnly());// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,// so we don't want to do it unnecessarily (for example if we've explicitly// configured the connection pool to set it already).if (con.getAutoCommit()) {txObject.setMustRestoreAutoCommit(true);if (logger.isDebugEnabled()) {logger.debug("Switching JDBC Connection [" + con + "] to manual commit");}con.setAutoCommit(false);}prepareTransactionalConnection(con, definition);txObject.getConnectionHolder().setTransactionActive(true);int timeout = determineTimeout(definition);if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {txObject.getConnectionHolder().setTimeoutInSeconds(timeout);}// Bind the connection holder to the thread.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);}
}
5.2 Spring事务传播机制
所谓spring事务的传播属性,就是定义在存在多个事务同时存在的时候,spring应该如何处理这些事务的行为。这些属性在TransactionDefinition中定义。7种 默认为REQUIRED
当事务方法被另外一个事务方法调用时,必须指定事务应该如何传播,例如,方法可能继续在当前事务中执行,也可以开启一个新的事务,在自己的事务中执行。
声明式事务的传播行为可以通过 @Transactional 注解中的 propagation 属性来定义,@Transactional 注解源代码如下:
package org.springframework.transaction.annotation;@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {@AliasFor("transactionManager")String value() default "";@AliasFor("value")String transactionManager() default "";String[] label() default {};Propagation propagation() default Propagation.REQUIRED;Isolation isolation() default Isolation.DEFAULT;int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;String timeoutString() default "";boolean readOnly() default false;Class<? extends Throwable>[] rollbackFor() default {};String[] rollbackForClassName() default {};Class<? extends Throwable>[] noRollbackFor() default {};String[] noRollbackForClassName() default {};
}
方法A是一个事务的方法,方法A执行过程中调用了方法B,那么方法B有无事务以及方法B对事务的要求不同都会对方法A的事务具体执行造成影响,同时方法A的事务对方法B的事务执行也有影响,这种影响具体是什么就由两个方法所定义的事务传播类型所决定。
REQUIRED(Spring默认的事务传播类型 required:需要、依赖、依靠):如果当前没有事务,则自己新建一个事务,如果当前存在事务则加入这个事务。
当A调用B的时候:如果A中没有事务,B中有事务,那么B会新建一个事务;如果A中也有事务、B中也有事务,那么B会加入到A中去,变成一个事务,这时,要么都成功,要么都失败。(假如A中有2个SQL,B中有2个SQL,那么这四个SQL会变成一个SQL,要么都成功,要么都失败)SUPPORTS(supports:支持;拥护):当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行
如果A中有事务,则B方法的事务加入A事务中,成为一个事务(一起成功,一起失败),如果A中没有事务,那么B就以非事务方式运行(执行完直接提交);MANDATORY(mandatory:强制性的):当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。
如果A中有事务,则B方法的事务加入A事务中,成为一个事务(一起成功,一起失败);如果A中没有事务,B中有事务,那么B就直接抛异常了,意思是B必须要支持回滚的事务中运行REQUIRES_NEW(requires_new:需要新建):创建一个新事务,如果存在当前事务,则挂起该事务。
B会新建一个事务,A和B事务互不干扰,他们出现问题回滚的时候,也都只回滚自己的事务;NOT_SUPPORTED(not supported:不支持):以非事务方式执行,如果当前存在事务,则挂起当前事务
被调用者B会以非事务方式运行(直接提交),如果当前有事务,也就是A中有事务,A会被挂起(不执行,等待B执行完,返回);A和B出现异常需要回滚,互不影响NEVER(never:从不): 如果当前没有事务存在,就以非事务方式执行;如果有,就抛出异常。就是B从不以事务方式运行
A中不能有事务,如果没有,B就以非事务方式执行,如果A存在事务,那么直接抛异常NESTED(nested:嵌套的事务)嵌套事务:如果当前事务存在,则在嵌套事务中执行,否则REQUIRED的操作一样(开启一个事务)
如果A中没有事务,那么B创建一个事务执行,如果A中也有事务,那么B会会把事务嵌套在里面。
5.3 常见Spring事务失效场景
a.数据表本身是不支持事务,导致事务失效
如果使用MySQL且存储引擎是MyISAM,则事务是不起作用的,原因是MyIASM不支持事务。
b.事务方法访问修饰符非public,是static、final,导致事务失效
spring要求被代理方法必须是public的。源码里有写对代理的方法是不是public校验。
如果事务是static、final的,同样无法通过动态代理,事务也是不会生效的。
Spring的声明式事务是基于Aop 动态代理实现的,我们无法重写final修饰的方法;
不管是JDK动态代理还是Cglib的动态代理,就是要通过代理的方式获取到代理的具体对象,而static方法修饰的方法是属于类的,不属于任何对象,所以static方法不能被重写,即便写法上是重写,但是并不具备重写的含义,也就是说static方法也不被进行动态代理。
c. @Transactional注解所在的类没有被spring管理,导致事务失效
加上@Service注解或者使用其他能注册成Spring Bean的方式或注解。
d.catch掉异常之后,没有再次抛出异常,导致事务失效
如果在加有事务的方法内,使用了try…catch…语句块对异常进行了捕获,而catch语句块没有throw new RuntimeException异常或者Spring支持的异常类型,则事务不会回滚。
e.直接调用内部方法(this),导致事务失效
非事务方法insert()中调用的自身类的事务方法insertUser()。spring采用动态代理机制来实现事务控制,而动态代理最终都是要调用原始对象的,而原始对象在去调用方法时,是不会再触发代理了!
f.事务的传播特性设置错了,事务也会失效
如下:propagation = Propagation.NEVER这种类型的传播特性不支持事务,如果有事务会抛出异常。
目前只有这三种传播特性才会创建新事物:REQUIRED、REQUIRES_NEW、NESTED