【Spring】(11)聲明式事務

一、事務

1.概念

在關係數據庫中,一個事務可以是一條SQL語句,一組SQL語句或整個程序。

2.特性

事務是恢復和併發控制的基本單位。

事務應該具有4個屬性:原子性、一致性、隔離性、持久性。這四個屬性通常稱爲ACID特性。

  • 原子性(atomicity)。一個事務是一個不可分割的工作單位,事務中包括的操作要麼都做,要麼都不做。
  • 一致性(consistency)。事務必須是使數據庫從一個一致性狀態變到另一個一致性狀態。一致性與原子性是密切相關的。
  • 隔離性(isolation)。一個事務的執行不能被其他事務干擾。即一個事務內部的操作及使用的數據對併發的其他事務是隔離的,併發執行的各個事務之間不能互相干擾。
  • 持久性(durability)。持久性也稱永久性(permanence),指一個事務一旦提交,它對數據庫中數據的改變就應該是永久性的。接下來的其他操作或故障不應該對其有任何影響。

事務總結一句話:要麼都成功!要麼都失敗!

3.爲什麼需要事務?

如果不配置事務,可能存在數據不一致的情況(例如下面的二、不開啓事務的項目案例)。

如果我們不在spring中去配置聲明式事務,我們就需要在代碼中手動去實現(try-catch)。

注意:事務在項目的開發中十分重要,涉及到數據的一致性和完整性問題!不容馬虎。

二、不開啓事務的案例

還是使用上一篇使用過的項目,不過增加了一些東西。

注意:這個項目會故意製造一個錯誤。

1.項目結構

項目數據庫原本數據爲:

在這裏插入圖片描述

項目結構

在這裏插入圖片描述

2.pom.xml依賴

需要導入的依賴。

使用事務需要導入spring-tx的包,不過新版本spring中的spring-jdbc下導入了spring-tx,就不用再單獨導入了。

    <dependencies>
        <!-- spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>
        <!-- spring要操作數據庫需要導入這個spring-jdbc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>
        <!-- aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
        <!-- mybatis核心包 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.4</version>
        </dependency>
        <!-- mybatis-spring -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.4</version>
        </dependency>
        <!-- mysql驅動包 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.40</version>
        </dependency>
        <!-- junit -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

3.配置文件

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"
       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">

    <!-- 一、導入 -->
    <import resource="spring-dao.xml"/>

    <!-- 創建繼承了SqlSessionDaoSupport的實現類userMapperImpl -->
    <bean id="userMapperImpl" class="com.shengjava.demo.mapper.impl.UserMapperImpl">
        <!-- 需要注入sqlSessionFactory -->
        <property name="sqlSessionFactory" ref="sqlSessionFactory" />
    </bean>

</beans>

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

    <!-- 創建 SqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>
    <!-- 數據源DataSource -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/demo"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>
     
</beans>

mybatis-config.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>
    <!-- 映射器 -->
    <mappers>
        <!--加載映射文件-->
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>

4.實體類

實體類User

public class User {
    private int id;
    private String name;

    public User() {
    }

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

5.接口和實現類

接口UserMapper

public interface UserMapper {
    User selectUser(int id);

    int insUser(User u);

    int delUser(int id);
}

實現類UserMapperImpl

注意:測試的時候會調用selectUser()方法,該方法執行了新增,刪除,查詢。其中刪除語句因爲故意寫錯,所以會拋出異常!

/** 第二種方式:繼承SqlSessionDaoSupport */
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
    @Override
    public User selectUser(int id) {
        UserMapper mapper = getSqlSession().getMapper(UserMapper.class);

        User user = new User(4, "name4");

        mapper.insUser(user);
        mapper.delUser(4);
        return mapper.selectUser(id);
    }

    @Override
    public int insUser(User u) {
        return getSqlSession().getMapper(UserMapper.class).insUser(u);
    }

    @Override
    public int delUser(int id) {
        return getSqlSession().getMapper(UserMapper.class).delUser(id);
    }
}

映射文件UserMapper.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.shengjava.demo.mapper.UserMapper">
    <select id="selectUser" resultType="com.shengjava.demo.pojo.User">
      select * from user where id = #{id};
    </select>
    
    <insert id="insUser" parameterType="com.shengjava.demo.pojo.User">
        insert into user(user_id, user_name) values (#{id}, #{name});
    </insert>
    
    <delete id="delUser" parameterType="int">
        /* 故意將delete語句寫錯 */
        deleteAAA from user where id=#{id};
    </delete>
</mapper>

6.測試

測試類UserMapperTest

public class UserMapperTest {
    @Test
    public void selectUserTest() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper userMapper = context.getBean("userMapperImpl", UserMapper.class);
        User user = userMapper.selectUser(1);
        System.out.println(user);
    }
}

拋出異常信息爲:說明我們剛剛故意寫錯語句成功了。

Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: 
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'deleteAAA from user where id=4' at line 2

查看數據庫:

可以看到,我們剛剛測試調用的selectUser(1)方法拋出了異常。但是,插入卻成功了(方法拋出異常時不應該插入數據),這違背了事務的:要麼都成功!要麼都失敗!這會導致數據出現不一致問題。

怎麼解決呢?使用spring的生命式事務。

在這裏插入圖片描述

三、Spring 聲明式事務

1.增加聲明式事務

我們只需要修改上面案例中的spring-dao.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
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 創建 SqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>
    <!-- 數據源DataSource -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/demo"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>

    <!-- 配置聲明式事務:要開啓 Spring 的事務處理功能,在 Spring 的配置文件中創建一個 DataSourceTransactionManager 對象 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 使用構造器添加數據源 -->
        <constructor-arg ref="dataSource" />
    </bean>

    <!-- 結合AOP實現事務的織入 -->
    <aop:config>
        <!-- 切點,expression:mapper包下所有類,所有方法 -->
        <aop:pointcut id="txPointcut" expression="execution(* com.shengjava.demo.mapper.*.*(..))"/>
        <!-- 配置通知 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>

    <!-- 配置事務通知 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!-- 給哪些方法配置事務,配置事務的傳播特性 -->
        <tx:attributes>
            <!-- name:給所有的xxx方法配置事務。propagation="REQUIRED"爲默認的傳播特性 -->
            <!--
            <tx:method name="ins"/>
            <tx:method name="del"/>
            <tx:method name="upd"/>
            <tx:method name="sel" propagation="REQUIRED" read-only="true"/>
            -->
            <!-- *表示給所有的方法配置事務,-->
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
</beans>

2.測試

我們將上面的數據刪除,重新運行測試類UserMapperTest的測試方法selectUserTest()。

運行還是按照希望的一樣,拋出異常。這時候去刷新數據庫,可以看到數據中該方法中的插入語句沒有成功。實現了事務的:要麼都成功!要麼都失敗!

在這裏插入圖片描述

可以看到在報錯的情況下,插入操作沒有成功,這樣就符合事務的原則了!

備註:如果想要項目不報錯,直接只要把UserMapper.xml文件中的delete語句修改正確就可以了。

相關

我的該分類的其他相關文章,請點擊:【Spring + Spring MVC + MyBatis】文章目錄

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章