Spring-事务

1. 事务

1.1 什么是事务

事务(Transaction):是数据库的一种安全机制、一个操作序列。事务将所有DML命令作为一个整体向系统提交或撤销操作请求,这些命令要么都执行、要么都不执行。

数据库查询语言(DQL):SELECT

数据库操纵语言(DML):INSERT、UPDATE、DELETE

数据库控制语言(DCL):GRANT、ROLLBACK、COMMIT

1.2 ACID原则

ACID原则:原子性、一致性、隔离性、持久性。

原子性(Atomicity):一个事务的操作,要么全部完成,要么全部都不完成。

一致性(Consistency):在事务开始之前和事务结束后,数据库的完整性没有破坏。

隔离性(Isolation):数据库允许多个事务同时对数据惊醒读写和修改的能力(四个隔离级别)

持久性(Durability):事务结束以后,对数据的修改是永久的,即使系统故障也不会丢失

2. 无事务情况

实现对数据库中表student的增删查改功能。

2.1 实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private int id;
    private String stu_username;
    private String stu_password;

    public Student(String stu_username, String stu_password) {
        this.stu_username = stu_username;
        this.stu_password = stu_password;
    }
}

2.2 接口Mapper

public interface StudentMapper {
    List<Student> GetAll();
    int Insert(Student student);
    int Delete(int id);
    int Update(Student student);
}

2.3 映射Mapper

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD com.Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.Map.StudentMapper">
    <select id="GetAll" resultType="com.POJO.Student">
        select * from student
    </select>
    <insert id="Insert" parameterType="com.POJO.Student">
        insert into student(stu_username, stu_password) values(#{stu_username}, #{stu_password})
    </insert>
    <delete id="Delete" parameterType="java.lang.Integer">
        delete from student where id = #{id}
    </delete>
    <update id="Update" parameterType="com.POJO.Student">
        update student set stu_username = #{stu_username}, stu_password = #{stu_password} where id = #{id}
    </update>
</mapper>

2.4 接口实现类

public class StudentMapperImpl extends SqlSessionDaoSupport implements StudentMapper{
    @Override
    public List<Student> GetAll() {
        return getSqlSession().getMapper(StudentMapper.class).GetAll();
    }

    @Override
    public int Insert(Student student) {
        return getSqlSession().getMapper(StudentMapper.class).Insert(student);
    }

    @Override
    public int Delete(int id) {
        return getSqlSession().getMapper(StudentMapper.class).Delete(id);
    }

    @Override
    public int Update(Student student) {
        return getSqlSession().getMapper(StudentMapper.class).Update(student);
    }
}

2.5 整合sqlSession

Beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           https://www.springframework.org/schema/beans/spring-beans.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">
    <!--配置数据源-->
    <bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url"
                  value="jdbc:mysql://localhost:3306/student?useSSL=true&amp;useUnicode=true&amp;charsetEncoding=utf8&amp;severTimezone=GMT"/>
        <property name="username" value="root"/>
        <property name="password" value="root199962"/>
    </bean>

    <!--配置SqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="datasource"/>
        <!--配置mybatis-config.xml-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--配置映射-->
        <property name="mapperLocations" value="classpath*:com/Map/*.xml"/>
    </bean>

    <!--配置sqlSessionFactory-->
    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <!--通过构造器注入(无set方法)-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>
</beans>

ApplicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--导入配置文件beans.xml-->
    <import resource="Beans.xml"/>

    <!--注册实现类的bean-->
    <bean id="studentMapperImpl" class="com.Map.StudentMapperImpl">
        <!--利用实现类的set方法注入sqlSessionFactory-->
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>
</beans>

2.6 测试

@Test
public void TestGetAll() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
    StudentMapper studentMapper = applicationContext.getBean("studentMapperImpl", StudentMapper.class);
    List<Student> students = studentMapper.GetAll();
    for (Student student : students) {
        System.out.println(student);
    }
}
/*
输出:
Student(id=1, stu_username=admin, stu_password=123456)
Student(id=15, stu_username=王五, stu_password=123456)
Student(id=16, stu_username=王五, stu_password=123456)
*/

2.7 添加错误测试

@Test
public void TestInsertDelete() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
    StudentMapper studentMapper = applicationContext.getBean("studentMapperImpl", StudentMapper.class);
    Student stu = new Student("王五", "123456");
    studentMapper.Insert(stu);
    studentMapper.Delete(5);
    List<Student> students = studentMapper.GetAll();
    for (Student student : students) {
        System.out.println(student);
    }
}

Spring-事务

可以看到,上面的添加操作成功了,但下面删除了一个表中不存在ID就报错了。

再来查下数据库,看上面的添加操作对数据库是否造成了影响。

Spring-事务

可以看到,虽然删除操作报错了,但添加操作依然对数据库造成了影响,这完全不符合事务的原子性!

3. Spring 事务

3.1 Spring 事务管理

使用 MyBatis-Spring 的主要原因之一是它允许 MyBatis 参与 Spring 事务。MyBatis-Spring 并没有创建特定于 MyBatis 的新事务管理器,而是利用了 Spring 中现有的DataSourceTransactionManager

3.2 事务分类

Spring支持编程式事务管理和声明式事务管理。

编程式事务管理:

  1. 将事务和代码嵌套到业务方法中,实现控制事务的提交和回滚。
  2. 缺点:必须再每个事务操作业务逻辑中包含额外的事务管理代码。

声明式事务管理:

  1. 将事务管理代码从业务方法中分离出来,以声明式的方式来实现事务管理。
  2. 将事务作为横切关注点,提供AOP方法模块化。
  3. Spring中通过Spring AOP框架支持声明式事务管理。

声明式事务管理比编程式事务管理更加可取。

3.3 窥见源码

3.3.1 PlatformTransactionManager

Spring 事务是由 org.springframework.transaction.PlatformTransactionManager 接口定义。

该接口提供了三个方法,内容如下:

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;
}

3.3.2 TransactionDefinition

该接口中定义了事务的状态,主要内容如下:

public interface TransactionDefinition {
    default int getPropagationBehavior() {return 0;}
    default int getIsolationLevel() {return -1;}
    default int getTimeout() {return -1;}
    default boolean isReadOnly() {return false;}
    @Nullable
    default String getName() {return null;}
}

getPropagationBehavior:该方法返回传播行为。

getIsolationLevel:该方法返回该事务独立于其他事务的工作程度(隔离级别)。

getTimeout:该方法返回一秒为单位的时间间隔,事务必须在该时间间隔内完成。

isReadOnly:该方法返回该是否是只读的。

getName:该方法返回该事务的名称。

3.3.3 事务隔离级别

事务隔离级别也定义在TransactionDefinition中。

// 这是默认的隔离级别
int ISOLATION_DEFAULT = -1;
// 读不提交,可以发生误读、不可重复读、幻读
int ISOLATION_READ_UNCOMMITTED = 1;
// 读提交,能够阻止误读,可以发生不可重复读、幻读
int ISOLATION_READ_COMMITTED = 2;
// 可重复读,阻止误读和不可重复读,可以发生误读
int ISOLATION_REPEATABLE_READ = 4;
// 可序列化,阻止误读、不可重复读和幻读
int ISOLATION_SERIALIZABLE = 8;
// 默认不超时
int TIMEOUT_DEFAULT = -1;

3.3.4 传播类型

传播类型也定义在TransactionDefinition中。

// 如果当前没有事务,就新建一个事务;如果已存在一个事务中,就加入到这个事务中。(最常见选项)
int PROPAGATION_REQUIRED = 0;
// 支持当前事务,如果没有当前事务,就以非事务方式执行
int PROPAGATION_SUPPORTS = 1;
// 使用当前事务,如果没有当前事务,就抛出异常
int PROPAGATION_MANDATORY = 2;
// 新建事务,如果当前存在事务,把当前事务挂起
int PROPAGATION_REQUIRES_NEW = 3;
// 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
int PROPAGATION_NOT_SUPPORTED = 4;
// 以非事务方式执行擦偶哦在,如果当前事务存在异常则抛出异常
int PROPAGATION_NEVER = 5;
// 如果当前存在事务,则在嵌套事务内执行;如果没有事务,则执行与PROPAGATION_REQUIRED类似的操作
int PROPAGATION_NESTED = 6;

4. 代码实现

原来的代码几乎不用删除,我们在此基础上加入事务的功能。

4.1 配置事务

我们在Beans.xml文件中加入事务。

4.1.1 添加约束

前提条件:添加事务的约束tx

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns: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/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">
</beans>

4.1.2 配置声明式事务

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <constructor-arg ref="datasource"/>
</bean>

4.1.3 配置事务的通知

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

传播特性(propagation):

  • REQUIRED、SUPPORTS、MANDATORY、NOT_SUPPORTED、NEVER、NESTED

隔离级别(isolation):

  • DEFAULT、READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE

只读(read-only):

  • false、true

超时(timeout):

  • 事务超时值(单位为秒)。

回滚异常(rollback-for):

  • 触发回滚的异常;以逗号分隔。例如com.foo.MyBusinessException, ServletException

不回滚异常(no-rollback-for):

  • 不触发回滚的异常;以逗号分隔。例如com.foo.MyBusinessException, ServletException

4.1.4 配置AOP织入

<aop:config>
    <aop:pointcut id="pointcut" expression="execution(* com.Map.*.*(..))"/>
    <aop:advisor advice-ref="interceptor" pointcut-ref="pointcut"/>
</aop:config>

4.2 测试

@Test
public void TestInsertDelete() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
    StudentMapper studentMapper = applicationContext.getBean("studentMapperImpl", StudentMapper.class);
    Student stu = new Student("王五", "123456");
    studentMapper.Insert(stu);
    studentMapper.Delete(5);
    List<Student> students = studentMapper.GetAll();
    for (Student student : students) {
        System.out.println(student);
    }
}

5. 写在最后

事务的功能时通过AOP织入实现的。

如果想了解MyBatis有关的事务,可以看下sqlSessionFactory.openSession();的源码。

 


❤️ END ❤️

ass);
Student stu = new Student(“王五”, “123456”);
studentMapper.Insert(stu);
studentMapper.Delete(5);
List students = studentMapper.GetAll();
for (Student student : students) {
System.out.println(student);
}
}


# 5. 写在最后

事务的功能时通过AOP织入实现的。

如果想了解MyBatis有关的事务,可以看下`sqlSessionFactory.openSession();`的源码。



&nbsp;

------

<div align="center">❤️&nbspEND&nbsp❤️</div>



上一篇:typeid详解(转)


下一篇:python 将数据库数据转换为字典类型