Spring事务管理

事务的四个特性

事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败。

事务四个特性(ACID)

  • (1)原子性
  • (2)一致性
  • (3)隔离性
  • (4)持久性

搭建事务操作环境

(1)创建数据库表:t_account,并添加数据

Spring事务管理

(2)创建 UserDao 和 UserDaoImpl,完成对象创建和注入关系

UserDao:

package com.lalala.spring5.dao;

public interface UserDao {
    //多钱
    public void addMoney();
    //少钱
    public void reduceMoney();
}

UserDaoImpl:

package com.lalala.spring5.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class UserDaoImpl implements UserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    //lucy转账100给mary
    //少钱
    @Override
    public void reduceMoney() {
        String sql = "update t_account set money=money-? where username=?";
        jdbcTemplate.update(sql,100,"lucy");
    }

    //多钱
    @Override
    public void addMoney() {
        String sql = "update t_account set money=money+? where username=?";
        jdbcTemplate.update(sql,100,"mary");
    }
}

创建UserService:

package com.lalala.spring5.service;

import com.lalala.spring5.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    //注入dao
    @Autowired
    private UserDao userDao;

    //转账的方法
    public void accountMoney() {
//        try {
            //第一步 开启事务

            //第二步 进行业务操作
            //lucy少100
            userDao.reduceMoney();

            //模拟异常
            int i = 10/0;

            //mary多100
            userDao.addMoney();

            //第三步 没有发生异常,提交事务
//        }catch(Exception e) {
            //第四步 出现异常,事务回滚
//        }
    }
}

Spring事务管理两种方式

第一种 编程式事务管理(不用)

第二种 声明式事务管理(底层使用AOP)

​ (1)基于xml配置文件实现

​ (2)基于注解实现

Spring事务管理的api介绍

接口 PlatformTransactionManager

Spring事务管理

声明式事务管理(xml配置)

配置文件如下,使用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: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/context http://www.springframework.org/schema/context/spring-context.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">

    <context:component-scan base-package="com.lalala"></context:component-scan>

    <!--数据库连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          destroy-method="close">
        <property name="url" value="jdbc:mysql:///springtest" />
        <property name="username" value="root" />
        <property name="password" value="123456" />
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    </bean>

    <!--JdbcTemplate对象-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--注入 dataSource-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--1.创建事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--2.配置通知-->
    <tx:advice id="txadvice">
        <!--配置事务参数-->
        <tx:attributes>
            <!--指定哪种规则的方法上面添加事务-->
            <tx:method name="accountMoney" propagation="REQUIRED"/>
            <!--<tx:method name="account*"/>-->
        </tx:attributes>
    </tx:advice>

    <!--3.配置切入点和切面-->
    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="pt" expression="execution(* com.lalala.spring5.service.UserService.*(..))"/>
        <!--配置切面-->
        <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
    </aop:config>

</beans>

声明式事务管理(注解)

配置文件如下:

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

    <!-- 组件扫描 -->
    <context:component-scan base-package="com.atguigu"></context:component-scan>

    <!-- 数据库连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          destroy-method="close">
        <property name="url" value="jdbc:mysql:///springtest" />
        <property name="username" value="root" />
        <property name="password" value="123456" />
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    </bean>

    <!-- JdbcTemplate对象 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--注入dataSource-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--创建事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--开启事务注解-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>

在要service类上面添加事务注解:@Transactional

(1)@Transactional 这个注解既可以添加到类上面,也可以添加方法上面

(2)如果把这个注解添加类上面,这个类里面所有的方法都添加事务

(3)如果把这个注解添加方法上面,为这个方法添加事务

package com.lalala.spring5.service;

import com.lalala.spring5.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class UserService {
    //注入dao
    @Autowired
    private UserDao userDao;

    //转账的方法
    public void accountMoney() {
        
            userDao.reduceMoney();

            //模拟异常
            int i = 10/0;

            //mary多100
            userDao.addMoney();
    }
}

注解@Transactional里面可以配置事务相关参数

Spring事务管理

propagation:事务传播行为

多事务方法进行调用,这个过程中事务是如何管理的

事务方法:对数据库表数据进行变化的操作

例如在add方法中调用update方法

Spring事务管理

Spring框架事务传播行为有7种

Spring事务管理

设置事务传播行为示例代码:propagation = Propagation.REQUIRED

@Service
@Transactional(propagation = Propagation.REQUIRED)
public class UserService {
	...
}

ioslation:事务隔离级别

(1)事务有特性成为隔离性,多事务操作之间不会产生影响,不考虑隔离性产生很多问题

(2)有三个读问题:脏读、不可重复读、虚(幻)读

  • 脏读:一个未提交事务读取到另一个未提交事务的数据

  • 不可重复读:一个未提交事务读取到另一提交事务修改的数据

  • 虚读:一个未提交事务读取到另一提交事务添加数据

解决:通过设置事务隔离级别,可以解决读问题

Spring事务管理

设置隔离级别示例代码:isolation = Isolation.REPEATABLE_READ

@Service
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
public class UserService {
	...
}

timeout:超时时间

(1)事务需要在一定时间内进行提交,如果不提交进行回滚

(2)默认值是 -1 ,设置时间以秒单位进行计算

readOnly:是否只读

(1)读:查询操作,写:添加修改删除操作

(2)readOnly 默认值 false,表示可以查询,可以添加修改删除操作

(3)设置 readOnly 值是 true,设置成 true 之后,只能查询

示例代码:readOnly = false

@Service
@Transactional(readOnly = false,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
public class UserService {
	...
}

rollbackFor:回滚

(1)设置出现哪些异常进行事务回滚

noRollbackFor:不回滚

(1)设置出现哪些异常不进行事务回滚

声明式事务管理(完全注解)

创建配置类,使用配置类替代 xml 配置文件

package com.lalala.spring5.config;

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

import javax.sql.DataSource;

@Configuration //配置类
@ComponentScan(basePackages = "com.lalala") //组件扫描
@EnableTransactionManagement //开启事务
public class TxConfig {

    //创建数据库连接池
    @Bean
    public DruidDataSource getDruidDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql:///springtest");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }

    //创建JdbcTemplate对象
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
        //到ioc容器中根据类型找到dataSource
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        //注入dataSource
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    //创建事务管理器
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}

测试代码

@Test
public void testAccount2() {
    ApplicationContext context =
        new AnnotationConfigApplicationContext(TxConfig.class);
    UserService userService = context.getBean("userService", UserService.class);
    userService.accountMoney();
}
上一篇:spring boot中配置hikari连接池属性方式


下一篇:2021-09-19当使用ClassLoader.getSystemClassLoader().getResourceAsStream(“jdbc.properties“);时报空指针异常