Skip to content

6. Spring事务

6.1. 事务基础知识

事务要么整体生效,要么整体失效。在数据库上即多条SQL语句要么全执行成功,要么全执行失败数据库事务必须同时满足4个特性: 原子性(Atomic),一致性(Consistency),隔离性(Isolation)和持久性(Durabiliy)

一致性是最终目标,其他特性都是为了达到这个目标而采取的措施。

数据库管理系统一般采用重执行日志来确保原子性,一致性和持久性。 重执行日志记录了数据库变化的每一个动作,数据库在一个事务中执行一部分操作后发生错误退出,数据库即可根据重执行日志撤销已经执行的操作。对于已经提交的事务,即使数据库奔溃,在重启数据库时也能根据日志对尚未持久化的数据进行相应的重执行操作数据库管理系统采用数据库锁机制保证事务的隔离性。当多个事务视图对相同的数据进行操作时,只有持有锁的事务才能操作数据,直到前一个事务完成后,后面的事务才有机会对数据进行操作。

Oracle数据库使用了数据版本的机制,在回滚段为数据的每个变化都保存一个版本,使数据的更改不影响数据的读取。

6.2. 事务并发问题

在实际开发中数据库操作一般都是并发执行的,即有多个事务并发执行,并发执行就可能遇到问题,目前常见的问题如下:

丢失更新:

两个事务同时更新一行数据,最后一个事务的更新会覆盖掉第一个事务的更新,从而导致第一个事务更新的数据丢失,这是由于没有加锁造成的

img

脏读:

一个事务看到了另一个事务未提交的更新数据

不可重复读:

在同一事务中,多次读取同一数据却返回不同的结果;也就是有其他事务更改了这些数据

幻读:

一个事务在执行过程中读取到了另一个事务已提交的插入数据;即在第一个事务开始时读取到一批数据,但此后另一个事务又插入了新数据并提交,此时第一个事务又读取这批数据但发现多了一条,即好像发生幻觉一样

6.3. 数据的隔离级别(标准SQL)

数据库默认隔离级别: mysql ---repeatable,oracle,sql server ---read commited

隔离级别说明
DEFAULT使用后端数据库默认的隔离级别(spring中的的选择项)
READ_UNCOMMITED允许你读取还未提交的改变了的数据。可能导致脏、幻、不可重复读
READ_COMMITTED允许在并发事务已经提交后读取。可防止脏读,但幻读和 不可重复读仍可发生
REPEATABLE_READ对相同字段的多次读取是一致的,除非数据被事务本身改变。可防止脏、不可重复读,但幻读仍可能发生。
SERIALIZABLE完全服从ACID的隔离级别,确保不发生脏、幻、不可重复读。这在所有的隔离级别中是最慢的,它是典型的通过完全锁定在事务中涉及的数据表来完成的。

6.4. Spring的事务传播策略

传播行为说明
REQUIRED业务方法需要在一个事务中运行。如果方法运行时,已经处在一个事务中,那么加入到该事务,否则为自己创建一个新的事务
NOT_SUPPORTED声明方法不需要事务。如果方法没有关联到一个事务,容器不会为它开启事务。如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行
REQUIRESNEW属性表明不管是否存在事务,业务方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到方法执行结束,新事务才算结束,原先的事务才会恢复执行
MANDATORY该属性指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果业务方法在没有事务的环境下调用,容器就会抛出例外
SUPPORTS这一事务属性表明,如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分。如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行
NEVER指定业务方法绝对不能在事务范围内执行。如果业务方法在某个事务中执行,容器会抛出例外,只有业务方法没有关联到任何事务,才能正常执行
NESTED如果一个活动的事务存在,则运行在一个嵌套的事务中.,如果没有活动事务, 则按REQUIRED属性执行.它使用了一个单独的事务, 这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效

6.5. 编程式事务的操作

自己写代码通过TransactionDefinitionAPI控制事务,commit、rollback

6.6. 声明式的事务

6.6.1. 需求:

在两个账户之前进行转账,A+20 B-20

A15.15
B80.15
sql

DROP TABLE IF EXISTS `account`;

CREATE TABLE `account` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(100) COLLATE utf8_bin NOT NULL COMMENT '账户名称',
  `amount` decimal(7,2) NOT NULL DEFAULT '0.00' COMMENT '账户余额',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

/*Data for the table `account` */

insert  into `account`(`id`,`name`,`amount`) values (1,'张飞','8000.00'),(2,'赵云','8000.00');

image-20240314152659352

6.6.2. 实现

搭建spring-jdbc-transcation工程,使用spring-jdbc连接数据库

img

添加依赖

添加tx模块,如果已经依赖过jdbc模块,可以不用单独引入。

xml
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-framework</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.neuedu.tx</groupId>
    <artifactId>p12-spring-tx</artifactId>

    <name>p12-spring-tx</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <spring.version>5.2.4.RELEASE</spring.version>
        <aspectjweaver.version>1.9.6</aspectjweaver.version>
    </properties>

    <dependencies>
        <!--Spring对JDBC封装-->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc 包含tx  -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>


<!--       支持事务  的 aop-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${aspectjweaver.version}</version>
        </dependency>


        <!-- 测试的支持 https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- 数据源: https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
        <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.21</version>
        </dependency>


        <!--msyql数据库的驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>


    </dependencies>

</project>

实现转账的功能

  • AccountDao : 提供更新账户的功能 参数: 账户id,更新的金额(正数、负数)
    • 依赖于 JDBCTemplate
  • AccountServcie : 添加事务(调用两次更新账户的操作 dao.update(账户1 , 负数) ---> dao.udpate(另外一个账户, 正数 ))
    • 依赖AccountDao

6.6.3 使用xml当容器配置

  • 编写xml形式的配置文件
    • 配置数据源 dataSource
    • 配置JdbcTemplate(执行jdbc的 update ) 给Dao用

具体文件

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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd">


<!--    扫描  Service、Dao-->
    <context:component-scan base-package="com.neuedu.tx"></context:component-scan>


    <!-- 声明数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="root"/>
        <property name="password" value="root"/>
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/jdbctemp"/>
    </bean>


    <!--    声明JT-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>


   
</beans>
java
package com.neuedu.tx.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

/**
 * 项目:      spring-framework
 * 类名:       AccountDao
 * 创建时间:  2024/3/14 14:05
 * 描述 :
 * 作者 :     张金山
 * QQ :     314649444
 * Site:      https://jshand.gitee.io
 */
@Repository
public class AccountDao {


    @Autowired
    JdbcTemplate jdbcTemplate;


    public int updateAccount(int id, double amount){
        String sql = "update account set amount = amount + ? where id = ?";
        return  jdbcTemplate.update(sql,amount,id);
    }


}
java
package com.neuedu.tx.service;

import com.neuedu.tx.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 项目:      spring-framework
 * 类名:       AccountService
 * 创建时间:  2024/3/14 14:26
 * 描述 :
 * 作者 :     张金山
 * QQ :     314649444
 * Site:      https://jshand.gitee.io
 */
@Service
public class AccountService {
    @Autowired
    AccountDao accountDao;


    /***
     * 转账
     * @return
     */
    public boolean transfer(){

        int id1 = 1 ;
        double amount1 = -500;
        int count1 = accountDao.updateAccount(id1,amount1);

        
        int id2 = 2;
        double amount2 = 500;
        int count2 = accountDao.updateAccount(id2,amount2);

        return count1 + count2 == 2;
    }


    public void select(){

    }
}

使用spirng-test测试AcccountService

java
package com.neuedu.tx.service;

import com.neuedu.tx.AppConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import static org.junit.Assert.*;

/**
 * 项目:      spring-framework
 * 类名:       AccountServiceTest
 * 创建时间:  2024/3/14 14:29
 * 描述 :
 * 作者 :     张金山
 * QQ :     314649444
 * Site:      https://jshand.gitee.io
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-tx.xml")
public class AccountServiceTest {


    @Autowired
    AccountService accountService;

    @Test
    public void transfer() {
        accountService.transfer();
    }
}

通过观察结果,两个账户更新,id为1 的账户变为 7500 , id为2的账户变为8500

image-20240314153743473

一旦service中操作不能同时成功需要事务回滚,数据源中Connection默认autocommit 为true,也就是自动提交(一条直接提交),需要使用spring-tx有一个事务管理器,参考下例

java
package com.neuedu.tx.service;

import com.neuedu.tx.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 项目:      spring-framework
 * 类名:       AccountService
 * 创建时间:  2024/3/14 14:26
 * 描述 :
 * 作者 :     张金山
 * QQ :     314649444
 * Site:      https://jshand.gitee.io
 */
@Service
public class AccountService {
    @Autowired
    AccountDao accountDao;


    /***
     * 转账
     * @return
     */
    public boolean transfer(){

        int id1 = 1 ;
        double amount1 = -500;
        int count1 = accountDao.updateAccount(id1,amount1);

        System.out.println("与蜀国打仗,会计支出 打算下个月涨工资,比例是: " + (0 /0));

        int id2 = 2;
        double amount2 = 500;
        int count2 = accountDao.updateAccount(id2,amount2);

        return count1 + count2 == 2;
    }


    public void select(){

    }
}

上述代码中32行会引起报错,目前账户1的钱减去500变为7500,但是 账户2的钱没有发生变化还是8000,使用事务管理器解决问题

  • 在容器中声明想要使用的事务管理
    • 需要配置事务管理器的 属性(传播特性、隔离特性) advisor
  • 配置切面
    • 定义切点
    • 使用advisor + 切点

声明完的spring-tx.xml

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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd">


<!--    扫描  Service、Dao-->
    <context:component-scan base-package="com.neuedu.tx"></context:component-scan>

    <!-- 声明数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="root"/>
        <property name="password" value="root"/>
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/jdbctemp"/>
    </bean>


    <!--    声明JT-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>


    <!--    事务管理器 : 帮助程序  做 commit  rollaback 的操作-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

<!--    配置xxxx 隔离级别 传播特性  -->
    <!--配置事务的通知-->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!--配置不同的方法如何应用事务的传播特性-->
        <tx:attributes>
            <tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT" />
            <tx:method name="insert*" propagation="REQUIRED"/>
            <!--对于select、query事务并不是必须的,已经包含在事务中可以正常执行,如果没有事务也不用开启可以正常执行-->
            <tx:method name="select*" propagation="SUPPORTS"/>
            <tx:method name="query*" propagation="SUPPORTS"/>
        </tx:attributes>

    </tx:advice>




    <!--配置切面 应用事务的通知-->
    <aop:config>
        <!--切点 想在哪个方法上添加事务 -->
        <aop:pointcut id="serviceMethod" expression="execution(* com.neuedu.tx.service.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod"/>

    </aop:config>

</beans>

WARNING

使用 AccountServiceTest 在AccountService方法中 产生异常和不产生异常两种情况

  • 有异常会回滚,都不发生变化
  • 没有异常 两个账户都同时发生变更

6.6.4. 在切面中应用事务的通知(配置事务切面)

事务通知的方法标签属性

name*:匹配方法名*

  • propagation:事播特性

    • REQUIRED 必包含在一中,如果有正常行,如果有需要容器开启
    • REQUIRED_New 必须单独开启
  • no-rollback-for="java.lang.ArithmeticException" 指定特定的型 不会滚*(依然提交)*

  • rollback-for="" 指定特殊行回滚 RuntimeException

  • isolation 设置事的隔离级别 默DEFAULT 根据库决定*

  • read-only 指定在查询的方法上只用,不开启

xml
<!--配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="txManager">
    <!--配置不同的方法如何应用事务的传播特性-->
    <tx:attributes>
        <!--REQUIRED 必须包含在一个事务中,如果有则正常执行,如果没有需要容器帮助开启事务
            REQUIRED_New  必须单独再开启事务
        -->
        <tx:method name="transfer" propagation="REQUIRED"  />
        <tx:method name="insert*" propagation="REQUIRED"/>
        <tx:method name="*save"  propagation="REQUIRED"/>
        <!--对于select、query事务并不是必须的,已经包含在事务中可以正常执行,如果没有事务也不用开启可以正常执行-->
        <tx:method name="select*" propagation="SUPPORTS"/>
        <tx:method name="query*" propagation="SUPPORTS" />
    </tx:attributes>

</tx:advice>



<!--配置切面 应用事务的通知-->
<aop:config>
    <!--切点 想在哪个方法上添加事务 -->
    <aop:pointcut id="serviceMethod" expression="execution(* com.neuedu.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod"/>
</aop:config>

6.6.5. @Transcational注解的形式声明事务

java
/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.transaction.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;
import org.springframework.transaction.TransactionDefinition;

/**
 * Describes a transaction attribute on an individual method or on a class.
 *
 * <p>At the class level, this annotation applies as a default to all methods of
 * the declaring class and its subclasses. Note that it does not apply to ancestor
 * classes up the class hierarchy; methods need to be locally redeclared in order
 * to participate in a subclass-level annotation.
 *
 * <p>This annotation type is generally directly comparable to Spring's
 * {@link org.springframework.transaction.interceptor.RuleBasedTransactionAttribute}
 * class, and in fact {@link AnnotationTransactionAttributeSource} will directly
 * convert the data to the latter class, so that Spring's transaction support code
 * does not have to know about annotations. If no rules are relevant to the exception,
 * it will be treated like
 * {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute}
 * (rolling back on {@link RuntimeException} and {@link Error} but not on checked
 * exceptions).
 *
 * <p>For specific information about the semantics of this annotation's attributes,
 * consult the {@link org.springframework.transaction.TransactionDefinition} and
 * {@link org.springframework.transaction.interceptor.TransactionAttribute} javadocs.
 *
 * @author Colin Sampaleanu
 * @author Juergen Hoeller
 * @author Sam Brannen
 * @since 1.2
 * @see org.springframework.transaction.interceptor.TransactionAttribute
 * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute
 * @see org.springframework.transaction.interceptor.RuleBasedTransactionAttribute
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

	/**
	 * Alias for {@link #transactionManager}.
	 * @see #transactionManager
	 */
	@AliasFor("transactionManager")
	String value() default "";

	/**
	 * A <em>qualifier</em> value for the specified transaction.
	 * <p>May be used to determine the target transaction manager,
	 * matching the qualifier value (or the bean name) of a specific
	 * {@link org.springframework.transaction.PlatformTransactionManager}
	 * bean definition.
	 * @since 4.2
	 * @see #value
	 */
	@AliasFor("value")
	String transactionManager() default "";

	/**
	 * The transaction propagation type.
	 * <p>Defaults to {@link Propagation#REQUIRED}.
	 * @see org.springframework.transaction.interceptor.TransactionAttribute#getPropagationBehavior()
	 */
	Propagation propagation() default Propagation.REQUIRED;

	/**
	 * The transaction isolation level.
	 * <p>Defaults to {@link Isolation#DEFAULT}.
	 * <p>Exclusively designed for use with {@link Propagation#REQUIRED} or
	 * {@link Propagation#REQUIRES_NEW} since it only applies to newly started
	 * transactions. Consider switching the "validateExistingTransactions" flag to
	 * "true" on your transaction manager if you'd like isolation level declarations
	 * to get rejected when participating in an existing transaction with a different
	 * isolation level.
	 * @see org.springframework.transaction.interceptor.TransactionAttribute#getIsolationLevel()
	 * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setValidateExistingTransaction
	 */
	Isolation isolation() default Isolation.DEFAULT;

	/**
	 * The timeout for this transaction (in seconds).
	 * <p>Defaults to the default timeout of the underlying transaction system.
	 * <p>Exclusively designed for use with {@link Propagation#REQUIRED} or
	 * {@link Propagation#REQUIRES_NEW} since it only applies to newly started
	 * transactions.
	 * @see org.springframework.transaction.interceptor.TransactionAttribute#getTimeout()
	 */
	int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

	/**
	 * A boolean flag that can be set to {@code true} if the transaction is
	 * effectively read-only, allowing for corresponding optimizations at runtime.
	 * <p>Defaults to {@code false}.
	 * <p>This just serves as a hint for the actual transaction subsystem;
	 * it will <i>not necessarily</i> cause failure of write access attempts.
	 * A transaction manager which cannot interpret the read-only hint will
	 * <i>not</i> throw an exception when asked for a read-only transaction
	 * but rather silently ignore the hint.
	 * @see org.springframework.transaction.interceptor.TransactionAttribute#isReadOnly()
	 * @see org.springframework.transaction.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly()
	 */
	boolean readOnly() default false;

	/**
	 * Defines zero (0) or more exception {@link Class classes}, which must be
	 * subclasses of {@link Throwable}, indicating which exception types must cause
	 * a transaction rollback.
	 * <p>By default, a transaction will be rolling back on {@link RuntimeException}
	 * and {@link Error} but not on checked exceptions (business exceptions). See
	 * {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)}
	 * for a detailed explanation.
	 * <p>This is the preferred way to construct a rollback rule (in contrast to
	 * {@link #rollbackForClassName}), matching the exception class and its subclasses.
	 * <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)}.
	 * @see #rollbackForClassName
	 * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
	 */
	Class<? extends Throwable>[] rollbackFor() default {};

	/**
	 * Defines zero (0) or more exception names (for exceptions which must be a
	 * subclass of {@link Throwable}), indicating which exception types must cause
	 * a transaction rollback.
	 * <p>This can be a substring of a fully qualified class name, with no wildcard
	 * support at present. For example, a value of {@code "ServletException"} would
	 * match {@code javax.servlet.ServletException} and its subclasses.
	 * <p><b>NB:</b> Consider carefully how specific the pattern is and whether
	 * to include package information (which isn't mandatory). For example,
	 * {@code "Exception"} will match nearly anything and will probably hide other
	 * rules. {@code "java.lang.Exception"} would be correct if {@code "Exception"}
	 * were meant to define a rule for all checked exceptions. With more unusual
	 * {@link Exception} names such as {@code "BaseBusinessException"} there is no
	 * need to use a FQN.
	 * <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(String exceptionName)}.
	 * @see #rollbackFor
	 * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
	 */
	String[] rollbackForClassName() default {};

	/**
	 * Defines zero (0) or more exception {@link Class Classes}, which must be
	 * subclasses of {@link Throwable}, indicating which exception types must
	 * <b>not</b> cause a transaction rollback.
	 * <p>This is the preferred way to construct a rollback rule (in contrast
	 * to {@link #noRollbackForClassName}), matching the exception class and
	 * its subclasses.
	 * <p>Similar to {@link org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(Class clazz)}.
	 * @see #noRollbackForClassName
	 * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
	 */
	Class<? extends Throwable>[] noRollbackFor() default {};

	/**
	 * Defines zero (0) or more exception names (for exceptions which must be a
	 * subclass of {@link Throwable}) indicating which exception types must <b>not</b>
	 * cause a transaction rollback.
	 * <p>See the description of {@link #rollbackForClassName} for further
	 * information on how the specified names are treated.
	 * <p>Similar to {@link org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(String exceptionName)}.
	 * @see #noRollbackFor
	 * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
	 */
	String[] noRollbackForClassName() default {};

}

注解中的属性,参考xml中配置事务管理器的属性、和切面的配置

使用

使用Transcational注解替代 事务属性、切面的配置

  • 在类后者方法上使用@Transactional注解,注解中可以使用的属性

    • value、transactionManager : 事务管理器的对象(在IOC容器中声明的txManager)

    • propagation:事播特性

      • REQUIRED 必包含在一中,如果有正常行,如果有需要容器开启
      • REQUIRED_New 必须单独开启
    • no-rollback-for="java.lang.ArithmeticException" 指定特定的型 不会滚*(依然提交)*

    • rollback-for="" 指定特殊行回滚 RuntimeException

    • isolation 设置事的隔离级别 默DEFAULT 根据库决定*

    • read-only 指定在查询的方法上只用,不开启

  • 在容器中声明AccountService、数据源(Datasource)、事务管理器、注解驱动

    • <tx:annotation-driven transaction-manager="txManager"/>
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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd">


<!--    扫描  Service、Dao-->
    <context:component-scan base-package="com.neuedu.tx"></context:component-scan>

    <!-- 声明数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="root"/>
        <property name="password" value="root"/>
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/jdbctemp"/>
    </bean>


    <!--    声明JT-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>


    <!--    事务管理器 : 帮助程序  做 commit  rollaback 的操作-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

   <!-- 注解驱动,使用 Transactional 注解生效-->
    <tx:annotation-driven transaction-manager="txManager"/>



</beans>

6.6.6 使用配置类替换xml的配置

java
package com.neuedu.tx;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

/**
 * 项目:      spring-framework
 * 类名:       AppConfig
 * 创建时间:  2024/3/14 16:45
 * 描述 :
 * 作者 :     张金山
 * QQ :     314649444
 * Site:      https://jshand.gitee.io
 */
@Configuration
@ComponentScan
@EnableTransactionManagement
public class AppConfig {

    /**
     * IOC容器 声明数据源
     * @return
     */
    @Bean
    public DataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/jdbctemp");
//        ......

        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }


    /**
     * 声明事务管理器
     * @param dataSource
     * @return
     */
    @Bean
    TransactionManager txManager(DataSource dataSource){
        DataSourceTransactionManager txManager = new DataSourceTransactionManager(dataSource);

//        DataSourceTransactionManager txManager = new DataSourceTransactionManager();
//        txManager.setDataSource(dataSource);

        return txManager;
    }



}

Released under the MIT License.