Spring事务

一、Spring事务管理

Spring 支持编程式事务管理以及声明式事务管理两种方式。

  • 编程式事务管理是侵入性事务管理,编程式事务每次实现都要单独实现,但业务量大功能复杂时,使用编程式事务无疑是痛苦的,所以并不推荐使用。
  • 声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注解的方式,便可以将事务规则应用到业务逻辑中。声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。显然声明式事务管理要优于编程式事务管理,这正是Spring倡导的非侵入式的编程方式。(必须掌握)

Spring事务的本质其实就是数据库对事务的支持,使用JDBC的事务管理机制就是利用 java.sql.Connection 对象完成对事务的提交

在企业级应用中,多用户访问数据库是常见的场景,这就是所谓的事务的并发。事务并发所可能存在的问题:

  1. 脏读:一个事务读到另一个事务未提交的更新数据。
  2. 不可重复读:一个事务两次读同一行数据,可是这两次读到的数据不一样。
  3. 幻读:一个事务执行两次查询,但第二次查询比第一次查询多出了一些数据行。
  4. 丢失更新:撤消一个事务时,把其它事务已提交的更新的数据覆盖了。

Spring诞生之后,我们再也无需去处理获得连接、关闭连接、事务提交和回滚等这些操作,使得我们把更多的精力放在处理业务上。Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。 Spring事务管理器的接口是 org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现由各个平台自己实现。

事务管理器接口 PlatformTransactionManager 通过 getTransaction(TransactionDefinition definition) 方法来得到事务,这个方法里面的参数是 TransactionDefinition 类,这个类就定义了一些基本的事务属性。事务属性包含了5个方面

  1. 是否为只读?
  2. 传播行为
  3. 隔离规则
  4. 回滚规则
  5. 事务超时

二、事务演示环境准备

1.创建一张表,用来模拟转账的案例

CREATE TABLE `account` (
  `uid` int(10) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `balance` float(255,0) DEFAULT NULL,
  PRIMARY KEY (`uid`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

2.项目结构如下图

3.导入依赖和JdbcTemplate相同

<dependencies>
        <!--引入德鲁伊连接池-->
        <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.8</version>
        </dependency>

        <!--引入MySQL连接依赖-->
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.22</version>
        </dependency>

        <!--spring切面 的包-->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.18</version>
        </dependency>

        <!--springJDBC包-->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.18</version>
        </dependency>

        <!--spring事务控制包-->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.3.18</version>
        </dependency>

        <!--spring-orm映射依赖包-->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>5.3.18</version>
        </dependency>

        <!--织入包  spring-aspects 已经导入该包,这里可以不导入-->
        <!--<dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.7</version>
        </dependency>-->

        <!--aop联盟包-->
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>

        <!--Apache Commons日志包-->
        <!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>

        <!--导入lombok-->
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
            <scope>provided</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>

    </dependencies>
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.xml</include>
                    <include>**/*.properties</include>
                </includes>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>6</source>
                    <target>6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

4.准备配置文件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: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
       http://www.springframework.org/schema/context/spring-context.xsd
">
    <!--开启包扫描-->
    <context:component-scan base-package="com.augus"></context:component-scan>

    <!--读取配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <!--配置德鲁伊连接池-->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${jdbc_username}"></property>
        <property name="password" value="${jdbc_password}"></property>
        <property name="url" value="${jdbc_url}"></property>
        <property name="driverClassName" value="${jdbc_driver}"></property>
    </bean>

    <!--配置JDBCTemplate,并向里面注入druidDataSource-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--通过set方法注入连接池-->
        <property name="dataSource" ref="druidDataSource"></property>
    </bean>
</beans>

5.准备步骤一中创建的表所对应的实体类

package com.augus.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@AllArgsConstructor
@NoArgsConstructor
@Data
public class Account implements Serializable {
    private Integer uid;
    private String name;
    private Double balance;
}

6.准备dao层接口和实现类

package com.augus.dao;

public interface AccountDao {
    //转账
    int transfer(int id, Double money);
}
package com.augus.dao.impl;

import com.augus.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class AccountDaoImpl implements AccountDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public int transfer(int id, Double money) {
        String sql = "update account set balance = balance + ? where uid =?";
        return jdbcTemplate.update(sql,money,id);
    }
}

7.准备service层接口和实现类

package com.augus.service;

public interface AccountService {
    //转账
    int transfer(int from, int to, Double money);
}
package com.augus.service.impl;

import com.augus.dao.AccountDao;
import com.augus.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Override
    public int transfer(int from, int to, Double money) {
        //统计执行的次数
        int rows = 0;

        //将dao层的方法调用两次,一个加一个减少
        rows += accountDao.transfer(from, -money); //这里是减少的意思
        rows += accountDao.transfer(to,money);
        return rows;
    }
}

8.测试代码

import com.augus.service.AccountService;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test1 {
    @Test
    public void testTransaction(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //获取对象
        AccountService accountService = context.getBean(AccountService.class);
        //调用方法
        int rs = accountService.transfer(1, 2, 500.0);
        System.out.println(rs);
    }

}

三、声明式事务管理( 基于注解方式实现 )

1.注解方式实现事务控制需要在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: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
       http://www.springframework.org/schema/context/spring-context.xsd

       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd
">
    <!--开启包扫描-->
    <context:component-scan base-package="com.augus"></context:component-scan>

    <!--读取配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <!--配置德鲁伊连接池-->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${jdbc_username}"></property>
        <property name="password" value="${jdbc_password}"></property>
        <property name="url" value="${jdbc_url}"></property>
        <property name="driverClassName" value="${jdbc_driver}"></property>
    </bean>

    <!--配置JDBCTemplate,并向里面注入druidDataSource-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--通过set方法注入连接池-->
        <property name="dataSource" ref="druidDataSource"></property>
    </bean>
    
    <!--配置一个事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--将数据源注入到事务控制器中-->
        <property name="dataSource" ref="druidDataSource"></property>
    </bean>
    
    <!--开启事务注解-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>

2.在Service层中添加事务的注解

package com.augus.service.impl;

import com.augus.dao.AccountDao;
import com.augus.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
//@Transactional //放在类中,会对该类中的所有方法都添加事务控制
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Override
    @Transactional //放在方法上,表示仅仅对当前方法增加了事务控制
    public int transfer(int from, int to, Double money) {
        //统计执行的次数
        int rows = 0;

        //将dao层的方法调用两次,一个加一个减少
        rows += accountDao.transfer(from, -money); //这里是减少的意思
        //模拟产生异常,这里发生异常,如果没有事务就会出现一个转走了,另一个没收到钱
        int i = 1/0;
        rows += accountDao.transfer(to,money);
        return rows;
    }
}

3.测试代码

还是之前环境准备的测试代码。但是执行会发现,加上事务控制后。如果再有异常,就会回滚事务

import com.augus.service.AccountService;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test1 {
    @Test
    public void testTransaction(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //获取对象
        AccountService accountService = context.getBean(AccountService.class);
        //调用方法
        int rs = accountService.transfer(1, 2, 500.0);
        System.out.println(rs);
    }

}

四、声明式事务管理( 基于xml方式实现 )

1.applicationContext中,通过AOP实现事务的控制

<?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" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--开启包扫描-->
    <context:component-scan base-package="com.augus"></context:component-scan>

    <!--读取配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <!--配置德鲁伊连接池-->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${jdbc_username}"></property>
        <property name="password" value="${jdbc_password}"></property>
        <property name="url" value="${jdbc_url}"></property>
        <property name="driverClassName" value="${jdbc_driver}"></property>
    </bean>
    <!--配置JDBCTemplate,并向里面注入druidDataSource-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--通过set方法注入连接池-->
        <property name="dataSource" ref="druidDataSource"></property>
    </bean>
    <!--配置一个事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--将数据源注入到事务控制器中-->
        <property name="dataSource" ref="druidDataSource"></property>
    </bean>

    <!--配置通知-->
    <tx:advice id="interceptor">
        <!--配置事务参数-->
        <tx:attributes>
            <!--
            propagation= REQUIRED: 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
            propagation= REQUIRES_NEW : 新建事务,如果当前存在事务,把当前事务挂起。
            -->
            <tx:method name="transfer" isolation="DEFAULT" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <!--配置AOP-->
    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="tr" expression="execution(* com.augus.service.AccountService.transfer(..))"/>
        <!--配置切面-->
        <aop:advisor advice-ref="interceptor" pointcut-ref="tr"></aop:advisor>
    </aop:config>

</beans>

2.service层实现类

上面xml方式配置后,service层中实现类就不需要@Transactional注解

package com.augus.service.impl;

import com.augus.dao.AccountDao;
import com.augus.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Override
    public int transfer(int from, int to, Double money) {
        //统计执行的次数
        int rows = 0;

        //将dao层的方法调用两次,一个加一个减少
        rows += accountDao.transfer(from, -money); //这里是减少的意思
        //模拟产生异常,这里发生异常,如果没有事务就会出现一个转走了,另一个没收到钱
        int i = 1/0;
        rows += accountDao.transfer(to,money);
        return rows;
    }
}

3.测试代码

跟之间一样没有改动,只需要测试,有异常是否会回滚

import com.augus.service.AccountService;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test1 {
    @Test
    public void testTransaction(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //获取对象
        AccountService accountService = context.getBean(AccountService.class);
        //调用方法
        int rs = accountService.transfer(1, 2, 500.0);
        System.out.println(rs);
    }

}

五、零xml实现事务控制(使用配置类实现事务控制)

1.创建配置类

package com.augus.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration //配置类标志注解
@ComponentScan(basePackages = "com.augus") //开启spring包扫描
@PropertySource("classpath:jdbc.properties") //读取属性配置文件
@EnableTransactionManagement //开启事务注解
public class SpringConfig {

    //将读取回来赋值给属性
    @Value("${jdbc_driver}")
    private String driver;
    @Value("${jdbc_url}")
    private String url;
    @Value("${jdbc_username}")
    private String username;
    @Value("${jdbc_password}")
    private String password;

    /*创建数据库连接池*/
    @Bean
    public DruidDataSource getDruidDataSource(){
        //创建连接对象
        DruidDataSource druidDataSource = new DruidDataSource();
        //设置属性
        druidDataSource.setDriverClassName(driver);
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        return druidDataSource;
    }

    //创建JdbcTemplate对象
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        //设置数据连接池
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    //创建事务管理器
    @Bean
    public PlatformTransactionManager getPlatformTransactionManager(DataSource dataSource){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        //设置连接池
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}

2.service层实现类添加事务注解

package com.augus.service.impl;

import com.augus.dao.AccountDao;
import com.augus.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Override
    @Transactional //对该方法开启事务
    public int transfer(int from, int to, Double money) {
        //统计执行的次数
        int rows = 0;

        //将dao层的方法调用两次,一个加一个减少
        rows += accountDao.transfer(from, -money); //这里是减少的意思
        //模拟产生异常,这里发生异常,如果没有事务就会出现一个转走了,另一个没收到钱
        int i = 1/0;
        rows += accountDao.transfer(to,money);
        return rows;
    }
}

3.测试代码

import com.augus.config.SpringConfig;
import com.augus.service.AccountService;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test2 {
    @Test
    public void testTransaction(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        //获取对象
        AccountService accountService = context.getBean(AccountService.class);
        //调用方法
        int rs = accountService.transfer(1, 2, 500.0);
        System.out.println(rs);
    }

}
posted @ 2019-10-23 14:44  酒剑仙*  阅读(1075)  评论(0编辑  收藏  举报