【spring源码】十一、事务

3.声明式事务都是针对于 ServiceImpl 类下方法的.

4.事务管理器基于通知(advice)的.

5.在 spring 配置文件中配置声明式事务

<context:property-placeholder location="classpath:db.properties,classpath:second.properties"/>
<!-- 定义数据源bean,使用C3P0数据源实现 -->
<bean id="dataSource"class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName"
              value="${jdbc.driver}"></property>
    <property name="url"
              value="${jdbc.url}"></property>
    <property name="username"
              value="${jdbc.username}"></property>
    <property name="password"
              value="${jdbc.password}"></property>
</bean>
<!-- spring-jdbc.jar 中 -->
<!-- 使用PlatformTransactionManager接口的实现类DataSourceTransactionManager -->
<bean id="txManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource"
              ref="dataSource"></property>
</bean>
<!-- 配置声明式事务 -->
<tx:advice id="txAdvice"
           transaction-manager="txManager">
    <tx:attributes>
        <!-- 哪些方法需要有事务控制 -->
        <!-- 方法以 ins 开头事务管理 -->
        <tx:method name="ins*" />
        <tx:method name="del*" />
        <tx:method name="upd*" />
        <tx:method name="*" />
    </tx:attributes>
</tx:advice>
<aop:config>
    <!-- 切点范围设置大一些 -->
    <aop:pointcut expression="execution(*com.bjsxt.service.impl.*.*(..))"
                  id="mypoint" />
    <aop:advisor advice-ref="txAdvice"
                 pointcut-ref="mypoint" />
</aop:config>

六.声明式事务中属性解释

\1. name=”” 哪些方法需要有事务控制
1.1 支持*通配符
\2. readonly=”boolean” 是否是只读事务.
2.1 如果为 true,告诉数据库此事务为只读事务.数据化优化,会对性能有一定提升,所以只要是查询的方法,建议使用此数据.
2.2 如果为 false(默认值),事务需要提交的事务.建议新增,删除,修改.
\3. propagation 控制事务传播行为.
3.1 当一个具有事务控制的方法被另一个有事务控制的方法调用后,需要如何管理事务(新建事务?在事务中执行?把事务挂起?报异常?)
3.2 REQUIRED (默认值): 如果当前有事务,就在事务中执行,如果当前没有事务,新建一个事务.
3.3 SUPPORTS:如果当前有事务就在事务中执行,如果当前没有事务,就在非事务状态下执行.
3.4 MANDATORY:必须在事务内部执行,如果当前有事务,就在事务中执行,如果没有事务,报错.
3.5 REQUIRES_NEW:必须在事务中执行,如果当前没有事务,新建事务,如果当前有事务,把当前事务挂起.
3.6 NOT_SUPPORTED:必须在非事务下执行,如果当前没有事务,正常执行,如果当前有事务,把当前事务挂起.
3.7 NEVER:必须在非事务状态下执行,如果当前没有事务,正常执行,如果当前有事务,报错.
3.8 NESTED:必须在事务状态下执行.如果没有事务,新建事务,如果当前有事务,创建一个嵌套事务.
\4. isolation=”” 事务隔离级别
4.1 在多线程或并发访问下如何保证访问到的数据具有完整性的.
4.2 脏读:
4.2.1 一个事务(A)读取到另一个事务(B)中未提交的数据,另一个事务中数据可能进行了改变,此时 A事务读取的数据可能和数据库中数据是不一致的,此时认为数据是脏数据,读取脏数据过程叫做脏读.
4.3 不可重复读:
4.3.1 主要针对的是某行数据.(或行中某列)
4.3.2 主要针对的操作是修改操作.
4.3.3 两次读取在同一个事务内
4.3.4 当事务 A 第一次读取事务后,事务 B 对事务 A 读取的淑君进行修改,事务 A 中再次读取的数据和之前读取的数据不一致,过程不可重复读.
4.4 幻读:
4.4.1 主要针对的操作是新增或删除
4.4.2 两次事务的结果.
4.4.3 事务 A 按照特定条件查询出结果,事务 B 新增了一条符合条件的数据.事务 A 中查询的数据和数据库中的数据不一致的,事务 A 好像出现了幻觉,这种情况称为幻读.
4.5 DEFAULT: 默认值,由底层数据库自动判断应该使用什么隔离界别
4.6 READ_UNCOMMITTED: 可以读取未提交数据,可能出现脏读,不重复读,幻读.
4.6.1 效率最高.
4.7 READ_COMMITTED:只能读取其他事务已提交数据.可以防止脏读,可能出现不可重复读和幻读.
4.8 REPEATABLE_READ: 读取的数据被添加锁,防止其他事务修改此数据,可以防止不可重复读.脏读,可能出现幻读.
4.9 SERIALIZABLE: 排队操作,对整个表添加锁.一个事务在操作数据时,另一个事务等待事务操作完成后才能操作这个表.
4.9.1 最安全的
4.9.2 效率最低的.
\5. rollback-for=”异常类型全限定路径”
5.1 当出现什么异常时需要进行回滚
5.2 建议:给定该属性值.
5.2.1 手动抛异常一定要给该属性值.
\6. no-rollback-for=””
6.1 当出现什么异常时不滚回事务.

<?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:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.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"
	default-autowire="byName">
    <context:property-placeholder location="classpath:db.properties,classpath:second.properties"/>
    <context:component-scan base-package="com.bjsxt.service.impl"></context:component-scan>
    <!-- 数据源 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    	<property name="driverClassName" value="${jdbc.driver}"></property>
    	<property name="url" value="${jdbc.url}"></property>
    	<property name="username" value="${jdbc.username}"></property>
    	<property name="password" value="${jdbc.password}"></property>
    </bean>
    <!-- SqlSessinFactory对象 -->
    <bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
    	<property name="typeAliasesPackage" value="com.bjsxt.pojo"></property>
    </bean>
    <!-- 扫描器 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    	<property name="basePackage" value="com.bjsxt.mapper"></property>
    	<property name="sqlSessionFactoryBeanName" value="factory"></property>
    </bean>
    
    <!-- 注入 -->
   <!--  <bean id="usersService" class="com.bjsxt.service.impl.UsersServiceImpl">
    	<property name=""></property>
    </bean> -->
    <bean id="txManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	<!-- 配置声明式事务 -->
	<tx:advice id="txAdvice" transaction-manager="txManager">
		<tx:attributes>
			<!-- 哪些方法需要有事务控制 -->
			<!-- 方法以ins开头事务管理 -->
			<tx:method name="ins*" />
			<tx:method name="del*"/>
			<tx:method name="upd*"/>
			<tx:method name="*" read-only="true"/>
		</tx:attributes>
	</tx:advice>
	<aop:config>
		<!-- 切点范围设置大一些 -->
		<aop:pointcut expression="execution(* com.bjsxt.service.impl.*.*(..))"
			id="mypoint" />
		<aop:advisor advice-ref="txAdvice" pointcut-ref="mypoint" />
	</aop:config>
    <!-- aop -->
    <!-- <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
    <bean id="mybefore" class="com.bjsxt.advice.MyBefore"></bean>
    <bean id="myafter" class="com.bjsxt.advice.MyAfter"></bean>
    <aop:config>
    	<aop:pointcut expression="execution(* com.bjsxt.service.impl.UsersServiceImpl.login(..))" id="mypoint"/>
    	<aop:advisor advice-ref="mybefore" pointcut-ref="mypoint"/>
    	<aop:advisor advice-ref="myafter" pointcut-ref="mypoint"/>
    </aop:config> -->
</beans>

Spring 事务管理分为编程式和声明式的两种方式。编程式事务指的是通过编码方式实现事务;声明式事务基于 AOP,将具体业务逻辑与事务处理解耦。声明式事务管理使业务代码逻辑不受污染, 因此在实际使用中声明式事务用的比较多。

img

声明式事务有两种方式,一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于 @Transactional 注解的方式。

需要明确几点:

1、默认配置下 Spring 只会回滚运行时、未检查异常(继承自 RuntimeException 的异常)或者 Error。
  2、@Transactional 注解只能应用到 public 方法才有效。

3、@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。然而仅仅 @Transactional 注解的出现不足以开启事务行为,它仅仅是一种元数据,能够被可以识别 @Transactional 注解和上述的配置适当的具有事务行为的beans所使用。其实是 <tx:annotation-driven/>元素的出现开启了事务行为。

4、注解不可以继承,建议在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。当然可以在接口上使用 @Transactional 注解,但是这将只有当你设置了基于接口的代理时它才生效。

Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSourceTransactionManager代理机制这三部分,无论哪种配置方式,一般变化的只是代理机制这部分。

DataSource、TransactionManager这两部分只是会根据数据访问方式有所变化,比如使用Hibernate进行数据访问时,DataSource实际为SessionFactory,TransactionManager的实现为HibernateTransactionManager。

img

事务的TransactionManager事务管理器总共有5种,与DataSource关联关系如图所示:

img

根据代理机制的不同,总结了五种Spring事务的配置方式,配置文件如下:

https://www.cnblogs.com/jing99/p/11495252.html

1 情景前要

0 情景:张三给李四转账100

-- 查看全局和当前session的事务隔离级别
SELECT @@GLOBAL.tx_isolation, @@tx_isolation;

-- 设置隔离级别:SET SESSION TRANSACTION ISOLATION LEVEL
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
-- level: READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ,  SERIALIZABLE

-- session 1:A开始事务后进行转账。先把自己账户-100,然后给对方账户+100
START TRANSACTION;
UPDATE t_user SET amount = amount - 100 WHERE username = 'A';
UPDATE t_user SET amount = amount + 100 WHERE username = 'B';
COMMIT;
-- ROLLBACK;



-- session 2:B开始事务后进行查询两次。查询全部与查自己
START TRANSACTION;
SELECT * FROM t_user;
SELECT * FROM t_user where username="B"
COMMIT;

1.1 隔离级别情景

1.1.1 隔离级别情景:可重复读

mysql默认的可重复读:当session 2只进行了select,而未commit时,session1即使commit了,session2查到的内容还是原来的。

1.1.2 隔离级别情景:脏读

A开始事务后只进行了-操作,还没进行+操作。但B查的时候没收到100元,但A的账户已经少了100。READ UNCOMMITED

1.1.3 隔离级别情景:SERIALIZABLE

所有的事务必须是排队执行,A转完提交后B才可以查。

private static final Logger LOG= LoggerFactory.getLogger(LocalJDBCTransApplication.class);
LOG.error(e.getLocalizedMessage());

jdbc代码测试隔离机制

jdbc常见的7步步骤:

package com.iot.mybatis.jdbc;

//import java.sql.*;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * 可以在每个方法内都进行一整套过程。可想而知很麻烦
 */
public class JdbcTest {
    public static void main(String[] args) {
        //数据库连接
        Connection connection = null;
        //预编译的Statement,使用预编译的Statement提高数据库性能
        PreparedStatement preparedStatement = null;
        //结果集
        ResultSet resultSet = null;

        try {
            //加载数据库驱动
            Class.forName("com.mysql.jdbc.Driver");

            //通过驱动管理类获取数据库链接
            connection =  DriverManager.getConnection("jdbc:mysql://120.25.162.238:3306/mybatis001?characterEncoding=utf-8", "root", "123");
            //定义sql语句 ?表示占位符
            String sql = "select * from user where username = ?";
            //获取预处理statement
            preparedStatement = connection.prepareStatement(sql);
            //设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
            preparedStatement.setString(1, "王五");
            //向数据库发出sql执行查询,查询出结果集
            resultSet =  preparedStatement.executeQuery();
            //遍历查询结果集
            while(resultSet.next()){
                System.out.println(resultSet.getString("id")+"  "+resultSet.getString("username"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            //释放资源
            if(resultSet!=null){//释放结果集
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if(preparedStatement!=null){//释放stmt
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if(connection!=null){//释放链接
                try {
                    connection.close();
                } catch (SQLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
}

现在我们模拟出两个java代码

需要用到的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">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.mav.example</groupId>
	<artifactId>local-tran-jdbc</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>local-tran-jdbc</name>
	<description>Example project for JDBC transaction management.</description>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>

		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.25</version>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>1.1.11</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.39</version>
		</dependency>

	</dependencies>

</project>

第一个java

jdbc的隔离

package com.imooc.example.localtranjdbc;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class LocalTranJdbcApplication {

    private static final Logger LOG = LoggerFactory.getLogger(LocalTranJdbcApplication.class);//用日志去打印,而不是用system.out.print

    public static void main(String[] args) throws SQLException {


        String plusAmountSQL = "UPDATE T_USER SET amount = amount + 100 WHERE username = ?";
        String minusAmountSQL = "UPDATE T_USER SET amount = amount - 100 WHERE username = ?";

        Connection dbConnection = getDBConnection();
        LOG.debug("Begin");
        dbConnection.setAutoCommit(false);//关闭自动提交//相当于开启一个事务

        // 设置statement
        PreparedStatement plusAmountPS = dbConnection.prepareStatement(plusAmountSQL);
        plusAmountPS.setString(1, "SuperMan");//SuperMan加钱。BatMan减钱
        // 执行
        plusAmountPS.executeUpdate();

        //simulateError();//模拟出错的情况//让这个程序只执行一半

        PreparedStatement minusAmountPS = dbConnection.prepareStatement(minusAmountSQL);
        minusAmountPS.setString(1, "BatMan");
        minusAmountPS.executeUpdate();

        // 提交
        dbConnection.commit();
        LOG.debug("Done!");

        plusAmountPS.close();
        minusAmountPS.close();
        dbConnection.close();//自动rollback
    }

    private static void simulateError() throws SQLException {
        throw new SQLException("Simulate some error!");
    }

    private static Connection getDBConnection() throws SQLException {
        String DB_DRIVER = "com.mysql.jdbc.Driver";
        String DB_CONNECTION = "jdbc:mysql://localhost:3306/dist_tran_course";
        String DB_USER = "mt";
        String DB_PASSWORD = "111111";
        try {
            Class.forName(DB_DRIVER);
        } catch (ClassNotFoundException e) {
            LOG.error(e.getMessage());
        }
        return DriverManager.getConnection(DB_CONNECTION, DB_USER, DB_PASSWORD);
    }
}

接下来需要使用debug

第二个java
package com.imooc.example.localtranjdbc;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.*;

public class LocalTranJdbcApplication2 {

    private static final Logger LOG = LoggerFactory.getLogger(LocalTranJdbcApplication2.class);

    public static void main(String[] args) throws SQLException {

        String sql = "SELECT * FROM T_USER FOR UPDATE";
        String plusAmountSQL = "UPDATE T_USER SET amount = ? WHERE username = ?";

        Connection dbConnection = getDBConnection();
        LOG.debug("Begin session2");

        PreparedStatement queryPS = dbConnection.prepareStatement(sql);
        ResultSet rs = queryPS.executeQuery();
        Long superManAmount = 0L;
        while (rs.next()) {
            String name = rs.getString(2);
            Long amount = rs.getLong(3);
            LOG.info("{} has amount:{}", name, amount);
            if (name.equals("SuperMan")) {
                superManAmount = amount;
            }
        }

        PreparedStatement updatePS = dbConnection.prepareStatement(plusAmountSQL);
        updatePS.setLong(1, superManAmount + 100);
        updatePS.setString(2, "SuperMan");
        updatePS.executeUpdate();

        LOG.debug("Done session2!");
        queryPS.close();
        updatePS.close();
        dbConnection.close();
    }

    private static Connection getDBConnection() throws SQLException {
        String DB_DRIVER = "com.mysql.jdbc.Driver";
        String DB_CONNECTION = "jdbc:mysql://localhost:3306/dist_tran_course";
        String DB_USER = "mt";
        String DB_PASSWORD = "111111";
        try {
            Class.forName(DB_DRIVER);
        } catch (ClassNotFoundException e) {
            LOG.error(e.getMessage());
        }
        return DriverManager.getConnection(DB_CONNECTION, DB_USER, DB_PASSWORD);
    }
}

依赖

  • jdbc(或orm)
  • aspectJ
  • spring-tx

事务的几种配置方式

https://www.cnblogs.com/myseries/p/10834172.html

(1)基于 TransactionProxyFactoryBean的声明式事务管理

  • 配置事务管理器bean,事务管理器管理DataSource
  • 配置TransactionProxyFactoryBean
    • 指定事务管理器
    • 指定target,即方法
    • 指定transactionAttributes,即事务的属性
<context:property-placeholder location="classpath:jdbc.properties"/>

<!-- 注册数据源 C3P0 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"  >
    <property name="driverClass" value="${jdbc.driverClass}"></property>
    <property name="jdbcUrl"  value="${jdbc.url}"></property>
    <property name="user"  value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
</bean>

<bean id="accountDao" class="transaction.test2.dao.AccountDaoImpl">
    <property name="dataSource" ref="dataSource"/>
</bean>

<bean id="stockDao" class="transaction.test2.dao.StockDaoImpl">
    <property name="dataSource" ref="dataSource"/>
</bean>

<bean id="buyStockService" class="transaction.test2.service.BuyStockServiceImpl">
    <property name="accountDao" ref="accountDao"></property>
    <property name="stockDao" ref="stockDao"></property>
</bean>


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

<!-- 事务代理工厂 -->
<!-- 生成事务代理对象 -->
<bean id="serviceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager" ref="myTracnsactionManager"></property>
    <property name="target" ref="buyStockService"></property>
    <property name="transactionAttributes">
        <props>
            <!-- 主要 key 是方法   
                        ISOLATION_DEFAULT  事务的隔离级别
                        PROPAGATION_REQUIRED  传播行为
                    -->
            <prop key="add*">ISOLATION_DEFAULT,PROPAGATION_REQUIRED</prop>
            <!-- -Exception 表示发生指定异常回滚,+Exception 表示发生指定异常提交 -->
            <prop key="buyStock">ISOLATION_DEFAULT,PROPAGATION_REQUIRED,-BuyStockException</prop>
        </props>
    </property>

</bean>

(2)基于 @Transactional 的声明式事务管理

  • 配置事务管理器bean,事务管理器管理DataSource
  • 将事务管理器配置到事务驱动中<tx:annotation-driven>
<context:property-placeholder location="classpath:jdbc.properties"/>

<!-- 注册数据源 C3P0 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"  >
    <property name="driverClass" value="${jdbc.driverClass}"></property>
    <property name="jdbcUrl"  value="${jdbc.url}"></property>
    <property name="user"  value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
</bean>

<bean id="accountDao" class="transaction.test3.dao.AccountDaoImpl">
    <property name="dataSource" ref="dataSource"/>
</bean>

<bean id="stockDao" class="transaction.test3.dao.StockDaoImpl">
    <property name="dataSource" ref="dataSource"/>
</bean>

<bean id="buyStockService" class="transaction.test3.service.BuyStockServiceImpl">
    <property name="accountDao" ref="accountDao"></property>
    <property name="stockDao" ref="stockDao"></property>
</bean>


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

<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="myTracnsactionManager"/>

(3)基于Aspectj AOP配置事务

  • 配置事务管理器bean,事务管理器管理DataSource
  • 配置<aop:config>
    • 指定切点<aop:pointcut>
    • 指定<aop:advisor>,他可以ref bean </tx:advice>
    • </tx:advice>bean来指定方法+事务属性
<context:property-placeholder location="classpath:jdbc.properties"/>

<!-- 注册数据源 C3P0 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"  >
    <property name="driverClass" value="${jdbc.driverClass}"></property>
    <property name="jdbcUrl"  value="${jdbc.url}"></property>
    <property name="user"  value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
</bean>

<bean id="accountDao" class="transaction.test4.dao.AccountDaoImpl">
    <property name="dataSource" ref="dataSource"/>
</bean>

<bean id="stockDao" class="transaction.test4.dao.StockDaoImpl">
    <property name="dataSource" ref="dataSource"/>
</bean>

<bean id="buyStockService" class="transaction.test4.service.BuyStockServiceImpl">
    <property name="accountDao" ref="accountDao"></property>
    <property name="stockDao" ref="stockDao"></property>
</bean>


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

<tx:advice id="txAdvice" transaction-manager="myTracnsactionManager">
    <tx:attributes>
        <!-- 为连接点指定事务属性 -->
        <tx:method name="add*" isolation="DEFAULT" propagation="REQUIRED"/>
        <tx:method name="buyStock" isolation="DEFAULT" propagation="REQUIRED" rollback-for="BuyStockException"/>
    </tx:attributes>
</tx:advice>

<aop:config>
    <!-- 切入点配置 -->
    <aop:pointcut expression="execution(* *..service.*.*(..))" id="point"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="point"/>
</aop:config>

慕课

事务管理抽象
  • PlatformTransactionManager
  • TransactionDefinition
  • TransactionStatus
Spring事务抽象–事务管理器
public interface PlatformTransactionManager{
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransationException;
    
    void commit(TransactionStatus status) throws TransationException;
    void rollback(TransactionStatus status) throws TransationException;
}
Spring事务抽象–事务定义
public interface TransactionDefinition{
    int getPropagationBehavior();//传播属性
    int getIsolationLevel();//隔离级别
    String getName();//事务名字
    int getTimeout();//超时时间
    boolean isReadOnly();
    
}
Spring事务抽象–事务隔离机制
TransactionDefinition.ISOLATION_DEFAULT ;//数据库用的隔离机制是什么,我用的隔离机制就是什么
TransactionDefinition.ISOLATION_READ_COMMITTED;//一个事务可以读取另外一个事务已经提交的数据
TransactionDefinition.ISOLATION_READ_UNCOMMITTED;//一个事务可以读取另外一个事务还没提交的数据(脏读)
TransactionDefinition.ISOLATION_REPEATABLE_READ;//可重复读//读期间不管别人改的数据
TransactionDefinition.ISOLATION_SERIALIZABLE;//排队,防止并发//效率有影响
Spring事务抽象–事务传播机制:

两个方法在各自的事务中,互相调用,调用时候事务是怎么传播的

TransactionDefinition.PROPAGATION_REQUIRED;//(Default)//A调B,A在事务中,B
TransactionDefinition.PROPAGATION_SUPPORT;
TransactionDefinition.PROPAGATION_MANDATORY;
TransactionDefinition.PROPAGATION_REQUIRES_NEW;
TransactionDefinition.PROPAGATION_NOT_SUPPORTED;
TransactionDefinition.PROPAGATION_NEVER;
TransactionDefinition.PROPAGATION_NESTED;
Spring事务抽象–事务管理器
public interface TransationStatus extends SavepointManager{
    boolean isNewTransaction();
    boolean hasSavapoint();
    void setRollbackOnly();
    boolean isRollbackOnly();
    boolean isCompleted();
}

第8章 声明式事务管理

8.1 事务概述

是以一种可靠、一致的方式,访问和操作数据库中数据的程序单元。

  1. 在JavaEE企业级开发的应用领域,为了保证数据的完整性一致性,必须引入数据库事务的概念,所以事务管理是企业级应用程序开发中必不可少的技术。

  2. 事务就是一组由于逻辑上紧密关联而合并成一个整体(工作单元)的多个数据库操作,这些操作要么都执行要么都不执行

  3. 事务的四个关键属性(ACID)

​ ①原子性(atomicity):“原子”的本意是“不可再分”,事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可。事务的原子性要求事务中的所有操作要么都执行,要么都不执行。

​ ②一致性(consistency):“一致”指的是数据的一致,具体是指:所有数据都处于满足业务规则的一致性状态。一致性原则要求:一个事务中不管涉及到多少个操作,都必须保证事务执行之前数据是正确的,事务执行之后数据仍然是正确的。如果一个事务在执行的过程中,其中某一个或某几个操作失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前的状态,这就是回滚

​ ③隔离性(isolation):在应用程序实际运行过程中,事务往往是并发执行的,所以很有可能有许多事务同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。隔离性原则要求多个事务在并发执行过程中不会互相干扰

​ ④持久性(durability):持久性原则要求事务执行完成后,对数据的修改永久的保存下来,不会因各种系统错误或其他意外情况而受到影响。通常情况下,事务对数据的修改应该被写入到持久化存储器中。

例子:张三给李四转账,李四在查余额。

张三的内容包括:给自己-100,给李四+100。原子性指的是不会出现给自己-100而李四没有+100的情况。一致性是指总的余额不会变化。

隔离性是指张三在转,李四在查。张三在转一般过程中李四查余额李四能否看到转账的结果

持久性:只有张三提交了,才回永久保存到数据库中。

MYSQL:

BEGIN TRANSACTION:
UPDATE t_user SET amount=amount-100 where username='zhangsan';
UPDATE t_user SET amount=amount+100 where username='lisi';
COMMIT
或--ROLLBACK

8.2 Spring事务管理

事务:分为编程式事务、声明式事务

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7eJsBbdC-1608402277177)(https://raw.githubusercontent.com/FermHan/tuchuang/master/20191202003222.png)]

在java中不可能用sql去进行事务管理,得用高等的API去进行管理。业务系统一般都有一个ResourceManager(TransactionManager),去进行事务管理。ResourceManager会管理Resource,Resource就是数据库的资源。ResourceManager会使用代理等方式去调用某种事务管理的实现类去进行事务管理,而Resource的具体实现类可能是DB Connection

java JDBC

Connection conn = getConnection();
conn.setAutoCommit(false);//关闭自动提交

PreparedStatement stnt1 = conn.prepareStatement(updateUser1SQL);
stmt1.executeUpdate();
PreparedStatement stnt2 = conn.prepareStatement(updateUser2SQL);
stmt2.executeUpdate();

conn.commit();// 提交或者回滚
//conn.rollback();

8.2.1编程式事务管理

  1. 使用原生的JDBC API进行事务管理:由程序员编程事务控制代码.

​ ①获取数据库连接Connection对象

​ ②取消事务的自动提交

​ ③执行操作

​ ④正常完成操作时手动提交事务

​ ⑤执行失败时回滚事务

​ ⑥关闭相关资源

  1. 评价

​ 使用原生的JDBC API实现事务管理是所有事务管理方式的基石,同时也是最典型的编程式事务管理。编程式事务管理需要将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在使用编程的方式管理事务时,必须在每个事务操作中包含额外的事务 管理代码。相对于核心业务而言,事务管理的代码显然属于非核心业务,如果多个模块都使用同样模式的代码进行事务管理,显然会造成较大程度的代码冗余

8.2.2 声明式事务管理

声明式事务:事务控制代码已经由 spring 写好,程序员只需要声明出哪些方法需要进行事务控制和如何进行事务控制。功能,业务,事务。声明式事务是针对于ServiceImpl类下方法的,一个业务对应一个事务。事务管理器是基于通知的

​ 大多数情况下声明式事务比编程式事务管理更好:它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。

​ 事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理。

​ Spring在不同的事务管理API之上定义了一个抽象层,通过配置的方式使其生效,从而让应用程序开发人员不必了解事务管理API的底层实现细节,就可以使用Spring的事务管理机制。

​ Spring既支持编程式事务管理,也支持声明式的事务管理。

public class UserServiceImpl implements UserService{
    @Override
    public int insert(Users users){// 把这个方法配置为切点。
        return 0;
    }
}

在applicationContext.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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.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"
       default-autowire="byName">
    <context:property-placeholder location="classpath:db.properties,classpath:second.properties"/>
    <context:component-scan base-package="com.bjsxt.service.impl"></context:component-scan>
    <!-- 数据源 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
    <!-- SqlSessinFactory对象 -->
    <bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="typeAliasesPackage" value="com.bjsxt.pojo"></property>
    </bean>
    <!-- 扫描器 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.bjsxt.mapper"></property>
        <property name="sqlSessionFactoryBeanName" value="factory"></property>
    </bean>

    <!-- 注入 -->
    <!--  <bean id="usersService" class="com.bjsxt.service.impl.UsersServiceImpl">
     <property name=""></property>
    </bean> -->
    <bean id="txManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>


    <!-- 配置事务管理器 -->
    <bean id="transactionManager" 
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/> 
    </bean>
    <tx:advice id="txAdvice" transactiontransaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="insert"/>下面的切点写大点,这里控制到底哪里真正有事务
        </tx:attributes>
    </tx:advice>

    <!-- 启用事务注解 ,如果上面的是id不是transactionManager,则要一致;如果是transactionManager,tx:annotation-driven在tx:annotation-driven中可以省略-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <aop:config>
        <aop:pointcut expression="execution(* com.bjsxtt.service.impl.*.*(..))" id="mypoint"/>
        <aop:advisor advice-ref="txManager" pointcut-ref="mypoint"/>
    </aop:config>
    

maven依赖

spring-jdbc

8.2.3 Spring提供的事务管理器

​ Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。事务管理器是基于AOP通知的

​ Spring的核心事务管理抽象是PlatformTransactionManager。它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。

​ 事务管理器可以以普通的bean的形式声明在Spring IOC容器中。

  • 提供统一的API接口支持不同的资源,在spring.tx库中
  • 提供声明式事务管理。。使用AOP,而不是嵌入到业务代码中
  • 方便的与spring框架继承
  • 多个资源的事务管理、同步

8.2.4事务管理器的主要实现

接口:PlatformTransactionManager

  1. DataSourceTransactionManager:在应用程序中只需要处理一个数据源,而且通过JDBC存取。

  2. JtaTransactionManager:在JavaEE应用服务器上用JTA(Java Transaction API)进行事务管理

  3. HibernateTransactionManager:用Hibernate框架存取数据库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p8L5aZL2-1608402277179)(https://raw.githubusercontent.com/FermHan/tuchuang/master/20191114153555.png)]

Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。

Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。此接口的内容如下:

Public interface PlatformTransactionManager()...{  
    // 由TransactionDefinition得到TransactionStatus对象
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; 
    // 提交
    Void commit(TransactionStatus status) throws TransactionException;  
    // 回滚
    Void rollback(TransactionStatus status) throws TransactionException;  
} 

从这里可知具体的具体的事务管理机制对Spring来说是透明的,它并不关心那些,那些是对应各个平台需要关心的,所以Spring事务管理的一个优点就是为不同的事务API提供一致的编程模型,如JTA、JDBC、Hibernate、JPA。下面分别介绍各个平台框架实现事务管理的机制。

2.1.1 JDBC事务

如果应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会为你处理事务边界。为了使用DataSourceTransactionManager,你需要使用如下的XML将其装配到应用程序的上下文定义中:

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

实际上,DataSourceTransactionManager是通过调用java.sql.Connection来管理事务,而后者是通过DataSource获取到的。通过调用连接的commit()方法来提交事务,同样,事务失败则通过调用rollback()方法进行回滚。

2.1.2 Hibernate事务

如果应用程序的持久化是通过Hibernate实习的,那么你需要使用HibernateTransactionManager。对于Hibernate3,需要在Spring上下文定义中添加如下的``声明:

    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

sessionFactory属性需要装配一个Hibernate的session工厂,HibernateTransactionManager的实现细节是它将事务管理的职责委托给org.hibernate.Transaction对象,而后者是从Hibernate Session中获取到的。当事务成功完成时,HibernateTransactionManager将会调用Transaction对象的commit()方法,反之,将会调用rollback()方法。

2.1.3 Java持久化API事务(JPA)

Hibernate多年来一直是事实上的Java持久化标准,但是现在Java持久化API作为真正的Java持久化标准进入大家的视野。如果你计划使用JPA的话,那你需要使用Spring的JpaTransactionManager来处理事务。你需要在Spring中这样配置JpaTransactionManager:

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

JpaTransactionManager只需要装配一个JPA实体管理工厂(javax.persistence.EntityManagerFactory接口的任意实现)。JpaTransactionManager将与由工厂所产生的JPA EntityManager合作来构建事务。

2.1.4 Java原生API事务

如果你没有使用以上所述的事务管理,或者是跨越了多个事务管理源(比如两个或者是多个不同的数据源),你就需要使用JtaTransactionManager:

    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManagerName" value="java:/TransactionManager" />
    </bean>

JtaTransactionManager将事务管理的责任委托给javax.transaction.UserTransaction和javax.transaction.TransactionManager对象,其中事务成功完成通过UserTransaction.commit()方法提交,事务失败通过UserTransaction.rollback()方法回滚。

2.2 基本事务属性的定义TransactionDefinition

上面讲到的事务管理器接口PlatformTransactionManager通过getTransaction(TransactionDefinition definition)方法来得到事务,这个方法里面的参数是TransactionDefinition类,这个类就定义了一些基本的事务属性。

那么什么是事务属性呢?事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。事务属性包含了5个方面,如图所示:

这里写图片描述

而TransactionDefinition接口内容如下:

public interface TransactionDefinition {
    
    
    int getPropagationBehavior(); // 返回事务的传播行为
    int getIsolationLevel(); // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
    int getTimeout();  // 返回事务必须在多少秒内完成
    boolean isReadOnly(); // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的
} 

我们可以发现TransactionDefinition正好用来定义事务属性,下面详细介绍一下各个事务属性。

2.2.1 传播行为

事务的第一个方面是传播行为(propagation behavior)。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。Spring定义了七种传播行为:

传播行为 含义
PROPAGATION_REQUIRED 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务
PROPAGATION_SUPPORTS 表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行
PROPAGATION_MANDATORY 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常
PROPAGATION_REQUIRED_NEW 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果没有当前事务,则新建事务。如果使用JTATransactionManager的话,则需要访问TransactionManager
PROPAGATION_NOT_SUPPORTED 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager
PROPAGATION_NEVER 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常
PROPAGATION_NESTED 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务

注:以下具体讲解传播行为的内容参考自Spring事务机制详解
(1)PROPAGATION_REQUIRED 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。

//事务属性 PROPAGATION_REQUIRED
methodA{
    
    
    ……
    methodB();
    ……
}
//事务属性 PROPAGATION_REQUIRED
methodB{
    
    
   ……
}

使用spring声明式事务,spring使用AOP来支持声明式事务,会根据事务属性,自动在方法调用之前决定是否开启一个事务,并在方法执行之后决定事务提交或回滚事务。

单独调用methodB方法:

main{
    
     
    methodB(); 
}  

相当于

Main{
    
     
    Connection con=null; 
    try{
    
     
        con = getConnection(); 
        con.setAutoCommit(false); 

        //方法调用
        methodB(); 

        //提交事务
        con.commit(); 
    } Catch(RuntimeException ex) {
    
     
        //回滚事务
        con.rollback();   
    } finally {
    
     
        //释放资源
        closeCon(); 
    } 
} 

Spring保证在methodB方法中所有的调用都获得到一个相同的连接。在调用methodB时,没有一个存在的事务,所以获得一个新的连接,开启了一个新的事务。

单独调用MethodA时,在MethodA内又会调用MethodB.

执行效果相当于:

main{
    
     
    Connection con = null; 
    try{
    
     
        con = getConnection(); 
        methodA(); 
        con.commit(); 
    } catch(RuntimeException ex) {
    
     
        con.rollback(); 
    } finally {
    
        
        closeCon(); 
    }
} 

调用MethodA时,环境中没有事务,所以开启一个新的事务。当在MethodA中调用MethodB时,环境中已经有了一个事务,所以methodB就加入当前事务。

(2)PROPAGATION_SUPPORTS 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。

//事务属性 PROPAGATION_REQUIRED
methodA(){
    
    
  methodB();
}

//事务属性 PROPAGATION_SUPPORTS
methodB(){
    
    
  ……
}

单纯的调用methodB时,methodB方法是非事务的执行的。当调用methdA时,methodB则加入了methodA的事务中,事务地执行。

(3)PROPAGATION_MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。

//事务属性 PROPAGATION_REQUIRED
methodA(){
    
    
    methodB();
}

//事务属性 PROPAGATION_MANDATORY
    methodB(){
    
    
    ……
}

当单独调用methodB时,因为当前没有一个活动的事务,则会抛出异常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);当调用methodA时,methodB则加入到methodA的事务中,事务地执行。

(4)PROPAGATION_REQUIRES_NEW 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。

//事务属性 PROPAGATION_REQUIRED
methodA(){
    
    
    doSomeThingA();
    methodB();
    doSomeThingB();
}

//事务属性 PROPAGATION_REQUIRES_NEW
methodB(){
    
    
    ……
}

调用A方法:

main(){
    
    
    methodA();
}

相当于

main(){
    
    
    TransactionManager tm = null;
    try{
    
    
        //获得一个JTA事务管理器
        tm = getTransactionManager();
        tm.begin();//开启一个新的事务
        Transaction ts1 = tm.getTransaction();
        doSomeThing();
        tm.suspend();//挂起当前事务
        try{
    
    
            tm.begin();//重新开启第二个事务
            Transaction ts2 = tm.getTransaction();
            methodB();
            ts2.commit();//提交第二个事务
        } Catch(RunTimeException ex) {
    
    
            ts2.rollback();//回滚第二个事务
        } finally {
    
    
            //释放资源
        }
        //methodB执行完后,恢复第一个事务
        tm.resume(ts1);
        doSomeThingB();
        ts1.commit();//提交第一个事务
    } catch(RunTimeException ex) {
    
    
        ts1.rollback();//回滚第一个事务
    } finally {
    
    
        //释放资源
    }
}

在这里,我把ts1称为外层事务,ts2称为内层事务。从上面的代码可以看出,ts2与ts1是两个独立的事务,互不相干。Ts2是否成功并不依赖于 ts1。如果methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所做的结果依然被提交。而除了 methodB之外的其它代码导致的结果却被回滚了。使用PROPAGATION_REQUIRES_NEW,需要使用 JtaTransactionManager作为事务管理器。

(5)PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作为事务管理器。(代码示例同上,可同理推出)

(6)PROPAGATION_NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常。

(7)PROPAGATION_NESTED如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。这是一个嵌套事务,使用JDBC 3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。需要JDBC 驱动的java.sql.Savepoint类。有一些JTA的事务管理器实现可能也提供了同样的功能。使用PROPAGATION_NESTED,还需要把PlatformTransactionManager的nestedTransactionAllowed属性设为true;而 nestedTransactionAllowed属性值默认为false。

//事务属性 PROPAGATION_REQUIRED
methodA(){
    
    
    doSomeThingA();
    methodB();
    doSomeThingB();
}

//事务属性 PROPAGATION_NESTED
methodB(){
    
    
    ……
}

如果单独调用methodB方法,则按REQUIRED属性执行。如果调用methodA方法,相当于下面的效果:

main(){
    
    
    Connection con = null;
    Savepoint savepoint = null;
    try{
    
    
        con = getConnection();
        con.setAutoCommit(false);
        doSomeThingA();
        savepoint = con2.setSavepoint();
        try{
    
    
            methodB();
        } catch(RuntimeException ex) {
    
    
            con.rollback(savepoint);
        } finally {
    
    
            //释放资源
        }
        doSomeThingB();
        con.commit();
    } catch(RuntimeException ex) {
    
    
        con.rollback();
    } finally {
    
    
        //释放资源
    }
}

当methodB方法调用之前,调用setSavepoint方法,保存当前的状态到savepoint。如果methodB方法调用失败,则恢复到之前保存的状态。但是需要注意的是,这时的事务并没有进行提交,如果后续的代码(doSomeThingB()方法)调用失败,则回滚包括methodB方法的所有操作。

嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。

PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:它们非常类似,都像一个嵌套事务,如果不存在一个活动的事务,都会开启一个新的事务。使用 PROPAGATION_REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。两个事务不是一个真正的嵌套事务。同时它需要JTA事务管理器的支持。

使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。DataSourceTransactionManager使用savepoint支持PROPAGATION_NESTED时,需要JDBC 3.0以上驱动及1.4以上的JDK版本支持。其它的JTA TrasactionManager实现可能有不同的支持方式。

PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行。

另一方面, PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 潜套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。

由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back.

PROPAGATION_REQUIRED应该是我们首先的事务传播行为。它能够满足我们大多数的事务需求。

2.2.2 隔离级别

事务的第二个维度就是隔离级别(isolation level)。隔离级别定义了一个事务可能受其他并发事务影响的程度。

(1)并发事务引起的问题
在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务。并发虽然是必须的,但可能会导致一下的问题。

  • 脏读(Dirty reads)——脏读发生在一个事务读取了另一个事务改写但尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。
  • 不可重复读(Nonrepeatable read)——不可重复读发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新。
  • 幻读(Phantom read)——幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。

不可重复读与幻读的区别

不可重复读的重点是修改:
同样的条件, 你读取过的数据, 再次读取出来发现值不一样了
例如:在事务1中,Mary 读取了自己的工资为1000,操作并没有完成

    con1 = getConnection();  
    select salary from employee empId ="Mary";  

在事务2中,这时财务人员修改了Mary的工资为2000,并提交了事务.

    con2 = getConnection();  
    update employee set salary = 2000;  
    con2.commit();  

在事务1中,Mary 再次读取自己的工资时,工资变为了2000

    //con1  
    select salary from employee empId ="Mary"; 

在一个事务中前后两次读取的结果并不一致,导致了不可重复读。

幻读的重点在于新增或者删除:
同样的条件, 第1次和第2次读出来的记录数不一样
例如:目前工资为1000的员工有10人。事务1,读取所有工资为1000的员工。

    con1 = getConnection();  
    Select * from employee where salary =1000; 

共读取10条记录

这时另一个事务向employee表插入了一条员工记录,工资也为1000

    con2 = getConnection();  
    Insert into employee(empId,salary) values("Lili",1000);  
    con2.commit();  

事务1再次读取所有工资为1000的员工

    //con1  
    select * from employee where salary =1000;  

共读取到了11条记录,这就产生了幻像读。

从总的结果来看, 似乎不可重复读和幻读都表现为两次读取的结果不一致。但如果你从控制的角度来看, 两者的区别就比较大。
对于前者, 只需要锁住满足条件的记录。
对于后者, 要锁住满足条件及其相近的记录。

(2)隔离级别
隔离级别 含义
ISOLATION_DEFAULT 使用后端数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
ISOLATION_READ_COMMITTED 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
ISOLATION_REPEATABLE_READ 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
ISOLATION_SERIALIZABLE 最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的

2.2.3 只读

事务的第三个特性是它是否为只读事务。如果事务只对后端的数据库进行该操作,数据库可以利用事务的只读特性来进行一些特定的优化。通过将事务设置为只读,你就可以给数据库一个机会,让它应用它认为合适的优化措施。

2.2.4 事务超时

为了使应用程序很好地运行,事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源。事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。

2.2.5 回滚规则

事务五边形的最后一个方面是一组规则,这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的)
但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。

2.3 事务状态

上面讲到的调用PlatformTransactionManager接口的getTransaction()的方法得到的是TransactionStatus接口的一个实现,这个接口的内容如下:

public interface TransactionStatus{
    
    
    boolean isNewTransaction(); // 是否是新的事物
    boolean hasSavepoint(); // 是否有恢复点
    void setRollbackOnly();  // 设置为只回滚
    boolean isRollbackOnly(); // 是否为只回滚
    boolean isCompleted; // 是否已完成
} 

可以发现这个接口描述的是一些处理事务提供简单的控制事务执行和查询事务状态的方法,在回滚或提交的时候需要应用对应的事务状态。

3 编程式事务

3.1 编程式和声明式事务的区别

Spring提供了对编程式事务和声明式事务的支持,编程式事务允许用户在代码中精确定义事务的边界,而声明式事务(基于AOP)有助于用户将操作与事务规则进行解耦。
简单地说,编程式事务侵入到了业务代码里面,但是提供了更加详细的事务管理;而声明式事务由于基于AOP,所以既能起到事务管理的作用,又可以不影响业务代码的具体实现。

3.2 如何实现编程式事务?

Spring提供两种方式的编程式事务管理,分别是:使用TransactionTemplate和直接使用PlatformTransactionManager。

3.2.1 使用TransactionTemplate

采用TransactionTemplate和采用其他Spring模板,如JdbcTempalte和HibernateTemplate是一样的方法。它使用回调方法,把应用程序从处理取得和释放资源中解脱出来。如同其他模板,TransactionTemplate是线程安全的。代码片段:

TransactionTemplate tt = new TransactionTemplate(); // 新建一个TransactionTemplate
Object result = tt.execute(
    new TransactionCallback(){
    
      
        public Object doTransaction(TransactionStatus status){
    
      
            updateOperation();  
            return resultOfUpdateOperation();  
        }  
    }); // 执行execute方法进行事务管理

使用TransactionCallback()可以返回一个值。如果使用TransactionCallbackWithoutResult则没有返回值。

3.2.2 使用PlatformTransactionManager

示例代码如下:

DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); //定义一个某个框架平台的TransactionManager,如JDBC、Hibernate
dataSourceTransactionManager.setDataSource(this.getJdbcTemplate().getDataSource()); // 设置数据源
DefaultTransactionDefinition transDef = new DefaultTransactionDefinition(); // 定义事务属性
transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); // 设置传播行为属性
TransactionStatus status = dataSourceTransactionManager.getTransaction(transDef); // 获得事务状态
try {
    
    
    // 数据库操作
    dataSourceTransactionManager.commit(status);// 提交
} catch (Exception e) {
    
    
    dataSourceTransactionManager.rollback(status);// 回滚
}

4 声明式事务

4.1 事务的5种配置方式

*注:以下配置代码参考自http://www.blogjava.net/robbie/archive/2009/04/05/264003.html

根据代理机制的不同,总结了五种Spring事务的配置方式,配置文件如下:

(1)每个Bean都有一个代理
<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context-2.5.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <bean id="sessionFactory" 
          class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- 事务管理器(声明式的事务) --> 
    <bean id="transactionManager"
          class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <!-- 配置DAO -->
    <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <bean id="userDao" 
          class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> 
        <!-- 配置事务管理器 --> 
        <property name="transactionManager" ref="transactionManager" />    
        <property name="target" ref="userDaoTarget" /> 
        <property name="proxyInterfaces" value="com.bluesky.spring.dao.GeneratorDao" />
        <!-- 配置事务属性 --> 
        <property name="transactionAttributes"> 
            <props>
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props> 
        </property> 
    </bean> 
</beans>
(2)所有Bean共享一个代理基类
<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context-2.5.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <bean id="sessionFactory" 
          class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- 定义事务管理器(声明式的事务) --> 
    <bean id="transactionManager"
          class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <bean id="transactionBase" 
          class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" 
          lazy-init="true" abstract="true"> 
        <!-- 配置事务管理器 --> 
        <property name="transactionManager" ref="transactionManager" /> 
        <!-- 配置事务属性 --> 
        <property name="transactionAttributes"> 
            <props> 
                <prop key="*">PROPAGATION_REQUIRED</prop> 
            </props> 
        </property> 
    </bean>   

    <!-- 配置DAO -->
    <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <bean id="userDao" parent="transactionBase" > 
        <property name="target" ref="userDaoTarget" />  
    </bean>
</beans>
(3)使用拦截器
<?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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- 定义事务管理器(声明式的事务) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean> 

    <bean id="transactionInterceptor" 
        class="org.springframework.transaction.interceptor.TransactionInterceptor"> 
        <property name="transactionManager" ref="transactionManager" /> 
        <!-- 配置事务属性 --> 
        <property name="transactionAttributes"> 
            <props> 
                <prop key="*">PROPAGATION_REQUIRED</prop> 
            </props> 
        </property> 
    </bean>

    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> 
        <property name="beanNames"> 
            <list> 
                <value>*Dao</value>
            </list> 
        </property> 
        <property name="interceptorNames"> 
            <list> 
                <value>transactionInterceptor</value> 
            </list> 
        </property> 
    </bean> 

    <!-- 配置DAO -->
    <bean id="userDao" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
</beans>
(4)使用tx标签配置的拦截器
<?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
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

    <context:annotation-config />
    <context:component-scan base-package="com.bluesky" />

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- 定义事务管理器(声明式的事务) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

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

    <aop:config>
        <aop:pointcut id="interceptorPointCuts"
            expression="execution(* com.bluesky.spring.dao.*.*(..))" />
        <aop:advisor advice-ref="txAdvice"
            pointcut-ref="interceptorPointCuts" />       
    </aop:config>     
</beans>
(5)全注解
<?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
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

    <context:annotation-config />
    <context:component-scan base-package="com.bluesky" />

    <!--开启事务的注解支持-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- 定义事务管理器(声明式的事务) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

</beans>
<!--MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源与DataSourceTransactionManager引用的数据源一致即可。-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> 
	<property name="dataSource" ref="dataSource" />  
	<property name="configLocation">  
		<value>classpath:mybatis-config.xml</value>  
	</property>  
</bean> 

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
	<property name="dataSource" ref="dataSource" />  
</bean> 

@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该注解来覆盖类级别的定义。

虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常

此时在DAO上需加上@Transactional注解,如下:

package com.bluesky.spring.dao;

import java.util.List;

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Component;

import com.bluesky.spring.domain.User;

@Transactional
@Component("userDao")
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
    
    

    public List<User> listUsers() {
    
    
        return this.getSession().createQuery("from User").list();
    }  
}

4.2 一个声明式事务的实例

注:该实例参考自[Spring中的事务管理实例详解](http://www.jb51.net/article/57589.htm)

首先是数据库表

book(isbn, book_name, price)
account(username, balance)
book_stock(isbn, stock)

然后是XML配置

<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
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

    <import resource="applicationContext-db.xml" />

    <context:component-scan
        base-package="com.springinaction.transaction">
    </context:component-scan>

    <tx:annotation-driven transaction-manager="txManager"/>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

</beans>

使用的类
BookShopDao

package com.springinaction.transaction;

public interface BookShopDao {
    
    
    // 根据书号获取书的单价
    public int findBookPriceByIsbn(String isbn);
    // 更新书的库存,使书号对应的库存-1
    public void updateBookStock(String isbn);
    // 更新用户的账户余额:account的balance-price
    public void updateUserAccount(String username, int price);
}

BookShopDaoImpl

package com.springinaction.transaction;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {
    
    

    @Autowired
    private JdbcTemplate JdbcTemplate;

    @Override
    public int findBookPriceByIsbn(String isbn) {
    
    
        String sql = "SELECT price FROM book WHERE isbn = ?";

        return JdbcTemplate.queryForObject(sql, Integer.class, isbn);
    }

    @Override
    public void updateBookStock(String isbn) {
    
    
        //检查书的库存是否足够,若不够,则抛出异常
        String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?";
        int stock = JdbcTemplate.queryForObject(sql2, Integer.class, isbn);
        if (stock == 0) {
    
    
            throw new BookStockException("库存不足!");
        }
        String sql = "UPDATE book_stock SET stock = stock - 1 WHERE isbn = ?";
        JdbcTemplate.update(sql, isbn);
    }

    @Override
    public void updateUserAccount(String username, int price) {
    
    
        //检查余额是否不足,若不足,则抛出异常
        String sql2 = "SELECT balance FROM account WHERE username = ?";
        int balance = JdbcTemplate.queryForObject(sql2, Integer.class, username);
        if (balance < price) {
    
    
            throw new UserAccountException("余额不足!");
        }
        String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
        JdbcTemplate.update(sql, price, username);
    }

}

BookShopService

package com.springinaction.transaction;
public interface BookShopService {
    
    
     public void purchase(String username, String isbn);
}

BookShopServiceImpl

package com.springinaction.transaction;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
    
    

    @Autowired
    private BookShopDao bookShopDao;

    /**
     * 1.添加事务注解
     * 使用propagation 指定事务的传播行为,即当前的事务方法被另外一个事务方法调用时如何使用事务。
     * 默认取值为REQUIRED,即使用调用方法的事务。如果当前事务存在,方法会在该事务中运行。否则,会启动一个新的事务
     * REQUIRES_NEW:使用自己的事务,调用的事务方法的事务被挂起。
     *
     * 2.使用isolation 指定事务的隔离级别,最常用的取值为READ_COMMITTED
     * 3.默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚,也可以通过对应的属性进行设置。通常情况下,默认值即可。
     * 4.使用readOnly 指定事务是否为只读。 表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务。若真的是一个只读取数据库值得方法,应设置readOnly=true
     * 5.使用timeOut 指定强制回滚之前事务可以占用的时间。
     */
    @Transactional(propagation=Propagation.REQUIRES_NEW,
            isolation=Isolation.READ_COMMITTED,
            noRollbackFor={
    
    UserAccountException.class},
            readOnly=true, timeout=3)
    @Override
    public void purchase(String username, String isbn) {
    
    
        //1.获取书的单价
        int price = bookShopDao.findBookPriceByIsbn(isbn);
        //2.更新书的库存
        bookShopDao.updateBookStock(isbn);
        //3.更新用户余额
        bookShopDao.updateUserAccount(username, price);
    }
}

Cashier

package com.springinaction.transaction;
import java.util.List;
public interface Cashier {
    
    
    public void checkout(String username, List<String>isbns);
}

CashierImpl:CashierImpl.checkout和bookShopService.purchase联合测试了事务的传播行为

package com.springinaction.transaction;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service("cashier")
public class CashierImpl implements Cashier {
    
    
    @Autowired
    private BookShopService bookShopService;

    @Transactional
    @Override
    public void checkout(String username, List<String> isbns) {
    
    
        for(String isbn : isbns) {
    
    
            bookShopService.purchase(username, isbn);
        }
    }
}

BookStockException

package com.springinaction.transaction;
public class BookStockException extends RuntimeException {
    
    

    private static final long serialVersionUID = 1L;

    public BookStockException() {
    
    
        super();
        // TODO Auto-generated constructor stub
    }

    public BookStockException(String arg0, Throwable arg1, boolean arg2,
            boolean arg3) {
    
    
        super(arg0, arg1, arg2, arg3);
        // TODO Auto-generated constructor stub
    }

    public BookStockException(String arg0, Throwable arg1) {
    
    
        super(arg0, arg1);
        // TODO Auto-generated constructor stub
    }

    public BookStockException(String arg0) {
    
    
        super(arg0);
        // TODO Auto-generated constructor stub
    }

    public BookStockException(Throwable arg0) {
    
    
        super(arg0);
        // TODO Auto-generated constructor stub
    }
}

UserAccountException

package com.springinaction.transaction;
public class UserAccountException extends RuntimeException {
    
    

    private static final long serialVersionUID = 1L;

    public UserAccountException() {
    
    
        super();
        // TODO Auto-generated constructor stub
    }

    public UserAccountException(String arg0, Throwable arg1, boolean arg2,
            boolean arg3) {
    
    
        super(arg0, arg1, arg2, arg3);
        // TODO Auto-generated constructor stub
    }

    public UserAccountException(String arg0, Throwable arg1) {
    
    
        super(arg0, arg1);
        // TODO Auto-generated constructor stub
    }

    public UserAccountException(String arg0) {
    
    
        super(arg0);
        // TODO Auto-generated constructor stub
    }

    public UserAccountException(Throwable arg0) {
    
    
        super(arg0);
        // TODO Auto-generated constructor stub
    }
}

测试类

package com.springinaction.transaction;

import java.util.Arrays;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTransitionTest {
    
    

    private ApplicationContext ctx = null;
    private BookShopDao bookShopDao = null;
    private BookShopService bookShopService = null;
    private Cashier cashier = null;
    {
    
    
        ctx = new ClassPathXmlApplicationContext("config/transaction.xml");
        bookShopDao = ctx.getBean(BookShopDao.class);
        bookShopService = ctx.getBean(BookShopService.class);
        cashier = ctx.getBean(Cashier.class);
    }

    @Test
    public void testBookShopDaoFindPriceByIsbn() {
    
    
        System.out.println(bookShopDao.findBookPriceByIsbn("1001"));
    }

    @Test
    public void testBookShopDaoUpdateBookStock(){
    
    
        bookShopDao.updateBookStock("1001");
    }

    @Test
    public void testBookShopDaoUpdateUserAccount(){
    
    
        bookShopDao.updateUserAccount("AA", 100);
    }
    @Test
    public void testBookShopService(){
    
    
        bookShopService.purchase("AA", "1001");
    }

    @Test
    public void testTransactionPropagation(){
    
    
        cashier.checkout("AA", Arrays.asList("1001", "1002"));
    }
}

8.3 测试数据准备

8.3.1 需求

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LmVs0oC1-1608402277183)(https://raw.githubusercontent.com/FermHan/tuchuang/master/20191114153607.png)]

DAO层分别有:

  • 根据书号找书的价格
  • 更新书的库存 -1
  • 更新用户的余额 -XX

8.3.2 数据库表

书:书号、书名、价格

库存:书号,库存量

账户:用户名,余额

tom和Jerry买书,

CREATE TABLE book (
  isbn VARCHAR (50) PRIMARY KEY,
  book_name VARCHAR (100),
  price INT
) ;

CREATE TABLE book_stock (
  isbn VARCHAR (50) PRIMARY KEY,
  stock INT,
) ;

CREATE TABLE account (
  username VARCHAR (50) PRIMARY KEY,
  balance INT,
) ;

INSERT INTO account (`username`,`balance`) VALUES ('Tom',100000);
INSERT INTO account (`username`,`balance`) VALUES ('Jerry',150000);

INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-001','book01',100);
INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-002','book02',200);
INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-003','book03',300);
INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-004','book04',400);
INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-005','book05',500);

INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-001',1000);
INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-002',2000);
INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-003',3000);
INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-004',4000);
INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-005',5000);

8.4 初步实现

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

<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
  1. 在需要进行事务控制的方法上加注解 @Transactional

8.5 事务的传播行为

8.5.1 简介

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。

事务传播行为类型 说明
PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中,当前的方法就在这个事务中运行。这是最常见的选择。
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。如果有事务在运行,当前方法就在这个事务中运行,否则它可以不运行在事务中
PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。当前的方法必须运行在事务内部,如果没有正在运行的事务,将它挂起
PROPAGATION_REQUIRES_NEW 当前的方法必须启动新事务新建事务,如果当前存在事务,有事务正在运行,把当前事务挂起
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。当前的方法不应该运行在事务中,如果有运行的事务,将它挂起。
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。如果有事务在运行,当前的方法就应该在这个事务的嵌套事务事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v2bIpRku-1608402277184)(https://raw.githubusercontent.com/FermHan/tuchuang/master/20191114153654.png)]

事务传播属性可以在@Transactional注解的propagation属性中定义。

8.5.2 测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-15pZPdqL-1608402277185)(https://raw.githubusercontent.com/FermHan/tuchuang/master/20191114153704.png)]

8.5.3 说明

①REQUIRED传播行为

当bookService的purchase()方法被另一个事务方法checkout()调用时,它默认会在现有的事务内运行。这个默认的传播行为就是REQUIRED。因此在checkout()方法的开始和终止边界内只有一个事务。这个事务只在checkout()方法结束的时候被提交,结果用户一本书都买不了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WwTPyMDX-1608402277186)(https://raw.githubusercontent.com/FermHan/tuchuang/master/20191114153718.png)]

②. REQUIRES_NEW传播行为

表示该方法必须启动一个新事务,并在自己的事务内运行。如果有事务在运行,就应该先挂起它。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bHgD9mZV-1608402277187)(https://raw.githubusercontent.com/FermHan/tuchuang/master/20191114153730.png)]

8.5.4 补充

在Spring 2.x事务通知中,可以像下面这样在<tx:method>元素中设定传播事务属性。

<tx:advice id="bookShopTxAdvice" transaction-manager="transactionMagager">
	<tx:attribute>
        <tx:method name="purchase" propagation="REQUIRES_NEW"/>
    </tx:attribute>
</tx:advice>

8.6 事务的隔离级别

8.6.1 数据库事务并发问题

​ 假设现在有两个事务:Transaction01和Transaction02并发执行。

  1. 脏读

​ ①Transaction01将某条记录的AGE值从20修改为30。

​ ②Transaction02读取了Transaction01更新后的值:30。

​ ③Transaction01回滚,AGE值恢复到了20。

​ ④Transaction02读取到的30就是一个无效的值。

  1. 不可重复读

​ ①Transaction01读取了AGE值为20。

​ ②Transaction02将AGE值修改为30。

​ ③Transaction01再次读取AGE值为30,和第一次读取不一致。

  1. 幻读

​ ①Transaction01读取了STUDENT表中的一部分数据。

​ ②Transaction02向STUDENT表中插入了新的行。

​ ③Transaction01读取了STUDENT表时,多出了一些行。

8.6.2 隔离级别

​ 数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。

  1. 读未提交:READ UNCOMMITTED

允许Transaction01读取Transaction02未提交的修改。

  1. 读已提交:READ COMMITTED

​ 要求Transaction01只能读取Transaction02已提交的修改。

  1. 可重复读:REPEATABLE READ

​ 确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。

  1. 串行化:SERIALIZABLE

​ 确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

  1. 各个隔离级别解决并发问题的能力见下表
脏读 不可重复读 幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE
  1. 各种数据库产品对事务隔离级别的支持程度
Oracle MySQL
READ UNCOMMITTED ×
READ COMMITTED √(默认)
REPEATABLE READ × √(默认)
SERIALIZABLE

8.6.3 在Spring中指定事务隔离级别

  1. 注解

用@Transactional注解声明式地管理事务时可以在@Transactional的isolation属性中设置隔离级别

  1. XML

在Spring 2.x事务通知中,可以在<tx:method>元素中指定隔离级别

<tx:advice id="bookShopTxAdvice" transaction-manager="transactionMagager">
	<tx:attribute>
        <tx:method name="purchase" propagation="REQUIRES_NEW" isolation="READ_COMMITTED"/>
    </tx:attribute>
</tx:advoce>

8.7 触发事务回滚的异常

8.7.1默认情况

捕获到RuntimeException或Error时回滚,而捕获到编译时异常不回滚。

8.7.2设置途经

  1. 注解@Transactional 注解

​ ① rollbackFor属性:指定遇到时必须进行回滚的异常类型,可以为多个

​ ② noRollbackFor属性:指定遇到时不回滚的异常类型,可以为多个

@Transactional(propagation=Propagation.REQUIRES_NEW,
               isolation=Isolation.READ_COMMITTED,
               rollbackFor={IOException.class,SQLException.class},
               noRollbackFor=ArithmeticException.class )
public void purchase(String isbn,String username){
    
}
  1. XML

在Spring 2.x事务通知中,可以在<tx:method>元素中指定回滚规则。如果有不止一种异常则用逗号分隔。

<tx:advice id="bookShopTxAdvice" transaction-manager="transactionMagager">
	<tx:attribute>
        <tx:method name="purchase" 
                   propagation="REQUIRES_NEW" 
                   isolation="READ_COMMITTED"
                   rollback-for="java.io.IOException,java.sql.SQLException"/>
    </tx:attribute>
</tx:advice>

8.8 事务的超时和只读属性

8.8.1简介

​ 由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。

如果一个事务只读取数据但不做修改,数据库引擎可以对这个事务进行优化。

​ 超时事务属性:事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。事务都卡着不动,卡的时间太长就进行回滚

8.8.2设置

​ 只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。

  1. 注解

@Transaction注解

@Transactional(propagation=Propagatio.REQUIRES_NEW,
               isolation=Isolation.READ_COMMITTED,
               rollbackFor=(IOException.class,SQLException.class),
               noRollbackFor=ArithmeticException.class
               readOnly=true,//只读
               timeout=30)//超时
public void purchase(String isbn,String username){

}

只读并不会限制方法内对数据库的操作,只是spring会认为这个方法他随便用

  1. XML

在Spring 2.x事务通知中,超时和只读属性可以在<tx:method>元素中进行指定

<tx:advice id="bookShopTxAdvice" transaction-manager="transactionMagager">
	<tx:attribute>
        <tx:method name="purchase" 
                   propagation="REQUIRES_NEW" 
                   isolation="READ_COMMITTED"
                   rollback-for="java.io.IOException,java.sql.SQLException"
                   timeout="30"
                   read-only="true"/>
    </tx:attribute>
</tx:advice>

8.9 基于XML文档的声明式事务配置

<!-- 配置事务切面 -->
<aop:config>
    <aop:pointcut 
                  expression="execution(* com.atguigu.tx.component.service.BookShopServiceImpl.purchase(..))" 
                  id="txPointCut"/>
    <!-- 将切入点表达式和事务属性配置关联到一起 -->
    <aop:advisor advice-ref="myTx" pointcut-ref="txPointCut"/>
</aop:config>

<!-- 配置基于XML的声明式事务  -->
<tx:advice id="myTx" transaction-manager="transactionManager">
    <tx:attributes>
        <!-- 设置具体方法的事务属性 -->
        <tx:method name="find*" read-only="true"/>
        <tx:method name="get*" read-only="true"/>
        <tx:method name="purchase" 
                   isolation="READ_COMMITTED" 
                   no-rollback-for="java.lang.ArithmeticException,java.lang.NullPointerException"
                   propagation="REQUIRES_NEW"
                   read-only="false"
                   timeout="10"/>
    </tx:attributes>
</tx:advice>

完整的helloworld

这个helloworld使用的jdbc方式是jdbcTemplate

------------------spring-tx.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-4.0.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
	
	<context:component-scan base-package="com.atguigu.spring.tx.annotation"></context:component-scan>
	
	<!-- 数据源 -->
	<context:property-placeholder location="classpath:db.properties"/>
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="${jdbc.driver}"></property>
		<property name="jdbcUrl" value="${jdbc.url}"></property>
		<property name="user" value="${jdbc.username}"></property>
		<property name="password" value="${jdbc.password}"></property>
	</bean>
	
	
	<!-- JdbcTemplate -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- NamedParameterJdbcTemplate -->
	<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
		<constructor-arg ref="dataSource"></constructor-arg>
	</bean>
	
	<!-- 事务管理器 -->
	<bean id="dataSourceTransactionManager"
		 class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- 开启事务注解 :使用注解来开发事务
		transaction-manager 用来指定事务管理器,如果上一个bean事务管理器的id值 是 transactionManager,可以省略不进行指定。 
	-->
	<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
	

</beans>
DAO层
------------BookShopDao接口-----------------------
package com.atguigu.spring.tx.annotation.dao;

public interface BookShopDao {
	//根据书号查询书的价格
	public int findPriceByIsbn(String isbn );
	//更新书的库存
	public void  updateStock(String isbn);
	//更新用户的余额
	public void  updateUserAccount(String username,Integer price);
}
----------------BookShopDao实现类-------------------------
package com.atguigu.spring.tx.annotation.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import com.atguigu.spring.tx.annotation.exception.BookStockException;
import com.atguigu.spring.tx.annotation.exception.UserAccountException;

@Repository
public class BookShopDaoImpl implements BookShopDao {

	@Autowired
	private JdbcTemplate jdbcTemplate ;
	
	@Override
	public int findPriceByIsbn(String isbn) {
		String sql = "select price from book where isbn = ? ";
		return	jdbcTemplate.queryForObject(sql, Integer.class, isbn);
        // SQL语句,返回类型,传入参数
	}

	@Override
	public void updateStock(String isbn) {
		//判断库存是否足够
		String sql ="select stock from book_stock where isbn = ?";
		Integer stock = jdbcTemplate.queryForObject(sql, Integer.class, isbn);
		
		if(stock <=0) {
			throw new BookStockException("库存不足.....");
		}
		
		sql = "update book_stock set stock = stock -1 where isbn = ? ";
		
		jdbcTemplate.update(sql, isbn);
	}

	@Override
	public void updateUserAccount(String username, Integer price) {
		//判断余额是否足够
		String sql ="select balance from account where username = ? ";
		Integer balance = jdbcTemplate.queryForObject(sql, Integer.class,username);
		
		if(balance < price) {
			throw new UserAccountException("余额不足......");
		}
		
		sql = "update account set balance = balance - ?  where username = ? ";
		
		jdbcTemplate.update(sql, price,username);	
	}
}
SERVICE层
-------------------BookShopService.java-----------
package com.atguigu.spring.tx.annotation.service;

public interface BookShopService {
	
	public void  buyBook(String username, String isbn);
}
---------------BookShopServiceImpl.java------------------
package com.atguigu.spring.tx.annotation.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.atguigu.spring.tx.annotation.dao.BookShopDao;
import com.atguigu.spring.tx.annotation.exception.UserAccountException;

@Transactional   //对当前类中所有的方法都起作用
@Service
public class BookShopServiceImpl implements BookShopService {
	
	@Autowired
	private BookShopDao  bookShopDao ;
	
	/**
	 * 事务属性: 
	 * 	 1. 事务的传播行为 propagation: 一个事务方法被另外一个事务方法调用时,当前的事务如何使用事务. 
	 * 			Propagation.REQUIRED  默认值.  使用调用者的事务. 
	 * 			Propagation.REQUIRES_NEW     将调用者的事务挂起, 重新开启事务来使用. 
	 *   2. 事务的隔离级别 isolation
	 *   		1    读未提交      脏读
	 *   		2    读已提交      不可重复读
	 *    		4    可重复读      幻读
	 *    		8    串行化         效率低。
	 *   3. 事务的回滚与不回滚   默认情况下, Spring会对所有的运行时异常进行事务回滚.
	 *   		rollbackFor	
	 *          rollbackForClassName
	 *          noRollbackFor
	 *          noRollbackForClassName
	 *   4. 事务的只读设置:
	 *   		readOnly 
	 *   			true:  只读     代表着只会对数据库进行读取操作, 不会有修改的操作.
	 *    						如果确保当前的事务只有读取操作,就有必要设置为只读,可以帮助数据库
	 *    						引擎优化事务 
	 *   			false: 非只读   不仅会读取数据还会有修改操作。 
	 *   5. 事务的超时设置:  设置事务在强制回滚之前可以占用的时间. 
	 *   		timeout:	
	 *   		 
	 */ 
	//只对当前的方法起作用 //加上这个注解代表这个方法中的3条语句形成了一个事务
	@Transactional(propagation=Propagation.REQUIRES_NEW,
					isolation=Isolation.READ_COMMITTED,
				 /* noRollbackFor={UserAccountException.class}*/
					readOnly=false,
					timeout=3)
	public void buyBook(String username, String isbn) {
			
		/*try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}*/
		
		Integer price = bookShopDao.findPriceByIsbn(isbn);
		
		bookShopDao.updateStock(isbn);
		
		bookShopDao.updateUserAccount(username, price);
		
	}
}

-------------Cashier.java-----------------------
package com.atguigu.spring.tx.annotation.service;

import java.util.List;

public interface Cashier {
	
	public void checkOut(String username, List<String> isbns );
}
-------------CashierImpl.java-------------------------------

package com.atguigu.spring.tx.annotation.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class CashierImpl implements Cashier {
	
	@Autowired
	private BookShopService bookShopService ;
	
	@Transactional
	public void checkOut(String username, List<String> isbns) {
		for (String isbn : isbns) {
				bookShopService.buyBook(username, isbn);
		}
	}
}

Exception.java
------------图书库存不足异常-----------------
package com.atguigu.spring.tx.annotation.exception;

public class BookStockException  extends RuntimeException{
	//自己定义异常
	public BookStockException() {
		super();
		// TODO Auto-generated constructor stub
	}

	public BookStockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
		super(message, cause, enableSuppression, writableStackTrace);
		// TODO Auto-generated constructor stub
	}

	public BookStockException(String message, Throwable cause) {
		super(message, cause);
		// TODO Auto-generated constructor stub
	}

	public BookStockException(String message) {
		super(message);
		// TODO Auto-generated constructor stub
	}

	public BookStockException(Throwable cause) {
		super(cause);
		// TODO Auto-generated constructor stub
	}
}
--------------------余额不足异常------------------------
package com.atguigu.spring.tx.annotation.exception;

public class UserAccountException extends RuntimeException {

	public UserAccountException() {
		super();
		// TODO Auto-generated constructor stub
	}

	public UserAccountException(String message, Throwable cause, boolean enableSuppression,
			boolean writableStackTrace) {
		super(message, cause, enableSuppression, writableStackTrace);
		// TODO Auto-generated constructor stub
	}

	public UserAccountException(String message, Throwable cause) {
		super(message, cause);
		// TODO Auto-generated constructor stub
	}

	public UserAccountException(String message) {
		super(message);
		// TODO Auto-generated constructor stub
	}

	public UserAccountException(Throwable cause) {
		super(cause);
		// TODO Auto-generated constructor stub
	}

}
TEST.java
package com.atguigu.spring.tx.annotation.test;

import java.util.ArrayList;
import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.atguigu.spring.tx.annotation.dao.BookShopDao;
import com.atguigu.spring.tx.annotation.service.BookShopService;
import com.atguigu.spring.tx.annotation.service.Cashier;

public class TestTransaction {
	
	private BookShopDao bookShopDao ; 
	
	private BookShopService bookShopService;
	
	private Cashier  cashier ; 
	
	@Before
	public void init() {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-tx.xml");
		
		bookShopDao = ctx.getBean("bookShopDaoImpl",BookShopDao.class);
		bookShopService = ctx.getBean("bookShopServiceImpl",BookShopService.class);
		System.out.println(bookShopService.getClass().getName());
		
		cashier = ctx.getBean("cashierImpl",Cashier.class);
	}

   @Test
   public void testTx() {
	   bookShopService.buyBook("Tom", "1001");
   }
   
   
   @Test
   public void testCheckOut() {  // 两本都买成功    买成功其中一本    两本都没买成功
	   List<String> isbns = new ArrayList<>();
	   
	   isbns.add("1001");
	   isbns.add("1002");
	   
	   cashier.checkOut("Tom", isbns);
   }
   
}

注解驱动开发中的事务

2.事务回滚

2.1 默认回滚策略

@Transactional
public void rollback() throws SQLException {
    // update db
    throw new SQLException("exception");
}

上述代码事务会回滚吗?不会的,就算抛出SQLException了,但是之前的数据库操作依然会提交,原因就是@Transactional默认情况下只回滚RuntimeException和Error。

2.2 指定回滚异常

因此,如果要指定哪些异常需要回滚,则通过配置@Transactional中rollbackFor,例如

@Transactional(rollbackFor = {SQLException.class})
public void rollback() throws SQLException {
    // update db
    throw new SQLException("exception");
}

按照上面例子,那指定的SQLException,当抛出RuntimeException的时候,还会回滚吗?

还是会回滚的。

2.3 事务嵌套的回滚

假设有下面的逻辑,事务会回滚吗(或者说 updateA,updateB,updateC)哪些更新会提交

@Transactional
public void rollback() {
    // updateA
    try{
        selfProxy.innelTransaction()
    }catch(RuntimeException e){
        //do nothing
    }
    //updateC
}
  
@Transactional
public void innelTransaction() throws SQLException {
    // updateB
    throw new RuntimeException("exception");
}

答案是会回滚,因为内部事务触发回滚,当前事务被标记为 rollback-only,

当外部事务提交的时候,Spring抛出以下异常,同时回滚外部事务

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

2.4 小结

所以,在需要事务回滚的时候,最好还是抛出RuntimeException,并且不要在代码中捕获此类异常

三、事务传播性

@Transaction中的propagation的可以配置事务的传播性,网上介绍的很多,就直接复制一段

PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。 (也是默认策略)
PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。 
PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。 
PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。 
PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 
PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。

3.1 如何在事务中读取最新配置

有时候需要在一个事务中,读取最新数据(默认是读取事务开始前的快照)。其实很简单,只要使用上面PROPAGATION_NOT_SUPPORTED传播性就可以了。

@Transactional
public void transaction() throws SQLException {
    // do something
    selfProxy.queryNewValue();
}
  
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void queryNewValue() throws SQLException {
    //查询数据中的最新值
} 

四、内部调用事务方法

事务注解的实质就是在创建一个动态代理,在调用事务方法前开启事务,在事务方法结束以后决定是事务提交还是回滚。

因此,直接在类内部中调用事务方法,是不会经过动态代理的

img

。 因此,如果要使方法B点事务生效,必须这样

img

4.1 解决办法

解决思路:需要在内部调用方法B的时候,找到当前类的代理类,用代理类去调用方法B

4.1.1 解决办法1

@Service
public class MyService{
    @Transactional
    public void transaction(){
        // do something
        ((MyService) AopContext.currentProxy()).queryNewValue();
    }
      
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void queryNewValue(){
        //查询数据中的最新值
    }
}

通过AopContext.currentProxy()可以拿到当前类的代理类,但是要使用这个时候,必须在启动类上加上

@EnableAspectJAutoProxy(exposeProxy=true)

4.1.2 解决办法2

既然是要拿到当前代理类,那其实直接在Spring的容器里面去拿也可以啊。在spring中拿Bean的方法大致有2种

通过注入

@Service``public` `class` `MyService{``  ``@Autowired``  ``private` `MyService self;``  ``@Transactional``  ``public` `void` `transaction() {``    ``// do something``    ``self.queryNewValue();``  ``}``   ` `  ``@Transactional``(propagation = Propagation.NOT_SUPPORTED)``  ``public` `void` `queryNewValue() {``    ``//查询数据中的最新值``  ``}``}

tips:spring现在对一些循环依赖是提供支持的,简单来说,满足:

  1. Bean是单例
  2. 注入的方式不是构造函数注入

通过BeanFactory

@Service
public class MyService{
    @Autowired
    private MyService self;
    @Transactional
    public void transaction() {
        // do something
        self.queryNewValue();
    }
      
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void queryNewValue() {
        //查询数据中的最新值
    }
}

4.2 需要注意的地方

  • 使用@Transaction注解的方法,必须用public来修饰。
  • 其实不止是@Transaction,其他类似@Cacheable,@Retryable等依赖spring proxy也必须使用上述方式达到内部调用。
  • @Transactional,@Async放在同一个类中,如果使用Autowire注入会循环依赖,而使用BeanFactoryAware会使得@Transactional无效

事务源码

package com.atguigu.tx;

import java.beans.PropertyVetoException;

import javax.sql.DataSource;

import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.aop.framework.autoproxy.InfrastructureAdvisorAutoProxyCreator;
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.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration;
import org.springframework.transaction.annotation.TransactionManagementConfigurationSelector;
import org.springframework.transaction.annotation.Transactional;

import com.mchange.v2.c3p0.ComboPooledDataSource;

/**
 * 声明式事务:
 * 
 * 环境搭建:
 * 1、导入相关依赖
 * 		数据源、数据库驱动、Spring-jdbc模块
 * 2、配置数据源、JdbcTemplate(Spring提供的简化数据库操作的工具)操作数据
 * 3、给方法上标注 @Transactional 表示当前方法是一个事务方法;
 * 4、 @EnableTransactionManagement 开启基于注解的事务管理功能;
 * 		@EnableXXX
 * 5、配置事务管理器来控制事务;
 * 		@Bean
 * 		public PlatformTransactionManager transactionManager()
 * 
 * 
 * 原理:
 * 1)、@EnableTransactionManagement
 * 			利用TransactionManagementConfigurationSelector给容器中会导入组件
 * 			导入两个组件
 * 			AutoProxyRegistrar
 * 			ProxyTransactionManagementConfiguration
 * 2)、AutoProxyRegistrar:
 * 			给容器中注册一个 InfrastructureAdvisorAutoProxyCreator 组件;
 * 			InfrastructureAdvisorAutoProxyCreator:?
 * 			利用后置处理器机制在对象创建以后,包装对象,返回一个代理对象(增强器),代理对象执行方法利用拦截器链进行调用;
 * 
 * 3)、ProxyTransactionManagementConfiguration 做了什么?
 * 			1、给容器中注册事务增强器;
 * 				1)、事务增强器要用事务注解的信息,AnnotationTransactionAttributeSource解析事务注解
 * 				2)、事务拦截器:
 * 					TransactionInterceptor;保存了事务属性信息,事务管理器;
 * 					他是一个 MethodInterceptor;
 * 					在目标方法执行的时候;
 * 						执行拦截器链;
 * 						事务拦截器:
 * 							1)、先获取事务相关的属性
 * 							2)、再获取PlatformTransactionManager,如果事先没有添加指定任何transactionmanger
 * 								最终会从容器中按照类型获取一个PlatformTransactionManager;
 * 							3)、执行目标方法
 * 								如果异常,获取到事务管理器,利用事务管理回滚操作;
 * 								如果正常,利用事务管理器,提交事务
 */
@EnableTransactionManagement
@ComponentScan("com.atguigu.tx")
@Configuration
public class TxConfig {
    
    
	
	//数据源
	@Bean
	public DataSource dataSource() throws Exception{
    
    
		ComboPooledDataSource dataSource = new ComboPooledDataSource();
		dataSource.setUser("root");
		dataSource.setPassword("123456");
		dataSource.setDriverClass("com.mysql.jdbc.Driver");
		dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
		return dataSource;
	}
	
	@Bean
	public JdbcTemplate jdbcTemplate() throws Exception{
    
    
		//Spring对@Configuration类会特殊处理;给容器中加组件的方法,多次调用都只是从容器中找组件
		JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
		return jdbcTemplate;
	}
	
	//注册事务管理器在容器中
	@Bean
	public PlatformTransactionManager transactionManager() throws Exception{
    
    
		return new DataSourceTransactionManager(dataSource());
	}
	
}

TransactionManagementConfigurationSelector

public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {
    
    

    @Override
    protected String[] selectImports(AdviceMode adviceMode) {
    
    
        switch (adviceMode) {
    
    
            case PROXY:// 
                return new String[] {
    
    AutoProxyRegistrar.class.getName(), //第一个组件
                                     ProxyTransactionManagementConfiguration.class.getName()};//第二个组件,是一个配置类,
            case ASPECTJ:
                return new String[] {
    
    TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME};//"org.springframework.transaction.aspectj.AspectJTransactionManagementConfiguration";
            default:
                return null;
        }
    }

}
2)、AutoProxyRegistrar:
 * 			给容器中注册一个 InfrastructureAdvisorAutoProxyCreator 组件;
 * 			InfrastructureAdvisorAutoProxyCreator:?
 * 			利用后置处理器机制在对象创建以后,包装对象,返回一个代理对象(增强器),代理对象执行方法利用拦截器链进行调用;
 * 
3)、ProxyTransactionManagementConfiguration 做了什么?
 * 			1、给容器中注册事务增强器;
 * 				1)、事务增强器要用事务注解的信息,AnnotationTransactionAttributeSource解析事务注解
 * 				2)、事务拦截器:
 * 					TransactionInterceptor;保存了事务属性信息,事务管理器;
 * 					他是一个 MethodInterceptor;
 * 					在目标方法执行的时候;
 * 						执行拦截器链;
 * 						事务拦截器:
 * 							1)、先获取事务相关的属性
 * 							2)、再获取PlatformTransactionManager,如果事先没有添加指定任何transactionmanger
 * 								最终会从容器中按照类型获取一个PlatformTransactionManager;
 * 							3)、执行目标方法
 * 								如果异常,获取到事务管理器,利用事务管理回滚操作;
 * 								如果正常,利用事务管理器,提交事务

(1) AutoProxyRegistrar

public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    
    

    /*
 * 		     给容器中注册一个 InfrastructureAdvisorAutoProxyCreator 组件;
 * 			InfrastructureAdvisorAutoProxyCreator:?
 * 			利用后置处理器机制在对象创建以后,包装对象,返回一个代理对象(增强器),代理对象执行方法利用拦截器链进行调用;
    */
    private final Log logger = LogFactory.getLog(getClass());

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
    
        boolean candidateFound = false;
        Set<String> annoTypes = importingClassMetadata.getAnnotationTypes();
        for (String annoType : annoTypes) {
    
    
            AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annoType);
            Object mode = candidate.get("mode");
            Object proxyTargetClass = candidate.get("proxyTargetClass");
            if (mode != null && proxyTargetClass != null && mode.getClass().equals(AdviceMode.class) &&
                proxyTargetClass.getClass().equals(Boolean.class)) {
    
    
                candidateFound = true;
                if (mode == AdviceMode.PROXY) {
    
    //如果是PROXY
                    // 用工具注册自带代理创建器,注册了一个InfrastructureAdvisorAutoProxyCreator
                    AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
                    /*
                    public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
		return registerAutoProxyCreatorIfNecessary(registry, null);
	}

	public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
		return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
	}
                    */
                    // 这个东西用于事务的传播
                    if ((Boolean) proxyTargetClass) {
    
    
                        AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
                        return;
                    }
                }
            }
        }
        if (!candidateFound) {
    
    
            String name = getClass().getSimpleName();
            logger.warn(String.format("%s was imported but no annotations were found " +
                                      "having both 'mode' and 'proxyTargetClass' attributes of type " +
                                      "AdviceMode and boolean respectively. This means that auto proxy " +
                                      "creator registration and configuration may not have occured as " +
                                      "intended, and components may not be proxied as expected. Check to " +
                                      "ensure that %s has been @Import'ed on the same class where these " +
                                      "annotations are declared; otherwise remove the import of %s " +
                                      "altogether.", name, name, name));
        }
    }

}
(2).1 InfrastructureAdvisorAutoProxyCreator
public class InfrastructureAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator {
    
    // 他也是一个BeanPostProcessor
    // 利用后置处理器机制在对象创建以后,包装对象,返回一个代理对象(增强器),代理对象执行方法利用拦截器链进行调用;
	@Nullable
	private ConfigurableListableBeanFactory beanFactory;


	@Override
	protected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    
    
		super.initBeanFactory(beanFactory);
		this.beanFactory = beanFactory;
	}

	@Override
	protected boolean isEligibleAdvisorBean(String beanName) {
    
    
		return (this.beanFactory != null && this.beanFactory.containsBeanDefinition(beanName) &&
				this.beanFactory.getBeanDefinition(beanName).getRole() == BeanDefinition.ROLE_INFRASTRUCTURE);
	}

}

(2) ProxyTransactionManagementConfiguration

这是一个配置类,导入了很多组件

@Configuration
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
    
    

    // 注册 事务增强器
    @Bean(name=TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
    
    
        BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
        // 事务属性,解析xml中的配置
        advisor.setTransactionAttributeSource(transactionAttributeSource());//里面有解析各种注解解析器,就是注解里能写的属性
        // 事务拦截器,保存了事务属性信息,事务管理器 // TransactionInterceptor实现了MethodInterceptor
        advisor.setAdvice(transactionInterceptor());
        advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
        return advisor;
    }

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public TransactionAttributeSource transactionAttributeSource() {
    
    
        return new AnnotationTransactionAttributeSource();
    }

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public TransactionInterceptor transactionInterceptor() {
    
    
        // new,该类实现了MethodInterceptor
        TransactionInterceptor interceptor = new TransactionInterceptor();
        interceptor.setTransactionAttributeSource(transactionAttributeSource());
        if (this.txManager != null) {
    
    
            interceptor.setTransactionManager(this.txManager);
        }
        return interceptor;
    }

}

spring提供了事务相关接口:

  • TransactionDefinition:事务定义:事务的隔离级别、事务的传播行为
  • TranscationAttribute:事务属性:实现了对回滚规则的扩展(处理异常)
  • PlatfromTransactionManager:事务管理器
  • TransactionStatus:事务运行时状态

配置说明

一、项目中spring+mybaits xml配置解析

一般我们会在datasource.xml中进行如下配置,但是其中每个配置项原理和用途是什么,并不是那么清楚,如果不清楚的话,在使用时候就很有可能会遇到坑,所以下面对这些配置项进行一一解说

(1)配置数据源
 <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
        <property name="url" value="${db_url}" />
        <property name="username" value="$db_user}" />
        <property name="password" value="${db_passwd}" />
        <property name="maxWait" value="${db_maxWait}" />
        <property name="maxActive" value="28" /> 
        <property name="initialSize" value="2" />
        <property name="minIdle" value="0" />
        <property name="timeBetweenEvictionRunsMillis" value="db_time" />
    </bean>
 
(2)创建sqlSessionFactory
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="mapperLocations" value="classpath*:com/**/mapper/*Mapper*.xml" /> 
        <property name="dataSource" ref="dataSource" />
        <property name="typeAliasesPackage" value="com.test.***.dal" />
</bean>
    
(3)配置扫描器,扫描指定路径的mapper生成数据库操作代理类
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="annotationClass" value="javax.annotation.Resource"></property>
        <property name="basePackage" value="com.test.***.dal.***.mapper" />
        <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
 
(4)配置事务管理器
<bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
 
(5)声明使用注解式事务
<tx:annotation-driven transaction-manager="transactionManager" />
(6)注册各种beanfactory处理器
<context:annotation-config />   
 
(7)该配置创建了一个TransactionInterceptor的bean,作为事务切面的执行方法
 <tx:advice id="defaultTxAdvice">
    <tx:attributes>
        <tx:method name="*" rollback-for="Exception" />
    </tx:attributes>
</tx:advice>
 
(8)该配置创建了一个DefaultBeanFactoryPointcutAdvisor的bean,该bean是一个advisor,里面包含了pointcut和advice.前者说明切面加在哪里,后者是执行逻辑。此处可以配多个advisor
<aop:config>
    <aop:pointcut id="myCut" expression="(execution(* *..*BoImpl.*(..))) "/>
    <aop:advisor pointcut-ref="myCut" advice-ref="defaultTxAdvice" />
</aop:config>

1.1 数据源配置

(1)是数据源配置,这个没啥好说的。

1.2 配置SqlSessionFactory

(2) 作用是根据配置创建一个SqlSessionFactory,看下SqlSessionFactoryBean的代码知道它实现了FactoryBean和InitializingBean类,由于实现了InitializingBean,所以自然它的afterPropertiesSet方法,由于实现了FactoryBean类,所以自然会有getObject方法。下面看下时序图:

img

从时序图可知,SqlSessionFactoryBean类主要是通过属性配置创建SqlSessionFactory实例,具体是解析配置中所有的mapper文件放到configuration,然后作为构造函数参数实例化一个DefaultSqlSessionFactory作为SqlSessionFactory。

1.3 配置扫描器,扫描指定路径的mapper生成数据库操作代理类

MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware接口,所以会重写一下方法:

1.3.1
//在bean注册到ioc后创建实例前修改bean定义和新增bean注册,这个是在context的refresh方法调用
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
1.3.2
//在bean注册到ioc后创建实例前修改bean定义或者属性值
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
1.3.3
//set属性设置后调用
void afterPropertiesSet() throws Exception;
1.3.4
//获取IOC容器上下文,在context的prepareBeanFactory中调用
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
1.3.5
//获取bean在ioc容器中名字,在context的prepareBeanFactory中调用
void setBeanName(String name);

先上个扫描mapper生成代理类并注册到ioc时序图:

img

首先MapperScannerConfigurer实现的afterPropertiesSet方法用来确保属性basePackage不为空

public void afterPropertiesSet() throws Exception {
    
    
    notNull(this.basePackage, "Property 'basePackage' is required");
  }

postProcessBeanFactory里面啥都没做,setBeanName获取了bean的名字,setApplicationContext里面获取了ioc上下文。下面看重要的方法postProcessBeanDefinitionRegistry,由于mybais是运行时候才通过解析mapper文件生成代理类注入到ioc,所以postProcessBeanDefinitionRegistry正好可以干这个事情。

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    
    
    if (this.processPropertyPlaceHolders) {
    
    
      processPropertyPlaceHolders();
    }
 
    //构造一个ClassPathMapperScanner查找mapper
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    //javax.annotation.Resource
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    //引用sqlSessionFactory
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    //ioc上下文
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.registerFilters();
   //basePackage=com.alibaba.***.dal.***.mapper,com.alibaba.rock.auth.mapper,com.alibaba.rock.workflow.dal.workflow.mapper
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

下面重点看下scan方法:

 public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    
    
    //根据指定路径去查找对应mapper的接口类,并转化为beandefination
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
 
    if (beanDefinitions.isEmpty()) {
    
    
      logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
    
    
      //修改接口类bean的beandefination
      processBeanDefinitions(beanDefinitions);
    }
 
    return beanDefinitions;
  }

其中super.doScan(basePackages);根据指定路径查找mapper接口类,并生成bean的定义对象,对象中包含beanclassname,beanclass属性,最后注册该bean到ioc容器。下面看下最重要的processBeanDefinitions方法对bean定义的改造。

 private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    
    
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
    
    
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
 
      // 上面讲的扫描后beanclass设置的为mapper接口类,但是这里修改为MapperFactoryBean,MapperFactoryBean代理了mapper接口类,并且实际mapper接口类作为构造函数传入了      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); 
      definition.setBeanClass(this.mapperFactoryBean.getClass());
      definition.getPropertyValues().add("addToConfig", this.addToConfig);
 
      //设置属性配置中的sqlSessionFactory
      boolean explicitFactoryUsed = false;
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
    
    
        definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
    
    
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }
 
      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
    
    
        if (explicitFactoryUsed) {
    
    
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {
    
    
        if (explicitFactoryUsed) {
    
    
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }
 
      if (!explicitFactoryUsed) {
    
    
        if (logger.isDebugEnabled()) {
    
    
          logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        }
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }

注:这里修改了mapper接口类的beandefination中的beanclass为MapperFactoryBean,它则负责生产数据类操作代理类,实际mapper接口类作为构造函数传入了 。由于只修改了beanclass,没有修改beanname,所以我们从容器中获取时候无感知的。

在上一个代理bean如何构造的时序图:

img

screenshot.png

下面看下MapperFactoryBean是如何生成代理类的:
首先,上面代码设置了MapperFactoryBean的setSqlSessionFactory方法:

  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    
    
    if (!this.externalSqlSession) {
    
    
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
  }

上面方法创建了sqlSession,由于MapperFactoryBean为工厂bean所以实例化时候会调用getObject方法:

 public T getObject() throws Exception {
    
    
    return getSqlSession().getMapper(this.mapperInterface);
  }

其实是调用了SqlSessionTemplate->getMapper,其中mapperInterface就是创建MapperFactoryBean时候的构造函数参数。

public <T> T getMapper(Class<T> type) {
    
    
    return getConfiguration().getMapper(type, this);
  }

这里调用getConfiguration().getMapper(type, this);实际是DefaultSqlSessionFactory里面的configration的getMapper方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    
    
   //knownMappers是上面时序图中步骤6设置进入的。
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
    
    
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
    
    
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
    
    
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
 protected T newInstance(MapperProxy<T> mapperProxy) {
    
    
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] {
    
     mapperInterface }, mapperProxy);
  }
 
  public T newInstance(SqlSession sqlSession) {
    
    
   //代理回调类为MapperProxy
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

在上一个实际执行sql时候调用代理类的序列图:

img

screenshot.png

所以当调用实际的数据库操作时候会调用MapperProxy的invoke方法:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
    if (Object.class.equals(method.getDeclaringClass())) {
    
    
      try {
    
    
        return method.invoke(this, args);
      } catch (Throwable t) {
    
    
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

mapperMethod.execute(sqlSession, args);里面实际是调用当前mapper对应的SqlSessionTemplate的数据库操作,而它有委托给了代理类sqlSessionProxy,sqlSessionProxy是在SqlSessionTemplate的构造函数里面创建的:

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {
    
    
 
    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");
 
    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] {
    
     SqlSession.class },
        new SqlSessionInterceptor());
  }

所以最终数据库操作有被代理SqlSessionInterceptor执行:

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
      //有TransactionSynchronizationManager管理
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
    
    
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
    
    
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
    
    
          .....
      }
    }
 
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
    
    
 
    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
 
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
 
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
    
    
      return session;
    }
 
    if (LOGGER.isDebugEnabled()) {
    
    
      LOGGER.debug("Creating a new SqlSession");
    }
   //这里看到了使用sessionfactory熟悉的打开了一个session
    session = sessionFactory.openSession(executorType);
 
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
 
    return session;
  }

注意:这里3里面配置的扫描文件在4的扫描文件里面一定要有,因为3给每个扫描文件生成了一个代理,如果4里面多了一个mapper,那么在4中将找不到。

1.4 配置事务管理器

事务管理器作用见名知意,是用来管理事务的。

1.5 advice配置

作用是创建了一个TransactionInterceptor的bean,作为事务切面的执行方法。标签解析的流程图:

img

screenshot.png

由于是tx标签,自然要查找TxNamespaceHandler,代码如下:

public class TxNamespaceHandler extends NamespaceHandlerSupport {
    
    
 
    static final String TRANSACTION_MANAGER_ATTRIBUTE = "transaction-manager";
    static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager";
 
    static String getTransactionManagerName(Element element) {
    
    
        return (element.hasAttribute(TRANSACTION_MANAGER_ATTRIBUTE) ?
                element.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE) : DEFAULT_TRANSACTION_MANAGER_BEAN_NAME);
    }
 
    @Override
    public void init() {
    
    
        registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
        registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
        registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser());
    }
 
}
 

从init方法知道advice标签需要TxAdviceBeanDefinitionParser这个解析类。
结合流程图第一步设置了事务管理器的引用,我们看下引用的bean的名字:

static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager";
 
static String getTransactionManagerName(Element element) {
    
    
        return (element.hasAttribute(TRANSACTION_MANAGER_ATTRIBUTE) ?
                element.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE) : DEFAULT_TRANSACTION_MANAGER_BEAN_NAME);
    }

可以知道如果没有配置这个属性,那么默认查找依赖beanname=transactionManager。
然后parseAttributeSource主要循环解析我们配置的method标签,和设置的方法的事务属性。
另外代码:

protected Class<?> getBeanClass(Element element) {
    
    
        return TransactionInterceptor.class;
    }

可以知道这个advice标签实际是创了TransactionInterceptor对象,并且通过调用setTransactionManager设置了事务管理器,通过setTransactionAttributeSources设置了事务属性。

1.6 设置advisor

标签<aop:config>作用是创建了DefaultBeanFactoryPointcutAdvisor作为拦截器,把满足切点的bean进行代理使用事务拦截器进行拦截。具体标签逻辑先看流程图:

img

screenshot.png

从标签aop:config可知要查找AopNamespaceHandler,代码如下:

public class AopNamespaceHandler extends NamespaceHandlerSupport {
    
    
 
    @Override
    public void init() {
    
    
        registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
        registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
        registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
 
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    }
}

可知config标签是ConfigBeanDefinitionParser来解析的,根据流程图知configureAutoProxyCreator注册了AspectJAwareAdvisorAutoProxyCreator类,然后createAdvisorBeanDefinition创建了DefaultBeanFactoryPointcutAdvisor,它是个advisor,并且设置引用了advice,这个adivce就是上面1.5讲解的,然后createPointcutDefinition创建了切点AspectJExpressionPointcut,最后把切点设置到了advisor。
DefaultBeanFactoryPointcutAdvisor作用就是对满足pointcut表达式的类的方法进行代理,并且使用advice进行拦截处理,而advice就是事务拦截器。

1.7 设置注解式事务

上面介绍完后就可以使用事务切面了,但是有时候还需要在具体类或者方法上进行注解行事务,那么这就需要加 <tx:annotation-driven transaction-manager=“transactionManager” />配置
先上时序图:

img

screenshot.png

!
同理1.6 不同是这里创建了advisor,设置了advice(事务拦截器),但是好像没有设置pointcut,看下BeanFactoryTransactionAttributeSourceAdvisor源码知道:

public class BeanFactoryTransactionAttributeSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
    
    
 
    private TransactionAttributeSource transactionAttributeSource;
 
    private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
    
    
        @Override
        protected TransactionAttributeSource getTransactionAttributeSource() {
    
    
            return transactionAttributeSource;
        }
    };
}

直接内置了pointcut,只不过1.6是AspectJExpressionPointcut表达式的切点,这里是注解。
那么这个BeanFactoryTransactionAttributeSourceAdvisor什么时候被用来增强注解事务的类那,那是InfrastructureAdvisorAutoProxyCreator所做的事情,InfrastructureAdvisorAutoProxyCreator是个BeanPostProcessor,会在bean创建初始化后时候调用postProcessAfterInitialization,就是这个方法。

另外注意如果配置了多个注解式标签在datasource.xml里面时候只有第一个生效

public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {
    
    
            AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);
 
            String txAdvisorBeanName = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME;
            //如果配置了多个注解式标签在datasource.xml里面时候只有第一个生效
            if (!parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) {
    
    
                Object eleSource = parserContext.extractSource(element);
 
                // Create the TransactionAttributeSource definition.
                RootBeanDefinition sourceDef = new RootBeanDefinition(
                        "org.springframework.transaction.annotation.AnnotationTransactionAttributeSource");
                sourceDef.setSource(eleSource);
                sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
                String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);
 
                // Create the TransactionInterceptor definition.
                RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class);
                interceptorDef.setSource(eleSource);
                interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
                registerTransactionManager(element, interceptorDef);
                interceptorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
                String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);
 
                // Create the TransactionAttributeSourceAdvisor definition.
                RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class);
                advisorDef.setSource(eleSource);
                advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
                advisorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
                advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
                if (element.hasAttribute("order")) {
    
    
                    advisorDef.getPropertyValues().add("order", element.getAttribute("order"));
                }
                parserContext.getRegistry().registerBeanDefinition(txAdvisorBeanName, advisorDef);
 
                CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource);
                compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));
                compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));
                compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, txAdvisorBeanName));
                parserContext.registerComponent(compositeDef);
            }
        }

1.8 注册各种beanfactory处理器

当我们需要使用BeanPostProcessor时,最直接的使用方法是在Spring配置文件中定义这些Bean。单这些会显得比较笨拙,
例如:使用@Autowired注解,必须事先在Spring容器中声明

<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor "/>

使用 @Required注解,就必须声明:

<bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"/>

通过标签context:annotation-config/ ,我们可以同时自动注册这些常用的beanfactory处理器,避免了我们一个个配置的繁琐步骤:

public class ContextNamespaceHandler extends NamespaceHandlerSupport {
    
    
 
                ...
        registerJava5DependentParser("annotation-config",
                "org.springframework.context.annotation.AnnotationConfigBeanDefinitionParser");
        ....
}
 
public class AnnotationConfigBeanDefinitionParser implements BeanDefinitionParser {
    
    
 
    public BeanDefinition parse(Element element, ParserContext parserContext) {
    
    
                ...
        // Obtain bean definitions for all relevant BeanPostProcessors.
        Set<BeanDefinitionHolder> processorDefinitions =
                AnnotationConfigUtils.registerAnnotationConfigProcessors(parserContext.getRegistry(), source);
 
        // Register component for the surrounding <context:annotation-config> element.
        CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
        parserContext.pushContainingComponent(compDefinition);
 
        // Nest the concrete beans in the surrounding component.
        for (BeanDefinitionHolder processorDefinition : processorDefinitions) {
    
    
            parserContext.registerComponent(new BeanComponentDefinition(processorDefinition));
        }
 
        // Finally register the composite component.
        parserContext.popAndRegisterContainingComponent();
 
        return null;
    }
 
}
 
 
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
            BeanDefinitionRegistry registry, Object source) {
    
    
 
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>(4);
 
        // Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
        if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
    
    
            RootBeanDefinition def = new RootBeanDefinition();
            try {
    
    
                ClassLoader cl = AnnotationConfigUtils.class.getClassLoader();
                def.setBeanClass(cl.loadClass(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME));
            }
            catch (ClassNotFoundException ex) {
    
    
                throw new IllegalStateException(
                        "Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
            }
            def.setSource(source);
            def.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            beanDefinitions.add(registerBeanPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
        }
 
        // Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
        if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
    
    
            RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
            def.setSource(source);
            def.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            beanDefinitions.add(registerBeanPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
        }
 
        if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
    
    
            RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
            def.setSource(source);
            def.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            beanDefinitions.add(registerBeanPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
        }
 
        if (!registry.containsBeanDefinition(REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
    
    
            RootBeanDefinition def = new RootBeanDefinition(RequiredAnnotationBeanPostProcessor.class);
            def.setSource(source);
            def.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            beanDefinitions.add(registerBeanPostProcessor(registry, def, REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
        }
 
        return beanDefinitions;
    }
 

主要注册常用的:
RequiredAnnotationBeanPostProcessor
AutowiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor
平时我们使用autowired或者required之所以能生效,就是因为这个自动注入ioc已经。

猜你喜欢

转载自blog.csdn.net/hancoder/article/details/111413369