Spring框架教程(六):事务管理

一、概念

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

  特点:

  • 原子性:这一组操作必须一起执行或者都不执行。
  • 一致性:保证数据的一致性,比如转出去100块接收方会增加100块。
  • 隔离性:事务之间不能相互影响。
  • 持久性:一旦执行成功,事务对数据库的改变是不可逆的。

二、搭建事务操作环境

1.三层结构

  • web层:给用户看的
  • service层:业务操作
  • dao层:数据库访问和操作

  我们以银行转账为例:首先,dao层需要两个方法——转出方法、转入方法。然后,在service层中需要创建的方法是转账方法,通过调用dao的两个方法实现。

2.具体操作步骤

  • 在数据库spring_db中创建表person,如下:

在这里插入图片描述

  • 我们手动加上两条记录,如下:

在这里插入图片描述

  • 在entity包下创建Person实体类:
	package com.wang.entity;
	
	public class Person {
    
    
	    private String person_id;
	    private String person_name;
	    private int person_money;
	
	    public Person(String person_id, String person_name, int person_money) {
    
    
	        this.person_id = person_id;
	        this.person_name = person_name;
	        this.person_money = person_money;
	    }
	
	    public String getPerson_id() {
    
    
	        return person_id;
	    }
	
	    public Person() {
    
    
	    }
	
	    public void setPerson_id(String person_id) {
    
    
	        this.person_id = person_id;
	    }
	
	    public String getPerson_name() {
    
    
	        return person_name;
	    }
	
	    public void setPerson_name(String person_name) {
    
    
	        this.person_name = person_name;
	    }
	
	    public int getPerson_money() {
    
    
	        return person_money;
	    }
	
	    public void setPerson_money(int person_money) {
    
    
	        this.person_money = person_money;
	    }
	}
  • 在service包和dao包下创建PersonService类、PersonDao接口、PersonDaoImpl实现类,添加相应的注解和具体的方法:
@Service
public class PersonService {
    
    
    @Autowired
    private PersonDao personDao;

    //转账操作
    public boolean transfer(Person p1,Person p2,int money){
    
    
        //先检查p1钱够不够
        if(personDao.queryMoneyById(p1)<money)return false;
        //钱够,转出,转入
        else{
    
    
            personDao.updateMoneyById(p1,money);
            personDao.updateMoneyById(p2,money*(-1));
        }
        return true;
    }
}

public interface PersonDao {
    
    

    void updateMoneyById(Person p2, int i);

    int queryMoneyById(Person p1);
}

@Component
public class PersonDaoImpl implements PersonDao {
    
    
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void updateMoneyById(Person p2, int i) {
    
    
        String sql="update person set person_money=person_money+? where person_id=?";
        Object[] arg={
    
    i,p2.getPerson_id()};
        jdbcTemplate.update(sql,arg);
        System.out.println("转账成功");
    }

    @Override
    public int queryMoneyById(Person p1) {
    
    
        String sql="select ifnull(person_money,0) from person where person_id=?";
        int money=jdbcTemplate.queryForObject(sql,Integer.class,p1.getPerson_id());
        System.out.println("得到id为"+p1.getPerson_id()+"的用户的余额为:"+money);
        return money;
    }
}
  • 测试一下:
	@Test
    public void testPerson(){
    
    
        ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
        PersonService personService=context.getBean("personService",PersonService.class);
        //转账
        Person p1=new Person();Person p2=new Person();
        p1.setPerson_id("1");p2.setPerson_id("2");
        int money=50;
        personService.transfer(p1,p2,money);
    }

在这里插入图片描述

在这里插入图片描述

3.事务场景引入

  我们来看看PersonService类中的转账方法:

 	//转账操作
    public boolean transfer(Person p1,Person p2,int money){
    
    
        //先检查p1钱够不够
        if(personDao.queryMoneyById(p1)<money)return false;
        //钱够,转出,转入
        else{
    
    
            personDao.updateMoneyById(p1,money);
            personDao.updateMoneyById(p2,money*(-1));
        }
        return true;
    }

  设想一下,如果在执行转出操作时,系统发生了异常,可能会造成的一种情况是,p1的账户少了钱,但p2的账户却并没有增加钱,这就是问题所在:异常导致数据不一致。

  为了解决这个问题,我们需要引入事务操作(编程实现),它的一般结构为:

try{
    
    
	//1开启事务

	//2业务操作

	//3若无异常,提交事务
}
catch(Exception e){
    
    
	//4出现异常,事务回滚
}

三、事务管理介绍

  • 把事务管理加到service层(业务逻辑层)是最合适的。
  • 事务管理实现的两种方式:编程式(前一节我们最后给出的就是编程式,很不方便)、声明式(推荐)。
  • 声明式事务管理又分两种:基于注解(推荐)、配置文件。
  • 声明式事务管理,底层使用的是AOP原理。
  • 相关接口PlatformTransactionManager,针对不同的框架有不同的实现类。

四、声明式事务管理

1.注解实现

  • 在配置文件中配置事务管理器:
	<!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
  • 引入tx名称空间:过程略

  • 开启事务注解:

	<!--开启事务注解-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
  • 在service类上,或者service类中方法上添加事务注解@Transactional。若添加到类上,表明类中所有的方法都将成为事务;若添加到方法上,表明只有该方法成为事务。

2.对注解进行参数配置

  @Transactional注解一共有6个参数:

  • propagation:事务传播行为
  • isolation:事务隔离级别
  • timeout:超时时间
  • readOnly:是否只读
  • rollbackFor:回滚
  • noRollbackFor:不回滚

(1)propagation:事务传播行为

  多事务方法之间进行调用,这个过程中事务是如何进行管理的。假设我们有两个方法add和update,add方法内部对update进行调用。

  • 级别一REQUIRED,add本身有事务,调用update之后也使用add中的事务;若add本身无事务,调用update后将在add中新建一个事务。
  • 级别二REQUIRED_NEW,无论add本身是否有事务,调用update后都在add中新建事务。

在这里插入图片描述

  剩下几种级别不要求记,但前两种需要记住。

(2)isolation:事务隔离级别

  事务之间具有隔离性,多事务操作之间不会产生影响,必须考虑事务的隔离性,如果不考虑的话会产生问题:脏读、不可重复读、虚读。

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

在这里插入图片描述

  • 不可重复读:一个事务范围内两个相同的查询却返回了不同数据。。

在这里插入图片描述

  • 虚读/幻读:在一个事务中相同的两次读取,第二次读取到了其他事务新插入的行。

  • 事务隔离级别:是解决上述问题的办法

在这里插入图片描述

(3)timeout:超时时间

  事务需要在一定时间内提交,不提交就回滚。默认值是-1,也就是不超时,单位为秒。

(4)readOnly:是否只读

  默认值是false,表示不仅可以读还可以写,可设置为true,表明只能做查询操作。

(5)rollbackFor:回滚

  设置出现哪些异常进行事务回滚。

(6)noRollbackFor:不回滚

  设置出现哪些异常不进行事务回滚。

3.xml实现

  • 配置事务管理器:tx名称空间、连接池、JdbcTemplate、事务管理器
  • 配置通知:增强部分
	<!--配置通知-->
    <tx:advice id="txadvice">
        <!--配置事务参数-->
        <tx:attributes>
            <!--配置哪些方法,以及相应的事务属性-->
            <!--针对名为acountMoney-->
            <tx:method name="acountMoney" propagation="REQUIRED" isolation="DEFAULT"/>
            <!--针对所有以acount开头的方法名-->
            <tx:method name="account*"/>
        </tx:attributes>
    </tx:advice>
  • 配置切入点和切面
	<!--配置切入点和切面-->
    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="point" expression="execution(* com.wang.service.UserService.*(..))"/>
        <!--配置切面-->
        <aop:advisor advice-ref="txadvice" pointcut-ref="point"/>
    </aop:config>

4.完全注解开发(配置类的写法)

@Configuration
@ComponentScan(basePackages="com.wang")//组件扫描
@EnableTransactionManagement//开启事务
public class txConfig {
    
    
    //创建数据库的连接池
    @Bean
    public DruidDataSource getDruidDataSource(){
    
    
        DruidDataSource dataSource=new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql:///spring_db");
        dataSource.setUsername("root");
        dataSource.setPassword("wang1996526");
        return dataSource;
    }

    //创建JdbcTemplate对象
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource 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;
    }
}

猜你喜欢

转载自blog.csdn.net/Tracycoder/article/details/112916554
今日推荐