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文件中配置的事务管理机制是一样的,在以后的开发中,注解是使用最广泛的,暂时理解这么多,妥了!!!