IDEA项目实践——Spring集成mybatis、spring当中的事务

系列文章目录

IDEA项目实践------创建Java项目以及创建Maven项目案例、使用数据库连接池创建项目简介

IDEWA项目实践------mybatis的一些基本原理以及案例

IDEA项目实践------动态SQL、关系映射、注解开发

IDEA项目实践------Spring框架简介,以及IOC注解

IDEA项目实践------Spring当中的切面AOP

文章目录

系列文章目录

前言

[一 Spring集成mybatis](#一 Spring集成mybatis)

[1.1 MySQL创建数据库ssm,新建表Student表](#1.1 MySQL创建数据库ssm,新建表Student表)

[1.1 MySQL 创建数据库 ssm,新建表 Student](#1.1 MySQL 创建数据库 ssm,新建表 Student)

[1.1.1 原JDBC建立连接形式的讲解](#1.1.1 原JDBC建立连接形式的讲解)

1)指定数据源

[2) 工具类](#2) 工具类)

[1.1.2 创建Maven项目](#1.1.2 创建Maven项目)

[1.2 maven 依赖 pom.xml](#1.2 maven 依赖 pom.xml)

[1.3 定义实体类 Student](#1.3 定义实体类 Student)

[1.4 定义 StudentDao 接口](#1.4 定义 StudentDao 接口)

[1.5 定义映射文件 mapper](#1.5 定义映射文件 mapper)

[1.6 定义 Service 接口和实现类](#1.6 定义 Service 接口和实现类)

[1.7 定义 MyBatis 主配置文件](#1.7 定义 MyBatis 主配置文件)

[1.8 修改 Spring 配置文件](#1.8 修改 Spring 配置文件)

[(1) 数据源的配置(掌握)](#(1) 数据源的配置(掌握))

[(2) 从属性文件读取数据库连接信息](#(2) 从属性文件读取数据库连接信息)

[(3) 注册 SqlSessionFactoryBean](#(3) 注册 SqlSessionFactoryBean)

[(4) 定义 Mapper 扫描配置器 MapperScannerConfigurer](#(4) 定义 Mapper 扫描配置器 MapperScannerConfigurer)

[1.9 向 Service 注入接口名](#1.9 向 Service 注入接口名)

[1.10 Spring 配置文件全部配置](#1.10 Spring 配置文件全部配置)

[二 Spring 事务](#二 Spring 事务)

[2.1 Spring 的事务管理](#2.1 Spring 的事务管理)

[2.2 Spring 事务管理 API](#2.2 Spring 事务管理 API)

[(1) 事务管理器接口(重点)](#(1) 事务管理器接口(重点))

[A、 常用的两个实现类](#A、 常用的两个实现类)

[B、Spring 的回滚方式(理解)](#B、Spring 的回滚方式(理解))

C、回顾错误与异常(理解)

[(2) 事务定义接口](#(2) 事务定义接口)

I、事务隔离级别常量

[(1)脏读(Dirty read)](#(1)脏读(Dirty read))

[(2)不可重复读(Nonrepeatable read)](#(2)不可重复读(Nonrepeatable read))

[(3)幻读(Phantom read)](#(3)幻读(Phantom read))

II、事务传播行为常量

[a、 PROPAGATION_REQUIRED](#a、 PROPAGATION_REQUIRED)

b、PROPAGATION_SUPPORTS

[c、 PROPAGATION_REQUIRES_NEW](#c、 PROPAGATION_REQUIRES_NEW)

III、事务默认超时时限常量【了解即可】

[2.3 程序举例环境搭建](#2.3 程序举例环境搭建)

Step0:创建数据库表

[​编辑 Step1: maven 依赖 pom.xml](#编辑 Step1: maven 依赖 pom.xml)

Step2:创建实体类

[Step3:定义 dao 接口](#Step3:定义 dao 接口)

[Step4:定义 dao 接口对应的 sql 映射文件](#Step4:定义 dao 接口对应的 sql 映射文件)

Step5:定义异常类

[Step6:定义 Service 接口](#Step6:定义 Service 接口)

[Step7:定义 service 的实现类](#Step7:定义 service 的实现类)

[Step8:修改 Spring 配置文件内容](#Step8:修改 Spring 配置文件内容)

Step9:定义测试类

[2.4 使用 Spring 的事务注解管理事务(掌握)](#2.4 使用 Spring 的事务注解管理事务(掌握))

实现注解的事务步骤:

[1. 声明事务管理器](#1. 声明事务管理器)

[2. 开启注解驱动 transaction-manager:](#2. 开启注解驱动 transaction-manager:)

[3. 业务层 public 方法加入事务属性​​​​​​​](#3. 业务层 public 方法加入事务属性)

第一句是需要有事务

此处事务回滚是发生库存不足和商品为空时的class

​​​​​​​​编辑

[2.5 使用 AspectJ 的 AOP 配置管理事务(掌握)](#2.5 使用 AspectJ 的 AOP 配置管理事务(掌握))

Step1:复制项目

[Step2:maven 依赖 pom.xml](#Step2:maven 依赖 pom.xml)

Step3:在容器中添加事务管理器

Step4:配置事务通知

Step5:配置增强器

Step6:修改测试类


前言

本文主要介绍Spring集成mybatis以及spring当中的事务,文章当中的案例仅供参考。

一 Spring集成mybatis

将MyBatis 与 Spring进行整合,主要解决的问题就是将SqlSessionFactory对象交由Spring来管理。所以,该整合,只需要将SqlSessionFactory的对象生成器 将与与Spring进行整合,主要解决的问题就是SqlSessionFactoryBean注册在Spring容器中,再将其注入给Dao的实现类即可完成整合。

实现Spring与MyBatis的整合常用的方式:扫描的Mapper动态代理

Spring 像插线板一样,mybatis框架是插头,可以容易的组合到一起。插线板spring插上 mybatis,两个框架就是一个整体。

1.1 MySQL创建数据库ssm,新建表Student表

创建实体类student

1.1 MySQL 创建数据库 ssm,新建表 Student

1.1.1 原JDBC建立连接形式的讲解

1)指定数据源

原来的jdbc形式的配置文件

指定数据源

2) 工具类

mybatis与spring

spring

1.1.2 创建Maven项目

1.2 maven 依赖 pom.xml

XML 复制代码
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.26</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.3.26</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.26</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.11</version>
    </dependency>
    <!-- Spring整合MyBatis的依赖 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.1.0</version>
    </dependency>
    <!-- --------------------- -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.29</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.8</version>
    </dependency>
</dependencies>	
 <!--插件:-->
<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>false</filtering>
        </resource>
    </resources>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.11.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

1.3 定义实体类 Student

创建学生的实体类

编辑代码如下:

java 复制代码
package com.ambow.pojo;

import lombok.Data;

@Data
public class Student {
    private int id;
    private String name;
    private int age;
}

1.4 定义 StudentDao 接口

此处选择接口,写的时候没有截全

java 复制代码
package com.ambow.dao;

import com.ambow.pojo.Student;

import java.util.List;

public interface StudentDao {
    int insertStudent(Student student);
    int updateStudent(Student student);
    int deleteStudent(Student student);

    Student selectStudentById(int id);
    List<Student> selectAllStudents();
}

1.5 定义映射文件 mapper

在 Dao 接口的包中创建 MyBatis 的映射文件 mapper,命名与接口名相 同,本例为 StudentDao.xml。mapper 中的 namespace 取值也为 Dao 接口 的全限定性名。

项目的路径在此处

编辑映射文件的代码:

XML 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--添加命名空间-->
<mapper namespace="com.ambow.dao.StudentDao">

    <insert id="insertStudent">
        insert into student values(null,#{name},#{age})
    </insert>
    <update id="updateStudent">
        update student set name = #{name},age = #{age} where id = #{id}
    </update>
    <delete id="deleteStudent">
        delete from student where id = #{id}
    </delete>
    <select id="selectStudentById" resultType="com.ambow.pojo.Student">
        select * from student where id = #{id}
    </select>
    <select id="selectAllStudents" resultType="com.ambow.pojo.Student">
        select * from student
    </select>

</mapper>

1.6 定义 Service 接口和实现类

接口定义:

java 复制代码
package com.ambow.service;

import com.ambow.pojo.Student;

import java.util.List;

public interface StudentService {
    int addStudent(Student student);
    int modifyStudent(Student student);
    int removeStudent(int id);

    Student findStudentById(int id);
    List<Student> findAllStudents();
}

接口的实现类定义:

自动生成接口的实现类

java 复制代码
package com.ambow.service.impl;

import com.ambow.dao.StudentDao;
import com.ambow.pojo.Student;
import com.ambow.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service("studentService")
public class StudentServiceImpl implements StudentService {

    //引入StudentDao的对象
    @Autowired
    private StudentDao studentDao;
    //Spring有set注入方法
    public void setStudentDao(StudentDao studentDao) {
        this.studentDao = studentDao;
    }

    @Override
    public int addStudent(Student student) {
        return studentDao.insertStudent(student);
    }

    @Override
    public int modifyStudent(Student student) {
        return studentDao.updateStudent(student);
    }

    @Override
    public int removeStudent(Student student) {
        return 0;
    }

    @Override
    public int removeStudent(int id) {
        return studentDao.deleteStudent(id);
    }

    @Override
    public Student findStudentById(int id) {
        return studentDao.selectStudentById(id);
    }

    @Override
    public List<Student> findAllStudents() {
        return studentDao.selectAllStudents();
    }
}

1.7 定义 MyBatis 主配置文件

在 src 下定义 MyBatis 的主配置文件,命名为 mybatis.xml。

这里有两点需要注意:

(1)主配置文件中不再需要数据源的配置了。因为数据源要交给 Spring 容器 来管理了。

(2)这里对 mapper 映射文件的注册,使用标签,即只需给出 mapper 映射文件所在的包即可。因为 mapper 的名称与 Dao 接口名相同, 可以使用这种简单注册方式。这种方式的好处是,若有多个映射文件,这里的 配置也是不用改变的。当然,也可使用原来的标签方式。

原先的environment数据源将交由Spring来管理 【代管】

mybatis-config.xml文件:

XML 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties resource="jdbc.properties"/>
    
    <typeAliases>
        <package name="com.ambow.pojo"/>
    </typeAliases>
    
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <package name ="com.ambow.dao"/>
    </mappers>
</configuration>

jdbc.properties文件:

XML 复制代码
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
username=root
password=root

可以先测试一下:

测试类的代码:

java 复制代码
package com.ambow.test;

import com.ambow.dao.StudentDao;
import com.ambow.pojo.Student;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class SMTest {
    @Test
    public void test01() throws IOException {
        //先找到主配置文件
        String url = "mybatis-config.xml";
        //读取主配置文件
        InputStream inputStream = Resources.getResourceAsStream(url);
        //输入流拿到SqlSessionFactory工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //再次拿到SqlSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //最后拿到StudentDao对象
        StudentDao studentDao = ((SqlSession) sqlSession).getMapper(StudentDao.class);
        //拿到学生对象,进行遍历
        List<Student> students = studentDao.selectAllStudents();
        for (Student student : students) {
            System.out.println(student);
        }
    }
}

等价于将原来的mybatis代码写一遍,然后修改

1.8 修改 Spring 配置文件

整合:把MyBatis中的核心对象,放到Spring容器

(1) 数据源的配置(掌握)

使用 JDBC 模板,首先需要配置好数据源,数据源直接以 Bean 的形式配置 在 Spring 配置文件中。根据数据源的不同,其配置方式不同:

Druid 数据源 DruidDataSource Druid 是阿里的开源数据库连接池。是 Java 语言中最好的数据库连接 池。Druid 能够提供强大的监控和扩展功能。Druid 与其他数据库连接池的 最大区别是提供数据库的

官网:GitHub - alibaba/druid: 阿里云计算平台DataWorks(https://help.aliyun.com/document_detail/137663.html) 团队出品,为监控而生的数据库连接池

使用地址:Home · alibaba/druid Wiki · GitHub常见问题

配置连接池:

Spring 配置文件:

(2) 从属性文件读取数据库连接信息

为了便于维护,可以将数据库连接信息写入到属性文件中,使 Spring 配置 文件从中读取数据。

属性文件名称自定义,但一般都是放在 src 下。

java 复制代码
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
jdbc.username=root
jdbc.password=root

Spring 配置文件从属性文件中读取数据时,需要在的 value 属性中使用${ },将在属性文件中定义的 key 括起来,以引用指定属性的值。

该属性文件若要被 Spring 配置文件读取,其必须在配置文件中进行注册。 使用<context>标签。

<context:property-placeholder />方式(掌握)

该方式要求在 Spring 配置文件头部加入 spring-context.xsd 约束文件

<context:property-placeholder />标签中有一个属性 location,用于指定属 性文件的位置。

(3) 注册 SqlSessionFactoryBean

工厂类拿到工厂对象,以及主配置文件

(4) 定义 Mapper 扫描配置器 MapperScannerConfigurer

Mapper 扫描配置器 MapperScannerConfigurer 会自动生成指定的基本 包中 mapper 的代理对象。该 Bean 无需设置 id 属性。basePackage 使用分 号或逗号设置多个包。

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

    <!--整合:把MyBatis中的核心对象,放到Spring容器-->

    <!--引入属性配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties" />
    <context:component-scan base-package="com.ambow.service" />
    <!--1.DataSource-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
    </bean>
    <!--2.注册SqlSessionFactoryBean-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!--起别名的也可以将其放在这里-->
        <property name="typeAliasesPackage" value="com.ambow.pojo" />
        <!--mapper映射文件的位置,此处的value里面包之间需要使用/分割-->
        <property name="mapperLocations" value="classpath:com/ambow/dao/*.xml" />
    </bean>
    <!--3.mapper的扫描配置器 -> 生成mapper的代理对象-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <property name="basePackage" value="com.ambow.dao" />
    </bean>

</beans>

1.9 向 Service 注入接口名

向 Service 注入 Mapper 代理对象时需要注意,由于通过 Mapper 扫描配置器 MapperScannerConfigurer 生成的 Mapper 代理对象没有名称,所以在 向 Service 注入 Mapper 代理时,无法通过名称注入。但可通过接口的简单类名注入,因为生成的是这个 Dao 接口的对象。

补充的地方:

XML 复制代码
<!--4.向service层注入Dao-->
<!--    <bean id="studentService" class="com.ambow.service.impl.StudentServiceImpl">
        <property name="studentDao" ref="studentDao" />
     </bean>-->

    <!--注入的过程-->
    <!--dataSource -> SqlSessionFactoryBean -> MapperScannerConfigurer(生成Dao代理对象) -> studentService-->

1.10 Spring 配置文件全部配置

测试一下:

java 复制代码
@Test
    public void test02() throws IOException {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        StudentService studentService = (StudentService) context.getBean("studentService");
        List<Student> allStudents = studentService.findAllStudents();
        System.out.println(allStudents.size());
        for (Student student : allStudents) {
            System.out.println(student);
        }
    }

注意事项:

前面的jdbc的配置文件里面需要加上jdbc.的前缀,否则,这个找到的是本机的username,而不是mysql的用户名

此处可以看到拿到的并不是mysql的用户,而是电脑主机的用户名。

再次压缩一下:

原来的mybatis-config文件里面的内容可以整合一下

整合完之后删除mybatis-config文件


前面的第四部可以使用注解的方式来做

实际的开发环境当中。

//自定义的类就可以使用注解的方法来写注入

//非自定义的类就需要在配置文件里面注入

二 Spring 事务

2.1 Spring 的事务管理

事务原本是数据库中的概念,在 Dao 层。但一般情况下,需要将事务提升到业务层,即 Service 层。这样做是为了能够使用事务的特性来管理具体的业务。

在 Spring 中通常可以通过以下两种方式来实现对事务的管理:

(1)使用 Spring 的事务注解管理事务

(2)使用 AspectJ 的 AOP配置管理事务

2.2 Spring 事务管理 API

Spring 的事务管理,主要用到两个事务相关的接口。

(1) 事务管理器接口(重点)

事务管理器是 PlatformTransactionManager 接口对象

其主要用于完成事务的提交、回滚,及获取事务的状态信息。

接口无法直接使用,需要实现类,实际使用的是下面的两个类

A、 常用的两个实现类

PlatformTransactionManager 接口有两个常用的实现类

  • DataSourceTransactionManager :使用JDBC 或 MyBatis进行数据库操作时使用。

  • HibernateTransactionManager :使用Hibernate 进行持久化数据时使用。

​​​​​​​B、Spring 的回滚方式(理解)

Spring 事务的默认回滚方式是:发生运行时异常和 error 时回滚,发生受查(编译)异常时提交。不过,对于受查异常,程序员也可以手工设置其回滚方式。

C、回顾错误与异常(理解)

Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类 (或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java 的 throw 语句抛出。

  1. Error:是程序在运行过程中出现的无法处理的错误,比如 OutOfMemoryError、ThreadDeath、NoSuchMethodError 等。当这些错误发生时,程序是无法处理(捕获或抛出)的,JVM 一般会终止线程。
  2. Exception:程序在编译和运行时出现的另一类错误称之为异常,它是 JVM 通知程序员的一种方式。通过这种方式,让程序员知道已经或可能出现错误,要求程序员对其进行处理。

异常分为运行时异常与受查异常。

运行时异常,是 RuntimeException 类或其子类,即只有在运行时才出现的异常。如,NullPointerException、ArrayIndexOutOfBoundsException、 IllegalArgumentException 等均属于运行时异常。这些异常由JVM抛出,在编译时不要求必须处理(捕获或抛出)。但,只要代码编写足够仔细,程序足够健壮,运行时异常是可以避免的。【因此,运行时异常,也叫非受查异常,编译器无法检查到,需要程序员通过修改代码来解决】

受查异常,也叫编译时异常,即在代码编写时要求必须捕获或抛出的异常,若不处理,则无法通过编译。如 SQLException, ClassNotFoundException,IOException 等都属于受查异常。

RuntimeException 及其子类以外的异常,均属于受查异常。当然,用户自定义的 Exception 的子类,即用户自定义的异常也属受查异常。程序员在定义异常时,只要未明确声明定义的为 RuntimeException 的子类,那么定义的就是受查异常。

Ø 非检查异常(unckecked exception):Error 和 RuntimeException 以及他们的子类。javac在编译时,不会提示和发现这样的异常,不要求程序员必须处理这些异常。在运行阶段,倘若发生Error则虚拟机几乎崩溃,倘若发生RuntimeException若程序员没处理它则一直回溯向上抛给java虚拟机处理。当然,如果程序员愿意的话,也可以编写代码处理(使用try...catch...finally)这样的异常(但是通常情况下不会这样做。需要这样做的情况是比如搞数学运算的这个专业领域要处理ArithmeticException)。对于这些异常,我们应该修正代码,而不是去通过异常处理器处理。这种异常发生的原因多半是代码写的有问题。如除0错误ArithmeticException,错误的强制类型转换错误ClassCastException,数组索引越界ArrayIndexOutOfBoundsException,使用了空对象NullPointerException等等。

Ø 检查异常(checked exception):除了Error和 RuntimeException的其它异常。javac强制要求程序员为这样的异常做预备处理工作(使用try...catch...finally或者throws)。在方法中要么用try-catch语句捕获它并处理,要么用throws子句声明抛出它,否则编译不会通过。这样的异常一般是由程序的运行环境导致的。因为程序可能被运行在各种未知的环境下,而程序员无法干预用户如何使用他编写的程序,于是程序员就应该为这样的异常时刻准备着。如SQLException , IOException,ClassNotFoundException 等。

发生运行时异常才会回滚​​​​​​​

Q&A:为什么添加事务管理器?

答:因为不同的技术管理事务的类不同,比如:

  • JDBC:Connecton con.commit(); con.rollback();
  • MyBatis:SqlSession sqlSession.commit(); sqlSession.rollback();
  • Hibernate:Session session.commit(); session.rollback();

事务管理器用来生成相应技术的连接对象及执行语句。

不同框架提交事务是不同的

(2) 事务定义接口

事务定义接口 TransactionDefinition 中定义了事务描述相关的三类常量:

事务隔离级别、事务传播行为、事务默认超时时限,及对它们的操作。

I、事务隔离级别常量

定义了五个事务隔离级别常量(掌握)

在应用程序中,多个事务并发运行,操作相同的数据,可能会引起脏读、不可重复读、幻读等问题。

(1)脏读(Dirty read)

第一个事务访问并改写了数据,尚未提交事务,这时第二个事务进来了,读取了刚刚改写的数据,如果这时第一个事务回滚了,这样第二个事务读取到的数据就是无效的"脏数据"。

(2)不可重复读(Nonrepeatable read)

第一个事务在其生命周期内多次查询同一个数据,在两次查询之间,第二个事务访问并改写了该数据,导致第一个事务两次查询同一个数据得到的结果不一样。

(3)幻读(Phantom read)

幻读和不可重复读类似。它发生在第一个事务在其生命周期进行了两次按同一查询条件查询数据,第一次按该查询条件读取了几行数据,这时第二个事务进来了,且插入或删除了一些数据,然后第一个事务再次按同一条件查询,发现多了一些原本不存在的记录或者原有记录不见了。

为了解决并发问题,TransactionDefinition接口定义了5个事务隔离常量如下:

这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。

  1. ISOLATION_DEFAULT : 采用数据库默认的事务隔离级别 。MySql 的默认为 REPEATABLE_READ(可重复读); Oracle 默认为 READ_COMMITTED(读已提交)。 【REPEATABLE_READ存在幻读的情况,但MySQL的InnoDB解决了幻读】
  2. ISOLATION_READ_UNCOMMITTED:读未提交。允许另外一个事务读取到当前事务未提交的数据,隔离级别最低,未解决任何并发问题,会产生脏读,不可重复读和幻读。
  3. ISOLATION_READ_COMMITTED:读已提交。被一个事务修改的数据提交后才能被另外一个事务读取,另外一个事务不能读取该事务未提交的数据。解决脏读,但还存在不可重复读与幻读。
  4. ISOLATION_REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
  5. ISOLATION_SERIALIZABLE:串行化读。按时间顺序一一执行多个事务,每次读都需要获得表级共享锁,读写相互都会阻塞,不存在并发问题,最可靠,但性能与效率最低。

从第2到第5,隔离级别越来越高。

II、事务传播行为常量

定义了七个事务传播行为常量(掌握)

所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况(合并?互斥?)。

如,A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的。

事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX。

  1. PROPAGATION_REQUIRED :必须包含事务(增删改必用)

  2. PROPAGATION_REQUIRES_NEW:自己新开一个事务,不管之前是否有事务

  3. PROPAGATION_SUPPORTS :支持事务,如果加入的方法有事务,则支持事务;如果没有,不单开事务

  4. PROPAGATION_NEVER:不能运行在事务中,如果包在事务中,抛异常

  5. PROPAGATION_NOT_SUPPORTED :不支持事务,运行在非事务环境中,如果加入的方法有事务,则会把事务先挂起【不常用】

  6. PROPAGATION_MANDATORY :必须包在事务中,没有事务则抛异常

  7. PROPAGATION_NESTED:嵌套事务

最后的两个不怎么常用,了解一下即可。

a、 PROPAGATION_REQUIRED

指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中; 若当前没有事务,则创建一个新事务。这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。 如该传播行为加在 doOther()方法上。若 doSome()方法在调用 doOther() 方法时就是在事务内运行的,则 doOther()方法的执行也加入到该事务内执行。若 doSome()方法在调用 doOther()方法时没有在事务内执行,则 doOther()方法会创建一个事务,并在其中执行。

b、PROPAGATION_SUPPORTS

指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。

c、 PROPAGATION_REQUIRES_NEW

总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。

III、事务默认超时时限常量【了解即可】

定义了默认事务超时时限

常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,及不支持事务超时时限设置的none值。

注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该值一般就使用默认值即可。

2.3 程序举例环境搭建

举例:购买商品 trans_sale 项目

本例要实现购买商品,模拟用户下订单,向订单表添加销售记录,从商品表减少库存。

实现步骤:

Step0:创建数据库表

创建两个数据库表 sale , goods

sale 销售表

goods 商品表

goods 表数据

创建项目:

Step1: maven 依赖 pom.xml

XML 复制代码
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.26</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.3.26</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.26</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.11</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.1.0</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.29</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.8</version>
    </dependency>
</dependencies>	
 <!--插件:-->
<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>false</filtering>
        </resource>
    </resources>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.11.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

Step2:创建实体类

创建实体类 Sale 与 Goods

​​​​​​​goods实体类

java 复制代码
package com.ambow.pojo;
//dao是数据访问层
//service是业务层
public class Goods {
    private Integer id;
    private String nname;
    private Integer amount;
    private float price;
}

sale实体类

java 复制代码
package com.ambow.pojo;

public class Sale {
    private Integer id;
    private Integer gid;
    private Integer nums;
}

​​​​​​​Step3:定义 dao 接口

定义两个 dao 的接口 SaleDao , GoodsDao

goodsdao数据访问层

java 复制代码
package com.ambow.dao;

import com.ambow.pojo.Goods;

public interface GoodsDao {
    int updateGoods(Goods goods);
    Goods selectGoodsById(int id);
}

saledao数据访问层:

java 复制代码
package com.ambow.dao;

import com.ambow.pojo.Sale;

public interface SaleDao {
    int insertSale(Sale sale);
}

Step4:定义 dao 接口对应的 sql 映射文件

SaleDao.xml

XML 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ambow.dao.SaleDao">

    <insert id="insertSale">
        insert into sale (gid,nums) values (#{gid},#{nums})
    </insert>
</mapper>

GoodsDao.xml

XML 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ambow.dao.GoodsDao">

    <update id="updateGoods">
        update goods
        set amount = amount - #{amount}
        where id = #{id};
    </update>
    <select id="selectGoodsById" resultType="com.ambow.pojo.Goods">
        select * from goods where id = #{id}
    </select>
</mapper>

update返回的是影响的行数

业务描述:

Step5:定义异常类

定义 service 层可能会抛出的异常类 NotEnoughException

java 复制代码
package com.ambow.exception;
//库存不足的时候的异常抛出
public class NotEnoughException extends RuntimeException{
    public NotEnoughException(){
        super();
    }

    public NotEnoughException(String msg){
        super(msg);
    }
}

Step6:定义 Service 接口

定义 Service 接口 BuyGoodsService

java 复制代码
package com.ambow.service;

public interface BuyGoodsService {
    public void buy(Integer goodsId,Integer amount);
}

Step7:定义 service 的实现类

定义 service 层接口的实现类 BuyGoodsServiceImpl

1)类定义

2)Dao 属性

3)Buy 方法

完整代码段:

java 复制代码
package com.ambow.service.impl;

import com.ambow.dao.GoodsDao;
import com.ambow.dao.SaleDao;
import com.ambow.exception.NotEnoughException;
import com.ambow.pojo.Goods;
import com.ambow.pojo.Sale;
import com.ambow.service.BuyGoodsService;
import org.springframework.transaction.annotation.Transactional;

public class BuyGoodsServiceImpl implements BuyGoodsService {

    //1.SaleDao - 添加销售记录
    private SaleDao saleDao;
    //2.GoodsDao - 修改库存
    private GoodsDao goodsDao;
    //添加两个set方法
    public void setSaleDao(SaleDao saleDao) {
        this.saleDao = saleDao;
    }

    public void setGoodsDao(GoodsDao goodsDao) {
        this.goodsDao = goodsDao;
    }

    //购买商品
    @Transactional
    @Override
    public void buy(Integer goodsId, Integer amount) {
        //1.添加销售记录
        Sale sale = new Sale();
        sale.setGid(goodsId);
        sale.setNums(amount);

        saleDao.insertSale(sale);
        //验证商品库存是否存在和商品的库存是否不足
        Goods goods = goodsDao.selectGoodsById(goodsId);
        if (goods == null){
            throw new NullPointerException("无此商品");
        }
        if (goods.getAmount() < amount){
            throw new NotEnoughException("库存不足");
        }
        //2.修改库存
        goods = new Goods();
        goods.setId(goodsId);
        goods.setAmount(amount);

        goodsDao.updateGoods(goods);
    }
}

Step8:修改 Spring 配置文件内容

声明Mybatis 对象

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

    <!--整合:把MyBatis中的核心对象,放到Spring容器-->

    <!--引入属性配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties" />
    <context:component-scan base-package="com.ambow.service" />
    <!--1.DataSource-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
    </bean>
    <!--2.注册SqlSessionFactoryBean-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="typeAliasesPackage" value="com.ambow.pojo" />
        <property name="mapperLocations" value="classpath*:com/ambow/dao/*.xml" />
    </bean>
    <!--3.mapper的扫描配置器 -> 生成mapper的代理对象-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <property name="basePackage" value="com.ambow.dao" />
    </bean>

    <!--4.向service层注入Dao-->
    <bean id="buyGoodsService" class="com.ambow.service.impl.BuyGoodsServiceImpl">
        <property name="saleDao" ref="saleDao" />
        <property name="goodsDao" ref="goodsDao" />
     </bean>

    <!--注入的过程-->
    <!--dataSource -> SqlSessionFactoryBean -> MapperScannerConfigurer(生成Dao代理对象) -> studentService-->

    <!--声明事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <!--声明事务的注解驱动-->
    <tx:annotation-driven transaction-manager="transactionManager" />

</beans>

声明业务层对象

Step9:定义测试类

定义测试类 MyTest。现在就可以在无事务代理的情况下运行了。

原先的库存数量:

执行测试代码:

java 复制代码
package com.ambow.test;

import com.ambow.service.BuyGoodsService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TransTest {
    @Test
    public void test01(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        BuyGoodsService buyGoodsService = (BuyGoodsService) context.getBean("buyGoodsService");
        buyGoodsService.buy(1001,15);//正常购买
//        buyGoodsService.buy(1008,10);//无此商品
        //buyGoodsService.buy(1001,100);//库存不足
    }
}

数据库里面的数量:

对应的sale表里面:

另外一种情况:

当没有此商品编号时会出现异常

最后一种情况:

当此商品库存不足的时候也会出现异常

2.4 使用 Spring 的事务注解管理事务(掌握)

通过**@Transactional** 注解方式,可将事务织入到相应public方法中,实现事务管理。

@Transactional 的所有可选属性如下所示:

  • propagation :用于设置事务传播属性。该属性类型为 Propagation 枚举, 默认值为 Propagation.REQUIRED。

  • isolation :用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认 值为 Isolation.DEFAULT。

  • readOnly :用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值为 false【可修改】。

  • timeout :用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int, 默认值为-1,即没有时限。

  • rollbackFor :指定需要回滚的异常类 。类型为 Class[] ,默认值为空数组。 当然,若只有一个异常类时,可以不使用数组。

  • rollbackForClassName :指定需要回滚的异常类类名 。类型为String[] ,默 认值为空数组。当然,若只有一个异常类时,可以不使用数组。

  • noRollbackFor :指定不需要回滚的异常类 。类型为 Class[],默认值为空数 组。当然,若只有一个异常类时,可以不使用数组。

  • noRollbackForClassName :指定不需要回滚的异常类类名 。类型为 String[], 默认值为空数组。当然,若只有一个异常类时,可以不使用数组。

  • 需要注意的是,@Transactional 若用在方法上,只能用于 public 方法 上。对于其他非 public 方法,如果加上了注解@Transactional,虽然 Spring 不会报错,但不会将指定事务织入到该方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction 注解。
  • 若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。

实现注解的事务步骤:

复制 trans_sale 项目,新项目 trans_sale_annotation

  1. 声明事务管理器

要实现事务管理,需要先添加事务管理器声明。

基于mybatis的项目需要使用datasourcetransactionmanager,此处的属性四指定数据源

  1. 开启注解驱动 transaction-manager:

上述的内容在前面2.3.8里面已经写了

此处注意选择第四个tx开头的这个

事务管理器 bean 的 id与1.相同

  1. 业务层 public 方法加入事务属性​​​​​​​

第一句是需要有事务

此处事务回滚是发生库存不足和商品为空时的class

​​​​​​​

此处可以直接写下面注释的那部分即可

java 复制代码
//默认的已有rollbackfor事务回滚,此处可以直接省略
    @Transactional(Propagation = Propagation.REQUIRED,
                    rollbackFor = {NotEnoughException.class,
                                    NullPointerException.class})
   // @Transactional

2.5 使用 AspectJ 的 AOP 配置管理事务(掌握)

使用 XML 配置事务代理的方式的不足是,每个目标类都需要配置事务代 理。当目标类较多,配置文件会变得非常臃肿。

使用 XML 配置顾问方式可以自动为每个符合切入点表达式的类生成事务代 理。其用法很简单,只需将前面代码中关于事务代理的配置删除,再替换为如 下内容即可。

Step1:复制项目

复制 trans_sale 项目,并重命名为 trans_sal_aspectj。在此基础上修改。此处直接新建一个项目

​​​​​​​

Step2:maven 依赖 pom.xml

新加入 aspectj 的依赖坐标

XML 复制代码
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.26</version>
</dependency>

Step3:在容器中添加事务管理器

XML 复制代码
<!--声明事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

Step4:配置事务通知

为事务通知设置相关属性。用于指定要将事务以什么方式织入给哪些方法。 例如,应用到 buy 方法上的事务要求是必须的,且当 buy 方法发生异常后要回滚业务。

可以将全部的都在此处配置,配置完成之后,修改起来只要修改此处即可

XML 复制代码
<!--声明事务的注解驱动-->
    <!--<tx:annotation-driven transaction-manager="transactionManager" />-->
    <!--添加通知 通知的ID是购买的参数-->
    <tx:advice id="buyAdvice" transaction-manager="transactionManager">
        <!-- 增强 先添加事务propagation 其isolation选择默认即可 回滚直接写类名,还有我们自己创建的一个事务回滚 两个事务之间使用逗号隔开-->
        <tx:attributes>
            <tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
            rollback-for="java.lang.NullPointerException,com.ambow.exception.NotEnoughException"/>
            <!--在开发时有许多的add方法,直接写全部的,在发生异常时全部回滚-->
            <tx:method name="add*" propagation="REQUIRED" isolation="DEFAULT"
            rollback-for="java.lang.Exception"/>
            <!--给所有的方法度加入method-->
            <tx:method name="*" propagation="SUPPORTS" />
        </tx:attributes>
    </tx:advice>

Step5:配置增强器

指定将配置好的事务通知,织入给谁。【次粗通过aop织入】

此处指定切入点表达式,指定传进来的包【此处为任意包里面的任意service里面的任意方法,以及对应的方法名】,再加入增强的通知

XML 复制代码
<aop:config>
        <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>
        <aop:advisor advice-ref="buyAdvice" pointcut-ref="servicePt" />
    </aop:config>

Step6:修改测试类

测试类中要从容器中获取的是目标对象。

此处将原来的注解方式先注释,接着运行测试类可以看到aop的方式也可以

接着运行测试即可

总结

以上就是今天的内容~

欢迎大家点赞👍,收藏⭐,转发🚀,

如有问题、建议,请您在评论区留言💬哦。

**最后:转载请注明出处!!**​​​​​​​

相关推荐
乐鑫科技 Espressif13 分钟前
“乐鑫组件注册表”简介
java·前端·microsoft·iot·乐鑫科技
小灰灰__36 分钟前
Java通过calcite实时读取kafka中的数据
java·kafka·linq
java小吕布42 分钟前
Java Servlet详解:Servlet的生命周期、请求处理与响应发送
java·开发语言·servlet
武子康1 小时前
大数据-225 离线数仓 - 目前需求分析 指标口径 日志数据采集 taildir source HDFS Sink Agent Flume 优化配置
java·大数据·数据仓库·hadoop·hdfs·数据挖掘·flume
世间万物皆对象1 小时前
Spring Boot核心概念:自动配置
java·spring boot·后端
祁思妙想1 小时前
23.<Spring图书管理系统(强制登录版本)>
java·spring·java-ee
csdn5659738501 小时前
Elasticsearch 查看磁盘占用 查看指定索引磁盘占用
java·数据库·elasticsearch
Dnelic-1 小时前
【Java 集合】Collections 空列表细节处理
java·集合·collection·空对象·自学笔记
weixin_438335401 小时前
根据 Maven 的不同 profiles 来改变 Spring Boot 应用程序的配置
java·spring boot·maven
菩提祖师_1 小时前
基于大模型实现论文观点查重
java·深度学习