Spring事务控制

Posted 蚂蚁小哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring事务控制相关的知识,希望对你有一定的参考价值。

一:事务介绍

1:什么是事务

  事务管理是程序开发中必不可少的技术,用来保证数据的完整性和一致性,它们被当作一个单独的工作单元。这些动作要么全部完成,要不就完全不起作用。就例如我们在完成转账操作的时候,转出用户的金额减少、转入用户的金额增加,这就完成了一个完整的操作,提交事务然后完成转账,但是如果有一方操作失败则全部失败,回滚数据。这就是事务,保证一组操作要么全部成功,要么全部失败。

2:事务四大属性

① 原子性(atomicity):
  事务是一个原子操作,有一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用
② 一致性(consistency):
  一旦所有事务动作完成,事务就被提交。数据和资源就处于一种满足业务规则的一致性状态中
③ 隔离性(isolation):
  可能有许多事务会同时处理相同的数据,因此每个事物都应该与其他事务隔离开来,防止数据损坏
④ 持久性(durability):
  一旦事务完成被提交,它对数据库中的数据操作是永久性的,反之发生什么系统错误,它的结果都不应该受到影响。通常情况下,事务的结果被写到持久化存储器中

 二:Spring中的事务管理分类

  作为企业级的应用程序框架,Spring在不同的事务管理API之上定义了一个抽象层。而开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring在分类上即支持编程式事务管理也支持声明式事务。

1:声明式事务管理

  Spring 的声明式事务管理在底层是建立在 AOP 的基础之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
  声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过等价的基于注解的方式),便可以将事务规则应用到业务逻辑中。因为事务管理本身就是一个典型的横切逻辑,正是 AOP 的用武之地。Spring 开发团队也意识到了这一点,为声明式事务提供了简单而强大的支持。

2:编程式事务管理

   所谓编程式事务指的是通过编码方式实现事务,允许用户在代码中精确定义事务的边界。即类似于JDBC编程实现事务管理。管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。

三:Spring事务管理接口

Spring 事务管理为我们提供了三个高层抽象的接口,分别是PlatformTransactionManagerTransactionDefinitionTransactionStatus

 1:PlatformTransactionManager事务管理器

  Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,Spring框架并不直接管理事务,而是通过这个接口为不同的持久层框架提供了不同的PlatformTransactionManager接口实现类,也就是将事务管理的职责委托给Hibernate或者iBatis等持久化框架的事务来实现。

①:org.springframework.jdbc.datasource.DataSourceTransactionManager:
  使用JDBC或者iBatis进行持久化数据时使用 ②:org.springframework.orm.hibernate5.HibernateTransactionManager:
  使用hibernate5版本进行持久化数据时使用 ③:org.springframework.orm.jpa.JpaTransactionManager:
  使用JPA进行持久化数据时使用 ④:org.springframework.jdo.JdoTransactionManager:
  当持久化机制是jdo时使用 ⑤:org.springframework.transaction.jta.JtaTransactionManager:
  使用一个JTA实现来管理事务,在一个事务跨越多个资源时必须使用
//接口源码
public
interface PlatformTransactionManager extends TransactionManager { //事务管理器通过TransactionDefinition,获得“事务状态”,从而管理事务 TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException; //根据状态提交 void commit(TransactionStatus var1) throws TransactionException; //根据状态回滚 void rollback(TransactionStatus var1) throws TransactionException; }

2:TransactionDefinition定义事务基本属性

  org.springframework.transaction.TransactionDefinition接口用于定义一个事务,它定义了Spring事务管理的五大属性:隔离级别传播行为是否只读事务超时回滚规则

 ①:隔离级别

  什么是事务的隔离级别?我们知道,隔离性是事务的四大特性之一,表示多个并发事务之间的数据要相互隔离,隔离级别就是用来描述并发事务之间隔离程度的大小
在并发事务之间如果不考虑隔离性,会引发如下安全性问题:
脏读 :一个事务读到了另一个事务的未提交的数据
不可重复读 :一个事务读到了另一个事务已经提交的 update 的数据导致多次查询结果不一致
幻读 :一个事务读到了另一个事务已经提交的 insert 的数据导致多次查询结果不一致

在 Spring 事务管理中,为我们定义了如下的隔离级别:
①:ISOLATION_DEFAULT:
    使用数据库默认的隔离级别
②:ISOLATION_READ_UNCOMMITTED:
    最低的隔离级别,允许读取已改变而没有提交的数据,可能会导致脏读、幻读或不可重复读
③:ISOLATION_READ_COMMITTED:
    允许读取事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
④:ISOLATION_REPEATABLE_READ:
    对同一字段的多次读取结果都是一致的,除非数据事务本身改变,可以阻止脏读和不可重复读,但幻读仍有可能发生
⑤:ISOLATION_SERIALIZABLE:
    最高的隔离级别,完全服从ACID的隔离级别,确保不发生脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的

②:传播行为

   Spring事务传播机制规定了事务方法和事务方法发生嵌套调用时事务如何进行传播,即协调已经有事务标识的方法之间的发生调用时的事务上下文的规则

Spring定义了七种传播行为,这里以方法A和方法B发生嵌套调用时如何传播事务为例说明:
①:PROPAGATION_REQUIRED:
    A如果有事务,B将使用该事务;如果A没有事务,B将创建一个新的事务
②:PROPAGATION_SUPPORTS:
    A如果有事务,B将使用该事务;如果A没有事务,B将以非事务执行
③:PROPAGATION_MANDATORY:
    A如果有事务,B将使用该事务;如果A没有事务,B将抛异常
④:PROPAGATION_REQUIRES_NEW:
    A如果有事务,将A的事务挂起,B创建一个新的事务;如果A没有事务,B创建一个新的事务
⑤:PROPAGATION_NOT_SUPPORTED:
    A如果有事务,将A的事务挂起,B将以非事务执行;如果A没有事务,B将以非事务执行
⑥:PROPAGATION_NEVER:
    A如果有事务,B将抛异常;A如果没有事务,B将以非事务执行
⑦:PROPAGATION_NESTED:
    A和B底层采用保存点机制,形成嵌套事务

③:是否只读

  如果将事务设置为只读,表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务

④:事务超时

  事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。在 TransactionDefinition 中以 int 的值来表示超时时间,默认值是-1,其单位是秒

⑤:回滚操作

  回滚规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚

 ⑥:源码展示

技术图片
public interface TransactionDefinition {
   //事务传播行为类型:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。
    int PROPAGATION_REQUIRED = 0;
    //事务传播行为类型:支持当前事务,如果当前没有事务,就以非事务方式执行。 
    int PROPAGATION_SUPPORTS = 1;
    //事务传播行为类型:当前如果有事务,Spring就会使用该事务;否则会抛出异常
    int PROPAGATION_MANDATORY = 2;
    //事务传播行为类型:新建事务,如果当前存在事务,把当前事务挂起。 
    int PROPAGATION_REQUIRES_NEW = 3;
    //事务传播行为类型:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 
    int PROPAGATION_NOT_SUPPORTED = 4;
    //事务传播行为类型:即使当前有事务,Spring也会在非事务环境下执行。如果当前有事务,则抛出异常
    int PROPAGATION_NEVER = 5;
    //事务传播行为类型:如果当前存在事务,则在嵌套事务内执行。
    int PROPAGATION_NESTED = 6;

   //隔离级别:默认的隔离级别(对mysql数据库来说就是ISOLATION_ READ_COMMITTED,可以重复读)
    int ISOLATION_DEFAULT = -1;
    //隔离级别:读未提交(最低)
    int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
    //隔离级别:读提交
    int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
    //隔离级别:可重复度
    int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
    //隔离级别:序列化操作(最高)
    int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;

    //默认事务的超时时间
    int TIMEOUT_DEFAULT = -1;

    //获取事务的传播行为
    int getPropagationBehavior();
    //获取事务的隔离级别
    int getIsolationLevel();
    //获取超时时间
    int getTimeout();
    
    //是否只读
    boolean isReadOnly();
    //事务名称
    String getName();
}
TransactionDefinition源码

 3:TransactionStatus接口源码

   org.springframework.transaction.TransactionStatus接口用来记录事务的状态,该接口定义了一组方法,用来获取或判断事务的相应状态信息

public interface TransactionStatus extends SavepointManager, Flushable {
    boolean isNewTransaction();// 是否是新的事物
    boolean hasSavepoint();// 是否有恢复点
    void setRollbackOnly();// 设置为只回滚
    boolean isRollbackOnly();// 是否为只回滚
    void flush();// 刷新
    boolean isCompleted();// 是否已完成
}
//注:在高版本如5.2.6会发现没这么多方法,其实是被封装到父类了

四:Spring事务管理实现方式

  Spring 事务管理有两种方式:编程式事务管理、声明式事务管理。
  编程式事务管理通过TransactionTemplate手动管理事务,在实际应用中很少使用;
  声明式事务管理有三种实现方式:基于TransactionProxyFactoryBean的方式、基于AspectJ的XML方式、基于注解的方式。
  在声明式事务管理的三种实现方式中,基于TransactionProxyFactoryBean的方式需要为每个进行事务管理的类配置一个TransactionProxyFactoryBean对象进行增强,所以开发中很少使用;基于AspectJ的XML方式一旦在XML文件中配置好后,不需要修改源代码,所以开发中经常使用;基于注解的方式开发较为简单,配置好后只需要在事务类上或方法上添加@Transaction注解即可,所以开发中也经常使用
  我们以学生转账为例来学习这基于AspectJ的XML方式、基于注解的方式不同的实现方式

n:不带任何的事务且带转账普通代码   建表语句

技术图片
public class Student {
    private int sid;            //主键id
    private String sname;       //姓名
    private String ssex;        //性别
    private int sage;           //年龄
    private double scredit;     //学分
    private double smoney;      //零花钱
    private String saddress;    //住址
    private String senrol;      //入学时间
    //因为简单的单表CRUD就不涉及到外键
    //private int fid;            //外键 连接家庭表信息学生对家庭,一对一
    //private int tid;            //外键 连接老师信息 学生对老师,一对一
    //创建构造器/get/set/toString就不展示了
}
实体类
技术图片
    <dependencies>
        <!--Spring核心包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <!--Spring的操作数据库坐标-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <!--Spring的事务-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <!--用于解析切面表达式坐标   只有用到AOP切面就必须导入-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
        <!--Spring测试坐标-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <!--Mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.32</version>
        </dependency>
        <!--测试包-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
pom.xml
技术图片
#####StudentDao接口

/**
 * Student接口数据操作
 * @author ant
 */
public interface StudentDao {
    //根据姓名查询
    Student findByName(String name);
    //更新学生
   void update(Student student);
}

+++++++++++++++++++++++++++++++++++++++++

#####StudentDaoImpl实现类
/**
 * Student数据操作实现类
 * @author ant
 */
public class StudentDaoImpl extends JdbcDaoSupport implements StudentDao {
    //根据姓名查询学生
    public Student findByName(String name) {
        List<Student> students = getJdbcTemplate().query("select * from student where sname=?",new BeanPropertyRowMapper<Student>(Student.class) , name);
        if (students == null || students.size() < 0) {
            return null;
        }
        return students.get(0);
    }
    //更新学生零花钱Money
    public void update(Student student) {
        String sql = "update student set smoney=? where sid=?";
        int isNo = getJdbcTemplate().update(sql, student.getSmoney(), student.getSid());
    }
}
dao接口及实现类
技术图片
#####StudentService接口
/**
 * Student业务处理接口
 * @author ant
 */
public interface StudentService {
    //转账
    void transfer(String currentName,String targetName,double money);
}

++++++++++++++++++++++++++++++++++++++++++

#####StudentServiceImpl实现类

/**
 * Student业务处理实现类
 * @author ant
 */
public class StudentServiceImpl implements StudentService {
    //聚合StudentDao 通过后面set后期注入
    private StudentDao studentDao;
    public void setStudentDao(StudentDao studentDao) {
        this.studentDao = studentDao;
    }

    //转账
    public void transfer(String currentName, String targetName, double money) {
        
        /*为了演示 我就不做那么细致的数据判断等等操作*/
        //①:查询学生
        Student currentStudent = studentDao.findByName(currentName);
        Student targetStudent = studentDao.findByName(targetName);
        //打印最初结果
        System.out.println("转账前: 当前学生姓名和余额:" + currentStudent.getSname() + "  " + currentStudent.getSmoney());
        System.out.println("转账前: 目标学生姓名和余额:" + targetStudent.getSname() + "  " + targetStudent.getSmoney());
        //②:开始转账
        currentStudent.setSmoney(currentStudent.getSmoney() - money);
        targetStudent.setSmoney(targetStudent.getSmoney() + money);

        //③:调用更新
        studentDao.update(currentStudent);
//        int a=1/0;
        studentDao.update(targetStudent);

        //打印最终结果
        Student currentStudentEnd = studentDao.findByName(currentName);
        Student targetStudentEnd = studentDao.findByName(targetName);
        System.out.println("转账后: 当前学生姓名和余额:" + currentStudentEnd.getSname() + "  " + currentStudentEnd.getSmoney());
        System.out.println("转账后: 目标学生姓名和余额:" + targetStudentEnd.getSname() + "  " + targetStudentEnd.getSmoney());
    }
}
service接口及实现类
技术图片
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
         http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd">
    <!--开启注解扫描-->
    <context:component-scan base-package="cn.xw"></context:component-scan>
    <!--DriverManagerDataSource放入容器-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql:///demo_school"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123"></property>
    </bean>
    <!--JdbcTemplate放入容器-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--StudentDao放入容器-->
    <bean id="studentDao" class="cn.xw.dao.impl.StudentDaoImpl">
       <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>
    <!--StudentService放入容器-->
    <bean id="studentService" class="cn.xw.service.impl.StudentServiceImpl">
        <property name="studentDao" ref="studentDao"></property>
    </bean>
</beans>
applicationContext.xml
技术图片
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class Client {
    @Autowired
    @Qualifier(value="studentService")
    private StudentService ss;

    @Test
    public void transfer() {
        ss.transfer("王生安","李鑫灏",10);
    }
}
测试类

   在写完这个不带事务的代码大家会发现,如果学过AOP,使用AOP手写事务也是可以的,这个我再前一篇文章介绍过AOP的使用,原生写法可以使用普通代码控制、JDK动态代理和Cglib三种方式来控制事务,还有使用AOP来通过前置和后置通知来控制事务。但是下面我讲为大家介绍一下Spring为我们提供的专门事务管理。

1:基于AspectJ的XML方式完成事务

   基于AspectJ的方式其实主要利用了AOP切面的原理,但是Spring为我们封装的事务更强大更灵活,我们就通过更改上面写好的代码来达到事务控制的效果

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
         http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd">
    <!--开启注解扫描-->
    <context:component-scan base-package="cn.xw"></context:component-scan>
    <!--DriverManagerDataSource放入容器-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql:///demo_school"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123"></property>
    </bean>
    <!--JdbcTemplate放入容器-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--StudentDao放入容器-->
    <bean id="studentDao" class="cn.xw.dao.impl.StudentDaoImpl">
       <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>
    <!--StudentService放入容器-->
    <bean id="studentService" class="cn.xw.service.impl.StudentServiceImpl">
        <property name="studentDao" ref="studentDao"></property>
    </bean>

<!--上面代码不用修改,主要添加下面一些代码--> <!-- spring中基于XML的声明式事务控制配置步骤 1、配置事务管理器 2、配置事务的通知 此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的 使用tx:advice标签配置事务通知 属性: id:给事务通知起一个唯一标识 transaction-manager:给事务通知提供一个事务管理器引用 3、配置AOP中的通用切入点表达式 4、建立事务通知和切入点表达式的对应关系 5、配置事务的属性 是在事务的通知tx:advice标签的内部 --> <!--++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--> <!--配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!--配置事务通知--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!--配置事务属性 isolation: 用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。 propagation: 用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。 read-only: 用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。 timeout: 用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。 rollback-for: 用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。 no-rollback-for: 用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。 --> <!--这下面2行介绍 name="*": propagation="REQUIRED":A如果有事务,B将使用该事务;如果A没有事务,B将创建一个新的事务, read-only="false":表示事务可读可写 name="find*": propagation="SUPPORTS":A如果有事务,B将使用该事务;如果A没有事务,B将以非事务执行 read-only="true":表示这个事务只读取数据但不更新数据 所以第一个覆盖全部,但第二个覆盖了全部查询操作 --> <tx:method name="*" propagation="REQUIRED" read-only="false"/> <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method> </tx:attributes> </tx:advice> <!--配置AOP切入点表达式--> <aop:config> <aop:pointcut id="pt1" expression="execution(* cn.xw.service.impl.*.*(..))"/> <!--引用上面的事务通知--> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor> </aop:config> </beans>

只要配这一段代码即可达到事务控制的效果

2:基于注解方式完成事务

   注解方式比基于AspectJ的方法要简单的多,可以不用导入aspectjweaver坐标,因为没用到解析切入点表达式,我就根据第一种没有事务的代码进行简单的代码改造!

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
         http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd">
    <!--开启注解扫描-->
    <context:component-scan base-package="cn.xw"></context:component-scan>
    <!--DriverManagerDataSource放入容器-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql:///demo_school"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123"></property>
    </bean>
    <!--JdbcTemplate放入容器-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--StudentDao放入容器-->
    <bean id="studentDao" class="cn.xw.dao.impl.StudentDaoImpl">
       <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>
    <!--StudentService放入容器-->
    <bean id="studentService" class="cn.xw.service.impl.StudentServiceImpl">
        <property name="studentDao" ref="studentDao"></property>
    </bean>

<!--添加下面2段代码--> <!--配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!--开启注解事务管理 内部添加一个事务管理器--> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven> </beans>
######逻辑Service实现类

//配置注解@Transactional,这个注解针对该方法里面的全部方法都是这个配置
@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
public class StudentServiceImpl implements StudentService {
    //聚合StudentDao 通过后面set后期注入
    private StudentDao studentDao;
    public void setStudentDao(StudentDao studentDao) {
        this.studentDao = studentDao;
    }
    //转账
    //覆盖开头设置的@Transactional注解,这里因为要操作数据和需要事务支持 就重写配置了一下
    @Transactional(readOnly = false,propagation = Propagation.REQUIRED)
    public void transfer(String currentName, String targetName, double money) {
       ......省略转账逻辑代码
    }
}

这种缺点是,如果该实现的业务类里面有很多增删改操作都要事务,那么都要重写配置一下事务的支持,不像xml方式,一次配置后面都不需要更改,如果使用纯注解则在主配置文件上添加:

@Configuration
@EnableTransactionManagement
public class SpringTxConfiguration {
//里面配置数据源,配置 JdbcTemplate,配置事务管理器。在之前的步骤已经写过了。
}

三:使用纯注解完成Spring内置事务

技术图片
public class Student {
    private int sid;            //主键id
    private String sname;       //姓名
    private String ssex;        //性别
    private int sage;           //年龄
    private double scredit;     //学分
    private double smoney;      //零花钱
    private String saddress;    //住址
    private String senrol;      //入学时间
    //因为简单的单表CRUD就不涉及到外键
    //private int fid;            //外键 连接家庭表信息学生对家庭,一对一
    //private int tid;            //外键 连接老师信息 学生对老师,一对一
    //创建构造器/get/set/toString就不展示了
}
实体类
技术图片
#####StudentDao接口

/**
 * 学生数据操作Dao接口
 * @author ant
 */
public interface StudentDao {
    //根据姓名查询
    Student findByName(String name);
    //修改金额
    void update(Student student);
}

+++++++++++++++++++++++++++++++++++++++++
#####StudentDaoImpl实现类
/**
 * 学生数据操作Dao实现类
 * @author ant
 */
@Repository("studentDao")
public class StudentDaoImpl implements StudentDao {
    @Autowired
    @Qualifier("jdbcTemplate")
    private JdbcTemplate jdbcTemplate;
    //根据姓名查询学生
    public Student findByName(String name) {
        String sql="select * from student where sname=?";
        Student student = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Student>(Student.class), name);
        return student;
    }
    //更改money金额
    public void update(Student student) {
        String sql="update student set smoney=? where sid=?";
        jdbcTemplate.update(sql,student.getSmoney(),student.getSid());
    }
}
dao接口及实现类
技术图片
#####StudentService接口
/**
 * 学生业务层Service 接口
 * @author ant
 */
public interface StudentService {
    //转账
    void transfer(String currentName, String targetName, double money);
}

++++++++++++++++++++++++++++++++++++++++++

#####StudentServiceImpl接口实现类

/**
 * 业务接口实现类 学生ServiceStudent
 * @author ant
 */
@Service("studentService")
@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
public class StudentServiceImpl implements StudentService {

    @Autowired
    @Qualifier("studentDao")
    private StudentDao studentDao;
    @Transactional(readOnly = false,propagation = Propagation.REQUIRED)
    public void transfer(String currentName, String targetName, double money) {
        /*为了演示 我就不做那么细致的数据判断等等操作*/
        //①:查询学生
        Student currentStudent = studentDao.findByName(currentName);
        Student targetStudent = studentDao.findByName(targetName);
        //打印最初结果
        System.out.println("转账前: 当前学生姓名和余额:" + currentStudent.getSname() + "  " + currentStudent.getSmoney());
        System.out.println("转账前: 目标学生姓名和余额:" + targetStudent.getSname() + "  " + targetStudent.getSmoney());
        //②:开始转账
        currentStudent.setSmoney(currentStudent.getSmoney() - money);
        targetStudent.setSmoney(targetStudent.getSmoney() + money);
        //③:调用更新
        studentDao.update(currentStudent);
        studentDao.update(targetStudent);
        //打印最终结果
        Student currentStudentEnd = studentDao.findByName(currentName);
        Student targetStudentEnd = studentDao.findByName(targetName);
        System.out.println("转账后: 当前学生姓名和余额:" + currentStudentEnd.getSname() + "  " + currentStudentEnd.getSmoney());
        System.out.println("转账后: 目标学生姓名和余额:" + targetStudentEnd.getSname() + "  " + targetStudentEnd.getSmoney());
    }
}
service接口及实现类
技术图片
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/demo_school
jdbc.user=root
jdbc.password=123
db.properties
技术图片
/**
 * 本类为了使操作方法的时候打印日志准备,在这里我使用了环绕通知,因为注解使用环绕通知可以避免顺序问题
 */
@Component("springAopLog")
@Aspect     //设置这个类为通知类
public class SpringAopLog {

    @Pointcut(value = "execution(* cn.xw.service.impl.*.*(..))")
    public void pointcut() {
    }
    //@Before("pointcut()")
    public void beforeLog() {
        System.out.println(Thread.currentThread().getName() + " log:方法执行开始");
    }

    //@AfterReturning("pointcut()")
    public void afterReturnLog() {
        System.out.println(Thread.currentThread().getName() + " log:方法执行成功");
    }

    //@AfterThrowing("pointcut()")
    public void afterThrowingLog() {
        System.out.println(Thread.currentThread().getName() + " log:方法执行异常");
    }

    //@After("pointcut()")
    public void afterLog() {
        System.out.println(Thread.currentThread().getName() + " log:完成方法操作");
    }

    @Around("pointcut()")
    public Object aroundPrint(ProceedingJoinPoint pp) {
        Object obj = null;
        try {
            beforeLog();
            Object[] args = pp.getArgs();
            obj = pp.proceed(args);
            afterReturnLog();
        } catch (Throwable e) {
            afterThrowingLog();
            throw new RuntimeException("运行错误");
        } finally {
            afterLog();
        }
        return obj;
    }
}
config.SpringAopLog AOP通知类
技术图片
/**
 * 该类用于获取数据库连接的数据源
 */
@PropertySource(value ="classpath:db.properties" )
public class SpringDataSource {

    @Value(value = "${jdbc.driver}")
    private String driver;
    @Value(value = "${jdbc.url}")
    private String url;
    @Value(value = "${jdbc.user}")
    private String user;
    @Value(value = "${jdbc.password}")
    private String password;

    /**
     * 在容器中创建了一个 DriverManagerDataSource的数据源
     * @return
     */
    @Bean(value="dataSource")
    public DataSource createDataSource(){
        DriverManagerDataSource dataSource=new DriverManagerDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(user);
        dataSource.setPassword(password);
        return dataSource;
    }
}
config.SpringDataSource 连接数据库的数据源类编写
技术图片
/**
 * 主配置文件
 */
@Configuration
@ComponentScan(basePackages = {"cn.xw"})
@Import(value = {SpringDataSource.class})
@EnableTransactionManagement //开启注解事务支持
@EnableAspectJAutoProxy //开启切面通知支持
public class SpringConfig {

    /**
     * 把JdbcTemplate放入容器
     * @param dataSource
     * @return
     */
    @Bean("jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(@Qualifier(value="dataSource") DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }

    /**
     * 配置事务管理器
     * @param dataSource
     * @return
     */
    @Bean("transactionManager")
    public DataSourceTransactionManager createTransactionManager(@Qualifier("dataSource") DataSource dataSource){
        DataSourceTransactionManager transactionManager=new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}
config.SpringConfig Spring配置文件的主配置类
技术图片
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class Client {
    @Autowired
    @Qualifier("studentService")
    private StudentService ss;
    @Test
    public void transfer(){
        ss.transfer("王生安","李鑫灏",10);
    }
}
测试类
main log:方法执行开始
转账前: 当前学生姓名和余额:王生安  80.1
转账前: 目标学生姓名和余额:李鑫灏  322.9
转账后: 当前学生姓名和余额:王生安  70.1
转账后: 目标学生姓名和余额:李鑫灏  332.9
main log:方法执行成功
main log:完成方法操作

总结:总的来说使用AspectJ加XML要方便很多,一次配置后面就一直使用

以上是关于Spring事务控制的主要内容,如果未能解决你的问题,请参考以下文章

阶段3 2.Spring_10.Spring中事务控制_4 spring中事务控制的一组API

spring 事务控制

阶段3 2.Spring_10.Spring中事务控制_9 spring编程式事务控制1-了解

Spring针对事务处理提供哪两种事务编程模式。

Spring AOP 应用:Spring 声明式事务的支持

关于Spring事务控制方面的问题,就是不在service层做控制