Spring系列(八)、Spring事务管理机制

spring与mybatis整合配置可参考上篇Spring系列(七)、Spring与MyBatis框架整合

8 Spring事务管理机制

在spring中事务是自动提交的,但是我们在操作数据的时候,总有些业务流程需要事务控制。

在项目中,业务层(Service层)既是处理业务的地方,业务层编写又是管理数据库事务的地方,要对事务进行测试,首先创建业务层,并在业务层编写添加用户取款、存款和转账操作的代码后,有意的咱取款处添加一行异常代码(如:int i = 1/0;)或者在数据库中添加一个账户余额最小不低于1元的约束条件,来模拟现实中的意外情况,最后编码测试方法,这样程序在执行到错误代码时就会出现异常,在没有事务管理的情况下,即使出现了取款(转出)异常,存款方依然成功存入了(转入)转账金额,这样银行就亏了;如果添加了事务管理机制,并且事务管理的配置正确,那么在执行上述操作时,转账双方,只要有任意一方在执行业务的过程中出现了异常,事务就会发生回滚,保证事务同时成功或失败!

分析:

  • 可以采用MyBatis控制事务
  • 事务应该在业务逻辑层控制
  • 硬编码方式,代码繁琐,且破坏分层,代码不易维护

提示:

  • 可以采用AOP的方式实现
  • Spring提供了声明式事务支持

8.1 声明式事务关注的核心问题

对哪些方法,采取什么样的事务策略;

配置步骤:

  • 导入tx和aop命名空间;
  • 定义事务管理Bean,并为其注入数据源Bean;
  • 通过< tx:advice>配置事务增强,绑定事务管理器并针对不同方法定义事务规则;
  • 配置切面,将事务增强与方法切入点组合。

8.2 事务传播机制

propagation有以下属性:

参数作用
REQUIERD如果当前没有事务,就新建一个事务;若果已存在一个事务,加入到这个事务中,这是最常见的选择
SUPPORTS支持当前事务,如果当前没有事务,就以非事务方法执行
MANDATORY使用当前事务,如果没有当前事务,就抛出异常
REQUIERD_NEW新建事务,如果当前存在事务,就把当前事务挂起
NOT_SOPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
NEVER以非事务方式执行操作,如果当前事务存在则抛出异常
NESTED则在嵌套事务内执行;如果当前没有事务,则执行与REQUIRED类似的事务

8.3 创建数据库account表

-- Create table
create table ACCOUNT
(
  accountid    VARCHAR2(20),
  accountname  VARCHAR2(20),
  accountmoney NUMBER(11)
)

-- 添加约束(账户金额不能低于1元)
alter table ACCOUNT add constraint CK_ACCOUNT_MONEY check (accountmoney>=1);

--添加两条数据
insert into account values('1001','张三',100);
insert into account values('1002','李四',1);

8.4 创建实体类Account

package com.pojo;
import java.io.Serializable;

/**
 * @author 一宿君(CSDN : qq_52596258)
 * @date 2021-07-20 10:18:37
 */
public class Account implements Serializable {
    private String accountid;
    private String accountname;
    private float accountmoney;

    public String getAccountid() {
        return accountid;
    }

    public void setAccountid(String accountid) {
        this.accountid = accountid;
    }

    public String getAccountname() {
        return accountname;
    }

    public void setAccountname(String accountname) {
        this.accountname = accountname;
    }

    public float getAccountmoney() {
        return accountmoney;
    }

    public void setAccountmoney(float accountmoney) {
        this.accountmoney = accountmoney;
    }
}

8.5 创建接口类AccountDaoMapper

package com.dao;

import org.apache.ibatis.annotations.Param;

import javax.ws.rs.PATCH;

/**
 * @author 一宿君(CSDN : qq_52596258)
 * @date 2021-07-20 10:20:04
 */
public interface AccountDaoMapper {

    /**
     * 取款
     * @param accountid 取款账户id
     * @param money  取款金额
     * @return
     */
    public int takeMoney(@Param("accountid") String accountid, @Param("money") float money);

    /**
     * 存款
     * @param accountid 存款账户id
     * @param money  存款金额
     * @return
     */
    public int saveMoney(@Param("accountid") String accountid,@Param("money")float money);

}

8.6 创建接口映射文件AccountDaoMapper.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">

<!--namespace是命名空间,要与对应的接口的路径保持一致(如果没有创建该接口,MyBatis在内部编译时会自动创建)-->
<mapper namespace="com.dao.AccountDaoMapper">

    <!--取款-->
    <update id="takeMoney">
        update account set accountmoney = accountmoney - #{money} where accountid = #{accountid}
    </update>

    <!--存款-->
    <update id="saveMoney">
        update account set accountmoney = accountmoney + #{money} where accountid = #{accountid}
    </update>

</mapper>

8.7 创建业务逻辑层接口AccountService

package com.service;

/**
 * @author 一宿君(CSDN : qq_52596258)
 * @date 2021-07-20 10:30:13
 */
public interface AccountService {

    /**
     * 转账
     * @param accountid1 取款id
     * @param accountid2 存款id
     * @param money 转账金额
     */
    public int transMoney(String accountid1,String accountid2,float money);
}

8.8 创建业务逻辑层接口实现类AccountServiceImpl

package com.service.impl;

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

import javax.ejb.TransactionManagement;


/**
 * @author 一宿君(CSDN : qq_52596258)
 * @date 2021-07-20 10:32:59
 */
@Service
@Transactional(propagation = Propagation.REQUIRED)
public class AccountServiceImpl implements AccountService {

    @Autowired
    AccountDaoMapper accountDaoMapper;

    /**
     * 取款
     * @param accountid1 取款id
     * @param accountid2 存款id
     * @param money 转账金额
     */
    @Override
    public int transMoney(String accountid1, String accountid2, float money) {

        //如果先取款
        int r1 = accountDaoMapper.takeMoney(accountid1,money);
        //后存款
        int r2 = accountDaoMapper.saveMoney(accountid2,money);

        return 1;
    }
}

在这里插入图片描述

8.9 创建测试类TestTransactionManager(在没有开启事务管理时测试转账)

package com.test;

import com.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author 一宿君(CSDN : qq_52596258)
 * @date 2021-07-20 10:47:10
 */
public class TestTransactionManager {
    public static void main(String[] args) {

        ApplicationContext ap = new ClassPathXmlApplicationContext("applicationContext.xml");
        AccountService accountService = (AccountService)ap.getBean("accountServiceImpl");
        String accountid1 = "1001";//张三账户id
        String accountid2 = "1002";//李四账户id
        float money = 100;//转账金额
        int r = accountService.transMoney(accountid1,accountid2,money);
        System.out.println();
    }
}

执行结果:
在这里插入图片描述
在这里插入图片描述
上述没有开启事务管理,转账失败了,是因为违反了accountmoney键的约束,不能低于1元,因为张三要转出100元,他的账户中只有100元,所以转出后零,就违反了约束条件,所以转账失败!

8.10 当存款在取款之前执行,仍然在没有开启事务管理下测试转账

在这里插入图片描述
执行结果:
在这里插入图片描述
在这里插入图片描述

要是出现上述情况,张三能高兴死!银行能亏死!所以我们要引入事务机制,要保证事务能够同时成功或同时失败!

8.11 引入事务机制,基于Aop切面实现事务机制(applicationConotext.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:bean="http://www.springframework.org/schema/aop"
       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/aop https://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">

    <!--加载连接Oracle数据库的参数配置文件-->
    <context:property-placeholder location="database.properties"/>

    <!--配置数据库连接池(使用alibaba提供的druid连接池——德鲁斯)-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!--此处我们不适用spring内部提供的jdbc连接数据库,所以jdbc.driverClassName不需要引入-->
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="filters" value="${jdbc.filters}"/>
        <property name="maxActive" value="${jdbc.maxActive}"/>
        <property name="initialSize" value="${jdbc.initialSize}"/>
        <property name="maxWait" value="${jdbc.maxWait}"/>
        <property name="minIdle" value="${jdbc.minIdle}"/>
        <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/>
        <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/>
        <property name="validationQuery" value="${jdbc.validationQuery}"/>
        <property name="testWhileIdle" value="${jdbc.testWhileIdle}"/>
        <property name="testOnBorrow" value="${jdbc.testOnBorrow}"/>
        <property name="testOnReturn" value="${jdbc.testOnReturn}"/>
        <property name="poolPreparedStatements" value="${jdbc.poolPreparedStatements}"/>
        <property name="maxPoolPreparedStatementPerConnectionSize"
                  value="${jdbc.maxPoolPreparedStatementPerConnectionSize}"/>
    </bean>

    <!--使用mybatis操作数据库,首先要解析xml文件,
    然后要根据sqlSessionFactoryBean工厂创建sqlSessionFactory,
    最后由sqlSessionFactory创建sqlSession来操作数据库-->

    <!--在spring中,mybatis将操作数据库的控制权全权交给了spring管理-->
    <!--配置sqlSessionFactory工厂-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--加载数据源-->
        <property name="dataSource" ref="dataSource"/>
        <!--读取mybatis的配置文件(此处是为了以后使用mybatis扩展业务,此文件中可以什么都不做)-->
        <property name="configLocation" value="mybatis-conf.xml"/>
    </bean>

    <!--指定Spring到哪个包下扫描MyBatis操作数据库的映射文件-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.dao"/>
    </bean>

    <!--开启注解扫描——指定Spring到哪个包下扫描业务逻辑注解组件类-->
    <context:component-scan base-package="com.service"/>


    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置事务传播机制-->
     <tx:advice id="txAdvice">
         <tx:attributes>
             <!--propagation 事务传播机制-->
             <tx:method name="trans*" propagation="REQUIRED"/>
             <tx:method name="save*" propagation="REQUIRED"/>
             <tx:method name="update*" propagation="REQUIRED"/>
             <tx:method name="add*" propagation="REQUIRED"/>
             <tx:method name="modify*" propagation="REQUIRED"/>
             <tx:method name="del*" propagation="REQUIRED"/>
             <tx:method name="remove*" propagation="REQUIRED"/>
             <tx:method name="get*" propagation="SUPPORTS"/>
             <tx:method name="find*" propagation="SUPPORTS"/>
             <tx:method name="search*" propagation="SUPPORTS"/>
         </tx:attributes>
     </tx:advice>


     <!--定义切面-->
     <aop:config>
         <!--定义切入点-->
         <aop:pointcut id="pointcutServiceAdvice" expression="execution(* com.service..*.*(..))"/>
         <!--开启通知-->
         <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcutServiceAdvice"/>
     </aop:config>

</beans>

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 再次执行测试类,查看控制台信息:
    在这里插入图片描述
    在这里插入图片描述
    我们再次查看数据库表记录:
    在这里插入图片描述

8.12 使用注解方式实现事务管理机制

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

业务逻辑层实现类AccountServiceImpl

package com.service.impl;

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

import javax.ejb.TransactionManagement;


/**
 * @author 一宿君(CSDN : qq_52596258)
 * @date 2021-07-20 10:32:59
 */
@Service
@Transactional(propagation = Propagation.REQUIRED)
public class AccountServiceImpl implements AccountService {

    @Autowired
    AccountDaoMapper accountDaoMapper;

    /**
     * 取款
     * @param accountid1 取款id
     * @param accountid2 存款id
     * @param money 转账金额
     */
    @Override
    /*@Transactional(propagation = Propagation.REQUIRED)*/
    public int transMoney(String accountid1, String accountid2, float money) {

        //如果先存款
        int r2 = accountDaoMapper.saveMoney(accountid2,money);
        //后取款
        int r1 = accountDaoMapper.takeMoney(accountid1,money);

        return 1;
    }
}

在这里插入图片描述
在此测试TestTransactionManager类:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

效果和上述在xml文件中配置的事务管理机制是一样的,在以后的开发中,注解是使用最广泛的,暂时理解这么多,妥了!!!

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一宿君

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值