Spring系列教程五一文了解Spring事务

数据库事务(Database Transaction)是指将一系列数据库操作当作一个逻辑处理单元的操作,这个单元中的数据库操作要么完全执行,要么完全不执行。通过将一组相关操作组合为一个逻辑处理单元,可以简化错误恢复,并使应用程序更加可靠。

5.1 事务的特性

一个逻辑处理单元要成为事务,必须满足ACID(原子性、一致性、隔离性和持久性)属性。所谓的ACID含义如下。

5.1 JDBC事务

在Java基础的学习中可以知道JDBC事务通常通过Connection接口的方法来控制事务开始,事务提交以及事务回滚,下面,先通过简单的实例回顾JDBC事务。

package cn.bytecollege.util;

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

public class DBUtil {
    //省略加载驱动步骤
    public void save() throws SQLException {
        String url = "jdbc:mysql://localhost:3306?chapter05";
        String username = "root";
        String password = "root";
        Connection connection = null;
        try{
            connection = DriverManager.getConnection(url,username,password);
            //开启事务
            connection.setAutoCommit(false);
            //省略新增操作
            //提交事务
            connection.commit();
        }catch (SQLException e){
            e.printStackTrace();
            connection.rollback();
        }
    }
}

直接使用JDBC的编程方式管理事务,虽然可以完成对应的功能,但这种编程方式对代码的复用性不高。为了解决通过直接适应JDBC的方式对事务进行控制,提高代码复用性的问题,Spring也提供了对事务进行控制的相关API,下面将介绍使用Spring的方式进行事务管理。

5.2 Spring事务管理

在正式学习Spring事务管理前,需要做一些准备工作,首先准备数据库,并初始化数据

CREATE DATABASE SPRING_CHAPTER05;
USE SPRING_CHAPTER05;
CREATE TABLE STUDENT(
    STUDENT_ID INT PRIMARY KEY AUTO_INCREMENT,
    STUDENT_NAME VARCHAR(20),
    STUDENT_AGE TINYINT,
    STUDENT_GENDER CHAR(2)
);
CREATE TABLE STUDENT_INFO(
    STUDENT_DETAIL_ID INT PRIMARY KEY AUTO_INCREMENT,
    STUDENT_ID INT,
    MAIL VARCHAR(30),
    TELEPHONE VARCHAR(15)
)
INSERT INTO `spring_chapter05`.`student`(`STUDENT_ID`, `STUDENT_NAME`, `STUDENT_AGE`, `STUDENT_GENDER`) VALUES (1, '张三', 19, '男');
INSERT INTO `spring_chapter05`.`student`(`STUDENT_ID`, `STUDENT_NAME`, `STUDENT_AGE`, `STUDENT_GENDER`) VALUES (2, '李四', 18, '女');

INSERT INTO `spring_chapter05`.`student_info`(`STUDENT_DETAIL_ID`, `STUDENT_ID`, `MAIL`, `TELEPHONE`) VALUES (1, 1, 'zhangsan@163.com', '17765868611');
INSERT INTO `spring_chapter05`.`student_info`(`STUDENT_DETAIL_ID`, `STUDENT_ID`, `MAIL`, `TELEPHONE`) VALUES (2, 2, 'lisi@163.com', '18676543421');

为了便于操作数据库以及学习Spring事务,在本小节内整合Mybatis框架。

5.2.1 Spring整合Mybatis

  1. 创建Maven项目,并添加如下依赖。
  
    
      junit
      junit
      4.11
      test
    
    
    
      mysql
      mysql-connector-java
      8.0.15
    
    
    
      org.mybatis
      mybatis
      3.5.6
    
    
    
      org.mybatis
      mybatis-spring
      2.0.6
    
    
    
      com.alibaba
      druid
      1.2.6
    
    
    
      org.projectlombok
      lombok
      1.18.20
      provided
    
    
      org.springframework
      spring-context
      5.3.9
    
    
    
      org.springframework
      spring-tx
      5.3.9
    
    
    
      org.springframework
      spring-jdbc
      5.3.9
    
    
    
      log4j
      log4j
      1.2.17
    
  

需要注意的是Mybatis为了同Spring整合,推出了Mybatis-Spring项目,因此需要添加该依赖,除此以外还需要添加Spring-jdbc及Spring-tx等依赖。

  1. 新建cn.bytecollege.entity包,并在包中创建实体类:
package cn.bytecollege.entity;
import lombok.Data;
@Data
public class Student {
    private int studentId;
    private String studentName;
    private int studentAge;
    private String studentGender;
}
package cn.bytecollege.entity;
import lombok.Data;
@Data
public class StudentInfo {
    private int studentDetailId;
    private int studentId;
    private String mail;
    private String telephone;
}
  1. 新建cn.bytecollege.mapper接口,添加Mybatis映射器绑定的接口:
package cn.bytecollege.mapper;
import cn.bytecollege.entity.Student;

import java.util.List;

public interface StudentMapper {
    List findAllStudent();
}
package cn.bytecollege.mapper;
import cn.bytecollege.entity.StudentInfo;
import java.util.List;
public interface StudentInfoMapper {
    List findAllStudentDetail();
}
  1. 在resource目录下新建mapper路径,用于存放映射器文件。






    


    
  1. 在resource目录下新建Mybatis配置文件:mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>


    
        
        
    
    
        
        
    

在Spring中整合Mybatis时,数据源和映射器位置可以在Spring配置文件中进行配置,因此在Mybatis配置文件中省略了数据源和映射器位置的配置。因此只配置了开启驼峰命名映射和生成实体类的别名。

  1. 在resource目录下新建Spring配置文件:spring.xml
<?xml version="1.0" encoding="UTF-8"?>

    
    
    
    
    
    
        
        
        
        
        
        
				
        
        
        
        
				
        
        

        
        
        

        
        

        
    
    
    
        
    
    
    
        
        
        
    
    
    
        
    
  	
    

在Spring配置文件中需要配置以下信息:

至此,Spring中已经成功整合Mybatis,新建测试类并调用StudentMapper中查询方法,代码如下:

package cn.bytecollege.test;

import cn.bytecollege.entity.Student;
import cn.bytecollege.mapper.StudentMapper;
import org.apache.log4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;

public class Test {
    public static void main(String[] args) {
        Logger log = Logger.getLogger(Test.class);
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        StudentMapper mapper = context.getBean(StudentMapper.class);
        List list = mapper.findAllStudent();
        list.forEach(e->log.info(e));
    }
}

运行结果如下:

5.2.2 Spring声明式事务

从数据库的设计上可以看出student表和student_info表存在一对一的关系,也就是说在添加学生基本信息时,需要同时添加学生详细信息,或者在学生详细信息中先添加一条占位数据,先不使用事务并在代码中抛出异常:

在接口中添加方法:

package cn.bytecollege.mapper;
import cn.bytecollege.entity.Student;
import java.util.List;

public interface StudentMapper {
    /**
     * 查询所有学生
     * @return
     */
    List findAllStudent();

    /**
     * 新增学生
     * @param student
     * @return
     */
    int addStudent (Student student);
}
package cn.bytecollege.mapper;
import cn.bytecollege.entity.StudentInfo;
import java.util.List;
public interface StudentInfoMapper {
    /**
     * 查询所有学生信息
     * @return
     */
    List findAllStudentDetail();

    /**
     * 新增学生信息
     * @param studentInfo
     * @return
     */
    int addStudentInfo(StudentInfo studentInfo);
}

在映射器中添加对应的SQL语句



    
    
        insert into student (student_name, student_age, student_gender)
        values
        (#{studentName},#{studentAge},#{studentGender});
    


    
    
        insert into student_info (STUDENT_ID, MAIL, TELEPHONE)
        values
        (#{studentId},#{mail},#{telephone})
    

新建测试类:

package cn.bytecollege.test;

import cn.bytecollege.entity.Student;
import cn.bytecollege.entity.StudentInfo;
import cn.bytecollege.mapper.StudentInfoMapper;
import cn.bytecollege.mapper.StudentMapper;
import org.apache.log4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;

public class Test {
    public static void main(String[] args) {
        Logger log = Logger.getLogger(Test.class);
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");

        StudentMapper studentMapper = context.getBean(StudentMapper.class);
        StudentInfoMapper studentInfoMapper = context.getBean(StudentInfoMapper.class);

        Student student = new Student();
        student.setStudentAge(18);
        student.setStudentName("李雷");
        student.setStudentGender("男");

        StudentInfo studentInfo = new StudentInfo();
        studentInfo.setMail("lilei@163.com");
        studentInfo.setTelephone("17765213454");

        studentMapper.addStudent(student);
        //1.抛出异常
        System.out.println(5/0);
        studentInfo.setStudentId(student.getStudentId());
        studentInfoMapper.addStudentInfo(studentInfo);
    }
}

在测试类中注释1处人为抛出异常,运行测试类可以看出student表中添加了数据,但是student_info表中并没有添加数据,显然这违背了事务的原子性。运行结果如下图:

为了方便使用Spring声明式事务,新建StudentService类,代码如下:

package cn.bytecollege.service;

import cn.bytecollege.entity.Student;
import cn.bytecollege.entity.StudentInfo;
import cn.bytecollege.mapper.StudentInfoMapper;
import cn.bytecollege.mapper.StudentMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class StudentService {
    @Autowired
    private StudentMapper studentMapper;
    @Autowired
    private StudentInfoMapper studentInfoMapper;
    @Transactional
    public void add(Student student,StudentInfo studentInfo){
        studentMapper.addStudent(student);
        //1.抛出异常
        System.out.println(5/0);
        studentInfo.setStudentId(student.getStudentId());
        studentInfoMapper.addStudentInfo(studentInfo);
    }
}

可以看出在add()中认为抛出了异常,并且在方法上添加了@Transactional注解,该注解将会在下一小节进行讲解。

新建测试类,进行测试。

package cn.bytecollege.test;

import cn.bytecollege.entity.Student;
import cn.bytecollege.entity.StudentInfo;
import cn.bytecollege.service.StudentService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");

        StudentService studentService = context.getBean(StudentService.class);

        Student student = new Student();
        student.setStudentName("李雷");
        student.setStudentAge(18);
        student.setStudentGender("男");

        StudentInfo studentInfo = new StudentInfo();
        studentInfo.setMail("lilei@163.com");
        studentInfo.setTelephone("17765878821");

        studentService.add(student,studentInfo);
    }
}

运行测试类可以看出数据库中数据并没有添加,也就是说数据库事务回滚了,此时删除StudentService中注释1处的代码,再次运行可以看出数据成功插入。

5.2.3 @Transactional注解

基于@Transactional注解的拥有一组普适性很强的默认事务属性,往往可以直接使用这些默认的属性。

这些默认设置在大多数情况下都是适用的,所以一般不需要手工设置事务注解的属性。

属性

说明

Propagation

事务传播行为,通过以下枚举类提供合法值:org.springframework.transaction.annotation.

Propagation。例如:@Transactional(propagation = Propagation.REQUIRES_NEW)

isolation

事务隔离级别,通过一下枚举类提供合法值:

org.springframework.transaction.annotation.Isolation例如 @Transactional(isolation=Isolation.READ_COMMITTED)

readOnly

事务读写性,布尔型。例如 @Transactional(readOnly=true)

timeout

超时时间,int 型,以秒为单位。例如 @Transactional(timeout=10)

rollbackFor

一组异常类,遇到时进行回滚,类型为 Class<?extends Throwable>[],默认值为{}。例如 @Transactional(rollbackFor={SQLException.class})。多个异常之间可用逗号分隔.

rollbackForClassName

一组异常类名,遇到时进行回滚,类型为String[],默认值为{}。例如:@Transactional(rollbackForClassName={SQLException})

noRollbackFor

一组异常类,遇到时不回滚,类型为Class<? extends Throwable>[],默认值为{}

noRollbackForClassName

一组异常类名,遇到时不回滚,类型为String[],默认值为{}

5.3 Spring事务隔离级别

Spring对事务的支持提供了5种隔离级别,各种隔离级别的含义如表所示。

5.4 Spring事务传播行为

事务传播行为是用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时候,事务的传播特性。Spring中定义了7种事务传播行为,如表所示。

展开阅读全文

页面更新:2024-03-02

标签:事务   数据源   注解   属性   异常   接口   操作   数据库   教程   系列   数据   学生

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top