Spring有一个基于AOP之上的事务管理模块,这个模块能够帮助我们在逻辑层中很方便的控制数据库的事务。在此之前我们介绍了Spring对JDBC的模板支持 以及 面向切面的Spring,而且也用Spring的AOP编写了一个简单的切面类用于控制事务,在此对其中一些相同的东西就不再赘述了。所以本文是硬文,就让我们单刀直入地学习如何使用Spring的事务管理模块为我们管理事务吧。

首先配置依赖的jar包,我这里使用的是maven工程,所以配置pom.xml文件内容如下:

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.14.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.3.12.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.44</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.13</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.13</version>
        </dependency>
    </dependencies>

然后就是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:p="http://www.springframework.org/schema/p"
       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.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       ">

    <!-- 配置Spring支持注解 -->
    <context:annotation-config/>
    <context:component-scan base-package="org.zero01"/>
    <!-- 配置Spring支持AOP -->
    <aop:aspectj-autoproxy/>

    <!-- 配置Spring支持注解形式的事务管理 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <!-- 配置Spring的事务管理员 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource"/>

    <!-- 配置数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
          p:driverClass="com.mysql.jdbc.Driver"
          p:jdbcUrl="jdbc:mysql:///school"
          p:user="root"
          p:password="your_password"
          p:maxPoolSize="10"
          p:minPoolSize="1"
          p:loginTimeout="2000"
    />

</beans>

完成以上配置后,我们就可以使用Spring的事务管理了,最简单的使用方式就是在需要被管理的类里写上 @Transactional 注解即可,下面我们来测试一下配置是否正常:

逻辑层接口:

package org.zero01.service;

import org.zero01.pojo.Student;

import java.util.List;

public interface School {

    public int enterSchool(Student student);

    public int deleteStudentData(int sid);

    public Student searchStudentData(int sid);

    public List<Student> searchStudentsData();

    public int alterStudentData(Student student);

}

写一个测试的实现类,用于测试 @Transactional 注解是否能成功进行事务管理的配置:

package org.zero01.service;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.zero01.pojo.Student;

import java.util.List;

@Service("test")
// 配置这个注解后,该类就受到Spring的事务管理了
@Transactional
public class TestService implements School {
    public int enterSchool(Student student) {
        return 0;
    }

    public int deleteStudentData(int sid) {
        return 0;
    }

    public Student searchStudentData(int sid) {
        return null;
    }

    public List<Student> searchStudentsData() {
        return null;
    }

    public int alterStudentData(Student student) {
        return 0;
    }
}

测试代码如下:

package org.zero01.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.zero01.service.School;

public class TestTran {

    public static void main(String[] args) {
        ApplicationContext app = new ClassPathXmlApplicationContext("app.xml");
        School testService = (School) app.getBean("test");

        // 查看是否是代理对象
        System.out.println(testService.getClass().getName());
    }
}

运行结果:

com.sun.proxy.$Proxy12

从打印结果中可以看到,拿出来的是代理对象,那就代表配置成功了,因为事务管理是基于AOP的而Spring的AOP又是基于动态代理的,拿到的是代理对象就证明已经代理上了。

以上使用到的 @Transactional 注解不仅可以写在类名上,还可以写在方法上,写在类上表示控制整个类,写在方法上表示只控制某个方法。因为有时候有些方法比较特殊,希望有不一样的管理方式,这就涉及到以下要提到的Spring的7种事务传播行为类型了,使用不同的事务传播行为类型能够进行不同的事务控制,下面就简单介绍一下这7种事务传播行为类型的使用方式:

  1. PROPAGATION_REQUIRED,这个类型能够在当前没有事务的情况下,新建一个事务,如果已经存在一个事务中,就加入到这个事务中。简单来说就是设置成这个级别时,会为每一个被调用的方法创建一个逻辑事务域。如果前面的方法已经创建了事务,那么后面的方法支持当前的事务,如果当前没有事务会重新建立事务。这是最常见的选择,也是默认的类型,示例:
@Service("test")
// 配置这个注解后,该类就受到Spring的事务管理了
@Transactional
public class TestService implements School {

    @Transactional(propagation = Propagation.REQUIRED)
    public int enterSchool(Student student) {
        return 0;
    }
    ...
  1. PROPAGATION_SUPPORTS,此类型支持当前事务,如果当前没有事务,就以非事务方式执行,非事务也就是说去除Spring的事务管理以默认的事务形式进行,示例:
@Service("test")
// 配置这个注解后,该类就受到Spring的事务管理了
@Transactional
public class TestService implements School {

    @Transactional(propagation = Propagation.SUPPORTS)
    public int enterSchool(Student student) {
        return 0;
    }
    ...
  1. PROPAGATION_MANDATORY,该类型使用当前的事务,如果当前没有事务,就抛出异常,也就是说这种类型必须要有一个当前事务,没有的话也不会以非事务的方式执行,更不会新建事务,而是会抛出异常,示例:
@Service("test")
// 配置这个注解后,该类就受到Spring的事务管理了
@Transactional
public class TestService implements School {

    @Transactional(propagation = Propagation.MANDATORY)
    public int enterSchool(Student student) {
        return 0;
    }
    ...
  1. PROPAGATION_REQUIRES_NEW,这类型会新建事务,如果当前存在事务,把当前事务挂起,也就是说非要用新的,当前的不用,示例:
@Service("test")
// 配置这个注解后,该类就受到Spring的事务管理了
@Transactional
public class TestService implements School {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public int enterSchool(Student student) {
        return 0;
    }
    ...
  1. PROPAGATION_NOT_SUPPORTED,此类型以非事务的方式执行操作,如果当前存在事务,就把当前事务挂起。示例:
@Service("test")
// 配置这个注解后,该类就受到Spring的事务管理了
@Transactional
public class TestService implements School {

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public int enterSchool(Student student) {
        return 0;
    }
    ...
  1. PROPAGATION_NEVER,以非事务方式执行,如果当前存在事务,则抛出异常。示例:
@Service("test")
// 配置这个注解后,该类就受到Spring的事务管理了
@Transactional
public class TestService implements School {

    @Transactional(propagation = Propagation.NEVER)
    public int enterSchool(Student student) {
        return 0;
    }
    ...
  1. PROPAGATION_NESTED,如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类 似的操作。示例:
@Service("test")
// 配置这个注解后,该类就受到Spring的事务管理了
@Transactional
public class TestService implements School {

    @Transactional(propagation = Propagation.NESTED)
    public int enterSchool(Student student) {
        return 0;
    }
    ...

以上所介绍的这个类型相比于其他六中类型有些特殊,因为它可以进行事务嵌套,当嵌套事务后就分为一级事务和二级事务了,二级事务是一级事务的子事务。我们来举个例子吧,例如我现在有一个需求:

  • 小明有三个游戏账户,小红有两个游戏账户,要求从小明的任意一个账户中减去1000金币,添加给小红的任意一个账户,类似于转账。

像这种类型的需求就可以使用嵌套事务类型,我们可以把从小明账户里减去1000金币,往小红账户里增加1000金币这一环节作为一级事务。而从小明的三个账户里任意一个账户里减少金币和从小红的两个账户里任意一个账户增加金币作为二级事务。

说明一下嵌套事务的回滚与提交:

  • 当二级事务被rollback时,一级事务不会随着二级事务被rollback,因为二级事务的rollback只针对自己,但是当一级事务rollback时所有的二级事务都会rollback。什么时候这个一级事务会commit,什么时候会被rollback呢?我们主要看二级里面出现的情况,当所有的二级事务被commit了,并且一级事务没有失败的操作,那整个事务就算是一个成功的事务,这种情况整个事务会被commit。当任意一个二级事务没有被commit那整个事务就是失败的,整个事务会被roolback。虽然二级事务rollback不会直接带着一级事务也rollback,但是当一级事务要提交时发现有一个二级事务rollback了,那么一级事务就会rollback。

把以上提到的类型整理成表格,Spring的7种事务传播行为类型:

事务传播行为类型 说明
PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类 似的操作。

除了以上所介绍的事务传播行为类型之外,在Spring的事务管理中也支持设置事务隔离级别,事务隔离级别也是在 @Transactional 注解中进行配置,下面简单介绍一下它们的配置方式:

  1. ISOLATION_DEFAULT,这个隔离级别表示使用数据库默认的事务隔离级别。示例:
@Service("test")
// 配置这个注解后,该类就受到Spring的事务管理了
@Transactional
public class TestService implements School {

    @Transactional(isolation = Isolation.DEFAULT)
    public int enterSchool(Student student) {
        return 0;
    }
    ...
  1. ISOLATION_READ_UNCOMMITTED,这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻读。示例:
@Service("test")
// 配置这个注解后,该类就受到Spring的事务管理了
@Transactional
public class TestService implements School {

    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    public int enterSchool(Student student) {
        return 0;
    }
    ...
  1. ISOLATION_READ_COMMITTED,该隔离级别保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据,这是大多数数据库的默认事务隔离级别。示例:
@Service("test")
// 配置这个注解后,该类就受到Spring的事务管理了
@Transactional
public class TestService implements School {

    @Transactional(isolation = Isolation.READ_COMMITTED)
    public int enterSchool(Student student) {
        return 0;
    }
    ...
  1. ISOLATION_REPEATABLE_READ,这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。示例:

    @Service("test")
    // 配置这个注解后,该类就受到Spring的事务管理了
    @Transactional
    public class TestService implements School {
    
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public int enterSchool(Student student) {
        return 0;
    }
    ...
  2. ISOLATION_SERIALIZABLE,这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻读。示例:

    @Service("test")
    // 配置这个注解后,该类就受到Spring的事务管理了
    @Transactional
    public class TestService implements School {
    
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public int enterSchool(Student student) {
        return 0;
    }
    ...

把以上提到的事务隔离级别整理成表格,Spring中的事务隔离级别:

隔离级别 说明
ISOLATION_DEFAULT 使用数据库默认的事务隔离级别
ISOLATION_READ_UNCOMMITTED 这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻读。
ISOLATION_READ_COMMITTED 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据
ISOLATION_REPEATABLE_READ 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻读。

除了以上最重要的事务传播行为类型以及事务隔离级别的配置之外,@Transactional 还有其他几个属性,下面介绍最后剩下的几个属性:

  1. readOnly ,这是一个只读模式的开关,当该属性的值为true时,表示开启只读,这时候无法往数据库里写入或修改数据,只能读取数据,默认值为false。示例:
@Service("test")
// 配置这个注解后,该类就受到Spring的事务管理了
@Transactional
public class TestService implements School {

    // 开启只读模式
    @Transactional(readOnly = true)
    public int enterSchool(Student student) {
        return 0;
    }
    ...
  1. timeout,从名称就可以知道这是用于配置事务超时时间的,单位为秒,默认值为-1也就是不开启超时时间。示例:
@Service("test")
// 配置这个注解后,该类就受到Spring的事务管理了
@Transactional
public class TestService implements School {

    // 设置事务超时时间为2秒
    @Transactional(timeout = 2)
    public int enterSchool(Student student) {
        return 0;
    }
    ...
  1. rollbackFor,该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。示例:
@Service("test")
// 配置这个注解后,该类就受到Spring的事务管理了
@Transactional
public class TestService implements School {

    // 配置单一异常类
    @Transactional(rollbackFor = NullPointerException.class)
    public int enterSchool(Student student) {
        return 0;
    }

    // 配置多个异常类
    @Transactional(rollbackFor = {NullPointerException.class, IndexOutOfBoundsException.class})
    public int deleteStudentData(int sid) {
        return 0;
    }
    ...
  1. rollbackForClassName,该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。示例:
@Service("test")
// 配置这个注解后,该类就受到Spring的事务管理了
@Transactional
public class TestService implements School {

    // 配置单一异常类
    @Transactional(rollbackForClassName = "RuntimeException")
    public int enterSchool(Student student) {
        return 0;
    }

    // 配置多个异常类
    @Transactional(rollbackForClassName = {"RuntimeException", "Exception"})
    public int deleteStudentData(int sid) {
        return 0;
    }
    ...
  1. noRollbackFor,该属性与rollbackFor相反,用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。示例:
@Service("test")
// 配置这个注解后,该类就受到Spring的事务管理了
@Transactional
public class TestService implements School {

    // 配置单一异常类
    @Transactional(noRollbackFor = NullPointerException.class)
    public int enterSchool(Student student) {
        return 0;
    }

    // 配置多个异常类
    @Transactional(noRollbackFor = {NullPointerException.class, IndexOutOfBoundsException.class})
    public int deleteStudentData(int sid) {
        return 0;
    }
    ...
  1. noRollbackForClassName,该属性与rollbackForClassName,用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。示例:
@Service("test")
// 配置这个注解后,该类就受到Spring的事务管理了
@Transactional
public class TestService implements School {

    // 配置单一异常类
    @Transactional(noRollbackForClassName = "RuntimeException")
    public int enterSchool(Student student) {
        return 0;
    }

    // 配置多个异常类
    @Transactional(noRollbackForClassName = {"RuntimeException", "Exception"})
    public int deleteStudentData(int sid) {
        return 0;
    }

以上我们就把 @Transactional 注解里所有的属性都介绍完了,把 @Transactional 注解的属性以及功能整理成表格如下:

参数名称 功能描述
readOnly 该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。
rollbackFor 该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。
rollbackForClassName 该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。
noRollbackFor 该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。
noRollbackForClassName 该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。
propagation 该属性用于设置事务的传播行为类型
isolation 该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,通常情况下不需要进行设置
timeout 该属性用于设置事务的超时秒数,默认值为-1表示永不超时

使用Spring的事务管理时注意的几点:

  1. @Transactional 只能被应用到public方法上, 对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能。
  2. 用 spring 事务管理器,由spring来负责数据库的打开,提交,回滚。默认遇到运行期例外 throw new RuntimeException(); 会回滚,即遇到不受检查(unchecked)的例外时回滚;而遇到需要捕获的例外 throw new Exception(); 不会回滚,即遇到受检查的例外时。所谓受检查的例外就是非运行时抛出的异常,编译器会检查到的异常叫受检查例外或说受检查异常。如下:

    @Service("test")
    @Transactional
    public class TestService implements School {
    
    // 由于是非运行时抛出的异常,所以不会回滚
    public int enterSchool(Student student) {
        throw new Exception();
    }
    
    // 运行时抛出的异常,则会回滚
    public int deleteStudentData(int sid) {
        throw new RuntimeException();
    }
    ...

在这种情况下需要我们指定方式来让事务回滚,想要所有异常都回滚,可以加上 @Transactional( rollbackFor={Exception.class,其它异常}) 。如果想要发生非运行时异常不回滚,则加上: @Transactional(notRollbackFor=RunTimeException.class)
如下示例:

// 指定回滚,遇到异常Exception时回滚
@Transactional(rollbackFor = Exception.class) 
public int enterSchool(Student student) {
   throw new Exception();
}

// 指定不回滚,即便遇到运行期例外(运行时异常)也不会回滚
@Transactional(noRollbackFor = Exception.class)
public int deleteStudentData(int sid) {
    throw new RuntimeException();
}
  1. @Transactional 注解应该只被应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。

  2. @Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。然而,请注意仅仅 @Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据,能够被可以识别 @Transactional 注解和上述的配置适当的具有事务行为的beans所使用。上面的例子中,其实正是 元素的出现 开启 了事务行为。

  3. Spring团队的建议是你在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。你当然可以在接口上使用 @Transactional 注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是不能继承的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。因此,请接受Spring团队的建议并且在具体的类上使用 @Transactional 注解。

以上我们已经介绍完了Spring基本的事务管理,下面我们来将之前基于AOP编写的代码重新使用JdbcTemplate + Spring的事务管理进行改造:

表格字段的封装类如下:

package org.zero01.pojo;

import org.springframework.stereotype.Component;

@Component("stu")
public class Student {

    private int sid;
    private String sname;
    private int age;
    private String sex;
    private String address;

    public int getSid() {
        return sid;
    }

    public void setSid(int sid) {
        this.sid = sid;
    }

    public String getSname() {
        return sname;
    }

    public void setSname(String sname) {
        this.sname = sname;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

package org.zero01.pojo;

import java.util.Date;

public class StudentLog {

    private int log_id;
    private int sid;
    private String sname;
    private int age;
    private String sex;
    private String address;
    private String operation_type;
    private Date log_time;

    public int getLog_id() {
        return log_id;
    }

    public void setLog_id(int log_id) {
        this.log_id = log_id;
    }

    public int getSid() {
        return sid;
    }

    public void setSid(int sid) {
        this.sid = sid;
    }

    public String getSname() {
        return sname;
    }

    public void setSname(String sname) {
        this.sname = sname;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getOperation_type() {
        return operation_type;
    }

    public void setOperation_type(String operation_type) {
        this.operation_type = operation_type;
    }

    public Date getLog_time() {
        return log_time;
    }

    public void setLog_time(Date log_time) {
        this.log_time = log_time;
    }
}
  1. 各层接口:
package org.zero01.dao;

import org.zero01.pojo.Student;

import java.util.List;

public interface DAO {

    public int insert(Student student);

    public int delete(int sid);

    public Student selectById(int sid);

    public List<Student> selectAll();

    public int update(Student student);

}

package org.zero01.dao;

import org.zero01.pojo.StudentLog;

import java.util.List;

public interface LogDAO {

    public int insert(StudentLog studentLog);

    public int delete(int log_id);

    public List<StudentLog> selectAll();

    public int update(StudentLog studentLog);

}

package org.zero01.service;

import org.zero01.pojo.Student;

import java.util.List;

public interface School {

    public int enterSchool(Student student);

    public int deleteStudentData(int sid);

    public Student searchStudentData(int sid);

    public List<Student> searchStudentsData();

    public int alterStudentData(Student student);

}
  1. 数据层接口的实现类:
package org.zero01.dao;

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

import javax.sql.DataSource;
import java.util.List;

@Repository("stuDAO")
public class StudentDAO extends JdbcTemplate implements DAO {

    @Autowired
    public void setDataSource(DataSource dataSource) {
        super.setDataSource(dataSource);
    }

    /**
     * @Description: 添加学生数据
     * @Param: 表格的字段封装对象
     * @return: 返回插入行的id
     * @Author: 01
     * @Date: 2018/3/6
     */
    public int insert(Student student) {

        String sql = "INSERT INTO student(sname,age,sex,address) VALUES (?,?,?,?)";

        update(sql, student.getSname(), student.getAge(), student.getSex(), student.getAddress());

        int sid = queryForObject("SELECT LAST_INSERT_ID()", Integer.class);

        return sid;
    }

    /**
     * @Description: 删除某个学生数据
     * @Param: 要删除行的id
     * @return: 返回影响的行数
     * @Author: 01
     * @Date: 2018/3/6
     */
    public int delete(int sid) {

        String sql = "DELETE FROM student WHERE sid=?";

        return update(sql, sid);
    }

    /**
     * @Description: 按id查找某个学生的数据
     * @Param: 要查询行的id
     * @return: 返回查询出来的学生数据
     * @Author: 01
     * @Date: 2018/3/6
     */
    public Student selectById(int sid) {

        String sql = "SELECT * FROM student WHERE sid=?";

        return queryForObject(sql, new Object[]{sid}, new StudentMapper());
    }

    /**
     * @Description: 查询全部学生的数据
     * @return: 返回查询出来的数据集合
     * @Author: 01
     * @Date: 2018/3/6
     */
    public List<Student> selectAll() {

        String sql = "SELECT * FROM student";

        return query(sql, new StudentMapper());
    }

    /**
     * @Description: 修改某个学生的数据
     * @Param: 表格的字段封装对象
     * @return: 返回影响行数
     * @Author: 01
     * @Date: 2018/3/6
     */
    public int update(Student student) {

        String sql = "UPDATE student SET sname=?,age=?,sex=?,address=? WHERE sid=?";

        return update(sql, student.getSname(), student.getAge(), student.getSex(), student.getAddress(), student.getSid());
    }
}

package org.zero01.dao;

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

import javax.sql.DataSource;
import java.util.List;

@Repository("stuLogDAO")
public class StudentLogDAO extends JdbcTemplate implements LogDAO {

    @Autowired
    public void setDataSource(DataSource dataSource) {
        super.setDataSource(dataSource);
    }

    /**
     * @Description: 添加日志记录
     * @Param: 表格的字段封装对象
     * @return: 返回影响行数
     * @Author: 01
     * @Date: 2018/3/6
     */
    public int insert(StudentLog studentLog) {

        String sql;

        if (studentLog.getOperation_type().equals("selectAll")) {
            sql = "INSERT INTO studentlog(operation_type,log_time) VALUES ('selectAll',sysdate())";

            return update(sql);
        }

        sql = "INSERT INTO studentlog(sid,sname,age,sex,address,operation_type,log_time) VALUES (?,?,?,?,?,?,sysdate())";

        return update(sql, studentLog.getSid(), studentLog.getSname(), studentLog.getAge(),
                studentLog.getSex(), studentLog.getAddress(), studentLog.getOperation_type());
    }

    /**
     * @Description: 删除日志记录
     * @Param: 要删除行的id
     * @return: 返回影响行数
     * @Author: 01
     * @Date: 2018/3/6
     */
    public int delete(int log_id) {

        String sql = "DELETE FROM studentlog WHERE log_id=?";

        return update(sql, log_id);
    }

    /**
     * @Description: 查询全部日志记录
     * @return: 返回查询出来的数据集合
     * @Author: 01
     * @Date: 2018/3/6
     */
    public List<StudentLog> selectAll() {

        String sql = "SELECT * FROM studentlog";

        return query(sql, new StudentLogMapper());
    }

    /**
     * @Description: 修改某条日志记录
     * @Param: 表格的字段封装对象
     * @return: 返回影响行数
     * @Author: 01
     * @Date: 2018/3/6
     */
    public int update(StudentLog studentLog) {

        String sql = "UPDATE student SET sid=?,sname=?,age=?,sex=?,address=?,operation_type=?,log_time=? WHERE log_id=?";

        return update(sql, studentLog.getSid(), studentLog.getSname(), studentLog.getAge(), studentLog.getSex(),
                studentLog.getAddress(), studentLog.getOperation_type(), studentLog.getLog_time(), studentLog.getLog_id());
    }
}
  1. 映射类:
package org.zero01.dao;

import org.springframework.jdbc.core.RowMapper;
import org.zero01.pojo.Student;

import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @program: spring-transaction
 * @description: 配置student表格字段与Student对象属性的映射
 * @author: 01
 * @create: 2018-03-06 16:36
 **/
public class StudentMapper implements RowMapper<Student> {

    public Student mapRow(ResultSet resultSet, int i) throws SQLException {
        Student student = new Student();

        student.setSid(resultSet.getInt("sid"));
        student.setSname(resultSet.getString("sname"));
        student.setAge(resultSet.getInt("age"));
        student.setSex(resultSet.getString("sex"));
        student.setAddress(resultSet.getString("address"));

        return student;
    }
}

package org.zero01.dao;

import org.springframework.jdbc.core.RowMapper;
import org.zero01.pojo.StudentLog;

import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @program: spring-transaction
 * @description: 配置studentlog表格字段与StudentLog对象属性的映射
 * @author: 01
 * @create: 2018-03-06 16:32
 **/
public class StudentLogMapper implements RowMapper<StudentLog> {

    public StudentLog mapRow(ResultSet resultSet, int i) throws SQLException {

        StudentLog studentLog = new StudentLog();

        studentLog.setLog_id(resultSet.getInt("log_id"));
        studentLog.setSid(resultSet.getInt("sid"));
        studentLog.setSname(resultSet.getString("sname"));
        studentLog.setAge(resultSet.getInt("age"));
        studentLog.setSex(resultSet.getString("sex"));
        studentLog.setAddress(resultSet.getString("address"));
        studentLog.setOperation_type(resultSet.getString("operation_type"));
        studentLog.setLog_time(resultSet.getTimestamp("log_time"));

        return studentLog;
    }
}
  1. 逻辑层接口的实现类:
package org.zero01.service;

import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.zero01.dao.DAO;
import org.zero01.dao.LogDAO;
import org.zero01.pojo.Student;
import org.zero01.pojo.StudentLog;

import java.util.List;

@Service("schoolService")
// 配置这个注解后,该类就受到Spring的事务管理了
@Transactional
public class SchoolService implements School{

    @Autowired
    private DAO dao;
    @Autowired
    private LogDAO logDAO;

    /**
     * @Description: 映射两张表格中相同的字段
     * @Author: 01
     * @Date: 2018/3/6
     */
    public StudentLog stuMap(Student student, String operation_type) {

        StudentLog studentLog = new StudentLog();
        if (student != null) {
            studentLog.setSid(student.getSid());
            studentLog.setSname(student.getSname());
            studentLog.setAge(student.getAge());
            studentLog.setSex(student.getSex());
            studentLog.setAddress(student.getAddress());
        }
        studentLog.setOperation_type(operation_type);

        return studentLog;
    }

    /**
     * @Description: 入学
     * @Param:
     * @return:
     * @Author: 01
     * @Date: 2018/3/6
     */
    public int enterSchool(Student student) {

        int sid = dao.insert(student);
        student.setSid(sid);

        return logDAO.insert(stuMap(student, "add"));
    }

    /**
     * @Description: 删除学生数据
     * @Param:
     * @return:
     * @Author: 01
     * @Date: 2018/3/6
     */
    public int deleteStudentData(int sid) {
        Student student = dao.selectById(sid);
        if (student != null) {
            student.setSid(sid);
            dao.delete(sid);
        } else {
            return 0;
        }

        return logDAO.insert(stuMap(student, "delete"));
    }

    /**
     * @Description: 搜索某个学生的资料
     * @Param:
     * @return:
     * @Author: 01
     * @Date: 2018/3/6
     */
    public Student searchStudentData(int sid) {
        Student student = dao.selectById(sid);
        if (student != null) {
            logDAO.insert(stuMap(student, "selectById"));
        } else {
            return null;
        }
        return student;
    }

    /**
     * @Description: 搜索全部学生的资料
     * @Param:
     * @return:
     * @Author: 01
     * @Date: 2018/3/6
     */
    public List<Student> searchStudentsData() {
        List<Student> students = dao.selectAll();
        logDAO.insert(stuMap(null, "selectAll"));
        return students;
    }

    /**
     * @Description: 修改某个学生的资料
     * @Param:
     * @return:
     * @Author: 01
     * @Date: 2018/3/6
     */
    public int alterStudentData(Student studentNew) {
        Student studentOld = dao.selectById(studentNew.getSid());
        int row = dao.update(studentNew);
        logDAO.insert(stuMap(studentOld, "alter"));

        return row;
    }
}

以上就改造完成了,经过测试没有什么问题,事务也能正常回滚,保证了两张表格数据的一致性。