spring · 2019-08-25 0

Spring事务两种方式(基于注解和XML配置)

一、添加依赖

在maven中添加spring依赖、spring aop依赖、spring数据库依赖、spring事务依赖、mysql依赖

Spring事务在业务逻辑上添加事务,容器中保存的是这个业务逻辑的代理对象,所有需要aop依赖(aop的实现是依靠动态代理,生成代理对象)

<properties>
    <spring.version>5.1.9.RELEASE</spring.version>
    <mysql.version>8.0.15</mysql.version>
</properties>

<dependencies>
    <!--spring依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-expression</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <!--spring aop依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <!--spring数据库模块 -->
    <!--spring jdbc-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <!--spring orm-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <!--spring tx-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <!-- mysql -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql.version}</version>
    </dependency>

</dependencies>

二、配置文件

spring配置文件

spring-conf.xml

<?xml version="1.0" encoding="utf-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--扫描包-->
    <context:component-scan base-package="com.example.dao"/>
    <context:component-scan base-package="com.example.service"/>

    <!-- dataSource -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
        <property name="username" value="root" />
        <property name="password" value="123456" />
        <property name="url" value="jdbc:mysql://localhost:3306/jdbc_test?useSSL=false" />
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver" />
    </bean>

    <!--jdbc template-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <constructor-arg name="dataSource" ref="dataSource"/>
    </bean>

    <!--事务控制 配置事务管理器-->
    <bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--控制住数据源-->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--支持事务注解的(@Transactional)-->
    <tx:annotation-driven transaction-manager="tm"/>

</beans>

三、测试

1.数据库表:

bankuser有字段bid、balance

2.实体类:

Bankuser.java

public class Bankuser {

    private Integer bid;
    private Integer balance;

    public Bankuser() {

    }

    public Bankuser(Integer bid, Integer balance) {
        this.bid = bid;
        this.balance = balance;
    }

    @Override
    public String toString() {
        return "Bankuser{" +
                "bid=" + bid +
                ", balance=" + balance +
                '}';
    }
    //set、get方法
}

3.dao层

BankuserDao.java

@Repository
public class BankuserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public int updateBalance(Integer bid, Integer money) {
        String sql = "update bankuser set balance=balance+? where bid = ?";
        int update = jdbcTemplate.update(sql, money, bid);
        return update;
    }

    public List<Bankuser> selectAll(){
        String sql = "select bid,balance from bankuser";
        List<Bankuser> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Bankuser.class));
        return list;
    }

}

4.service层

BankuserService.java

transfer()方法上添加事务

对id为1的用户banlance减100,id为2的用户减200;

如果方法上添加事务,1/0语句会发生异常,触发事务回滚;如果方法上没有添加事务,会造成第一个updateBalance()成功修改数据,第二个updateBalance()执行不到,没有修改数据

@Service
public class BankuserService {

    @Autowired
    private BankuserDao bankuserDao;

    @Transactional
    public void transfer(){
        bankuserDao.updateBalance(1,-100);
        int i = 1/0;
        bankuserDao.updateBalance(2,100);

    }

    public List<Bankuser> selectAll(){
        return bankuserDao.selectAll();
    }
}

5.测试类

有事务的业务逻辑,容器中保存的是这个业务逻辑的代理对象

public class Main {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-conf1.xml");
        IBankuserService bankuserService = context.getBean(IBankuserService.class);
        try {
            bankuserService.transfer();
            System.out.println("操作成功");
        } catch (Exception e) {
            System.out.println(String.format("操作失败:%s", e.getMessage()));
        }
        List<Bankuser> list = bankuserService.selectAll();
        list.forEach(System.out::println);
    }
}

四、事务属性

只有 @Transactional 注解应用到 public 方法,才能进行事务管理

1.timeout

默认值为-1

@Transactional(timeout = 3),超时

事务超出指定执行时长后自动终止并回滚(单位为秒)

2.readOnly

默认值为 readOnly = false

@Transactional(readOnly = true),设置事务为只读事务

可以进行事务优化,加快查询速度,只能用于查询

3.noRollbackFor、noRollbackForClassName

异常分为:运行时异常、编译时异常

运行时异常,可以处理,默认都回滚;编译时异常,try-catch或throws处理,默认不回滚

@Transactional(noRollbackFor = {ArithmeticException.class, NullPointerException.class})

哪些异常事务可以不回滚(原本运行时异常是回滚的)

4.rollbackFor、rollbackForClassName

@Transactional(rollbackFor = {FileNotFoundException.class})

哪些异常事务需要回滚(原本编译时异常是不回滚的)

5.isolation

默认值采用 DEFAULT

数据库事务并发问题:

1)脏读:A修改数据,B读取修改后的数据,A回滚,B属于脏读

2)不可重复读:A读数据,B修改数据,A再次读两次数据不一致

3)幻读:A读数据,B插入数据,A读数据多了几条数据

隔离级别:

1)读未提交:read uncommited,允许B读取A未提交的数据(会发生脏读)

2)读已提交:read commited,要求B只能读取A以提交的修改(避免脏读,会发生不可重复读,发生幻读)

3)可重复读:repeatable read,确保A可以多次从一个字段中读取到相同的值,即A执行期间禁止其它事务对这个字段进行更新

4)串行化:serializable,确保A可以多次从一个表中读取相同的行,在A执行期间,禁止其它事务对这个表进行添加、更新、删除操作,可以避免任何并发问题

mysql支持1)、2)、3)、4),oracle支持2)、4)

@Transactional(isolation = Isolation.READ_UNCOMMITTED),设置隔离级别为读未提交

6.propagation

默认值为 Propagation.REQUIRED

事务传播行为:

required(当前方法需要事务,如果有事务,就用别人的事务)、required_new(开启一个新事务)

如果是如果是required事务的属性都是继承大事务的,而propagation=Propagation.REQUIRES_NEW可以调整

@Transactional(propagation = Propagation.REQUIRED) ,当前方法需要事务,如果有事务,就用别人的事务

五、基于XML的事务配置

spring-conf.xml

<?xml version="1.0" encoding="utf-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- dataSource -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
        <property name="username" value="root" />
        <property name="password" value="123456" />
        <property name="url" value="jdbc:mysql://localhost:3306/jdbc_test?useSSL=false" />
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver" />
    </bean>

    <!--jdbc template-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <constructor-arg name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="bankuserDao" class="com.example.dao.BankuserDao" >
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>

    <bean id="bankuserService" class="com.example.service.BankuserService">
        <property name="bankuserDao" ref="bankuserDao"/>
    </bean>

    <!--事务控制 配置事务管理器-->
    <bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--控制住数据源-->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--支持事务注解的(@Transactional)-->
    <!--<tx:annotation-driven transaction-manager="tm"/>-->

    <!--配置切面 aop-->
    <aop:config>
        <!--切入点表达式表明事务管理器切入哪些方法-->
        <aop:pointcut id="txPont" expression="execution(* com.example.service.*.*(..))"/>
        <!-- advice-ref指向切面类,有前置通知,后置通知等 -->
        <aop:advisor advice-ref="myAdvice" pointcut-ref="txPont"/>
    </aop:config>

    <!--transaction-manager指定配置哪个事务管理器-->
    <tx:advice id="myAdvice" transaction-manager="tm">
        <!--事务属性-->
        <tx:attributes>
            <!--指明哪些方法是事务方法-->
            <tx:method name="transfer" propagation="REQUIRED" timeout="3"/>
            <tx:method name="get" read-only="true"/>
        </tx:attributes>
    </tx:advice>

</beans>

测试类

public class Main {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-conf1.xml");
        IBankuserService bankuserService = context.getBean(IBankuserService.class);
        try {
            bankuserService.transfer();
            System.out.println("操作成功");
        } catch (Exception e) {
            System.out.println(String.format("操作失败:%s", e.getMessage()));
        }
        List<Bankuser> list = bankuserService.selectAll();
        list.forEach(System.out::println);
    }
}

六、spring 事务原理

<tx:advice> 会转化为 TransactionInterceptor

例如,由 Proxy 形成的代理,每次调用方法时,会执行 InvocationHandler 的 invoke() 方法,即 JdkDynamicAopProxy 的 invoke() 方法

JdkDynamicAopProxy 的 invoke() 方法中,形成拦截链有TransactionInterceptor

// TransactionInterceptor.java
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
    // Work out the target class: may be {@code null}.
    // The TransactionAttributeSource should be passed the target class
    // as well as the method, which may be from an interface.
    Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

    // Adapt to TransactionAspectSupport's invokeWithinTransaction...
    return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}

createTransactionIfNecessary() 创建事务

// TransactionAspectSupport.java
@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 PlatformTransactionManager tm = determineTransactionManager(txAttr);
    final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

    if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
        // Standard transaction demarcation with getTransaction and commit/rollback calls.
        TransactionInfo txInfo = createTransactionIfNecessary(tm, 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.
            retVal = invocation.proceedWithInvocation();
        }
        catch (Throwable ex) {
            // target invocation exception
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        }
        finally {
            cleanupTransactionInfo(txInfo);
        }
        commitTransactionAfterReturning(txInfo);
        return retVal;
    }

    else {
        final ThrowableHolder throwableHolder = new ThrowableHolder();

        // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
        try {
            Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
                TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
                try {
                    return invocation.proceedWithInvocation();
                }
                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);
                }
            });

            // Check result state: It might indicate a Throwable to rethrow.
            if (throwableHolder.throwable != null) {
                throw throwableHolder.throwable;
            }
            return result;
        }
        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;
        }
    }
}

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) {
            @Override
            public 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");
            }
        }
    }
    return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}

DataSourceTransactionManager 继承 AbstractPlatformTransactionManager

// AbstractPlatformTransactionManager.java
@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
    Object transaction = doGetTransaction();

    // Cache debug flag to avoid repeated checks.
    boolean debugEnabled = logger.isDebugEnabled();

    if (definition == null) {
        // Use defaults if no transaction definition given.
        definition = new DefaultTransactionDefinition();
    }

    if (isExistingTransaction(transaction)) {
        // Existing transaction found -> check propagation behavior to find out how to behave.
        return handleExistingTransaction(definition, transaction, debugEnabled);
    }

    // Check definition settings for new transaction.
    if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
        throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
    }

    // No existing transaction found -> check propagation behavior to find out how to proceed.
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
        throw new IllegalTransactionStateException(
                "No existing transaction found for transaction marked with propagation 'mandatory'");
    }
    else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
            definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
            definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
        SuspendedResourcesHolder suspendedResources = suspend(null);
        if (debugEnabled) {
            logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
        }
        try {
            boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
            DefaultTransactionStatus status = newTransactionStatus(
                    definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
            doBegin(transaction, definition);
            prepareSynchronization(status, definition);
            return status;
        }
        catch (RuntimeException | Error ex) {
            resume(null, suspendedResources);
            throw ex;
        }
    }
    else {
        // Create "empty" transaction: no actual transaction, but potentially synchronization.
        if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
            logger.warn("Custom isolation level specified but no actual transaction initiated; " +
                    "isolation level will effectively be ignored: " + definition);
        }
        boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
        return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
    }
}

从 TransactionSynchronizationManager 获得 ConnectionHolder

doBegin() 方法判断如果 ConnectionHolder 为 null,则从数据库连接池得到一个连接,创建一个新的 ConnectionHolder,并把这个连接设置非自动提交

调用 TransactionSynchronizationManager.bindResource() 方法,datasouce 为 key,connectionHolder 为 value,放入 ThreadLocal 中

// DataSourceTransactionManager.java
@Override
protected Object doGetTransaction() {
    DataSourceTransactionObject txObject = new DataSourceTransactionObject();
    txObject.setSavepointAllowed(isNestedTransactionAllowed());
    ConnectionHolder conHolder =
            (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
    txObject.setConnectionHolder(conHolder, false);
    return txObject;
}

@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);

        // 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);
    }
}
// TransactionSynchronizationManager.java
public static void bindResource(Object key, Object value) throws IllegalStateException {
    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
    Assert.notNull(value, "Value must not be null");
    Map<Object, Object> map = resources.get();
    // set ThreadLocal Map if none found
    if (map == null) {
        map = new HashMap<>();
        resources.set(map);
    }
    Object oldValue = map.put(actualKey, value);
    // Transparently suppress a ResourceHolder that was marked as void...
    if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
        oldValue = null;
    }
    if (oldValue != null) {
        throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
                actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
    }
    if (logger.isTraceEnabled()) {
        logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
                Thread.currentThread().getName() + "]");
    }
}

@Nullable
public static Object getResource(Object key) {
    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
    Object value = doGetResource(actualKey);
    if (value != null && logger.isTraceEnabled()) {
        logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
                Thread.currentThread().getName() + "]");
    }
    return value;
}

@Nullable
private static Object doGetResource(Object actualKey) {
    Map<Object, Object> map = resources.get();
    if (map == null) {
        return null;
    }
    Object value = map.get(actualKey);
    // Transparently remove ResourceHolder that was marked as void...
    if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
        map.remove(actualKey);
        // Remove entire ThreadLocal if empty...
        if (map.isEmpty()) {
            resources.remove();
        }
        value = null;
    }
    return value;
}

实现jdbc操作,提交,回滚。保证同一个连接,依靠 aop 形成代理类,执行方法前,执行 TransactionInterceptor 拦截器。

TransactionInterceptor 利用 DataSourceTransactionManager 获得连接,把连接放入 TransactionSynchronizationManager 的 ThreadLocal<Map<Object, Object>> resources

执行操作时,例如 jdbcTemplate,通过 TransactionSynchronizationManager 获得连接

七、事务失效

1.访问权限问题

spring 要求被代理方法必须是 public 的

在 AbstractFallbackTransactionAttributeSource 类的 computeTransactionAttribute 方法中有个判断,如果目标方法不是 public ,则 TransactionAttribute 返回 null ,即不支持事务

// AbstractFallbackTransactionAttributeSource.java
@Nullable
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
    // Don't allow no-public methods as required.
    if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
        return null;
    }

    // The method may be on an interface, but we need attributes from the target class.
    // If the target class is null, the method will be unchanged.
    Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

    // First try is the method in the target class.
    TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
    if (txAttr != null) {
        return txAttr;
    }

    // Second try is the transaction attribute on the target class.
    txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
    if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
        return txAttr;
    }

    if (specificMethod != method) {
        // Fallback is to look at the original method.
        txAttr = findTransactionAttribute(method);
        if (txAttr != null) {
            return txAttr;
        }
        // Last fallback is the class of the original method.
        txAttr = findTransactionAttribute(method.getDeclaringClass());
        if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
            return txAttr;
        }
    }

    return null;
}

2. 方法用 final 或 static 修饰

spring 事务底层使用了 aop,也就是通过 jdk 动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。

但如果某个方法用 final 修饰了,那么在它的代理类中,就无法重写该方法,而添加事务功能。

如果某个方法是 static 的,同样无法通过动态代理,变成事务方法。

3.方法内部调用

方法拥有事务的能力是因为 spring aop 生成代理了对象,但是如果方法内部调用,则直接调用了this对象的方法

解决办法:

  1. 新加一个Service方法
  2. 在该Service类中注入自己
  3. 通过AopContent类,使用AopContext.currentProxy()获取代理对象

需要配置 <aop:config expose-proxy="true">,则才会在每次调用方法时,把代理对象设置到 AopContext 中的 ThreadLocal<Object> currentProxy

4.未被spring管理

即使用spring事务的前提是:对象要被spring管理,需要创建bean实例。

5.多线程调用

两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。

同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务。

6.表不支持事务

myisam好用,但有个很致命的问题是:不支持事务。

7.未开启事务

如果使用的是springboot项目,springboot通过 DataSourceTransactionManagerAutoConfiguration 类,已经默默开启了事务。只需要配置spring.datasource相关参数即可。

如果使用的还是传统的spring项目,则需要在 applicationContext.xml 文件中,手动配置事务相关参数。如果忘了配置,事务肯定是不会生效的。

<!-- 配置事务管理器 -->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="tm">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<tx:advice id="advice" transaction-manager="tm"> 
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

<!-- 用切点把事务切进去 -->
<aop:config>
    <aop:pointcut expression="execution(* com.example.*.*(..))" id="pointcut"/>
    <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
</aop:config>