文章目录
- 前言
- 一、事务的特性(ACID)
- 二、事务的隔离级别
- 三、spring中的事务
- 平台事务管理器.
- 事务定义
- ISOLation_XXX:**事务隔离级别.**
- PROPAGATION_XXX:**事务的传播行为**.
- 事务状态
- 关系:
- 四、使用XML文件配置事务
- 1、 搭建环境
- 2、 配置及业务编写
- 3、 测试
- 五、使用注解形式进行事务开发
- 总结
前言
事务是逻辑上的一组操作的集合,要么全部成功,要么全部失败。在实际的业务场景中,事务的应用场景范围很广泛,例如转账,订单,支付等
一、事务的特性(ACID)
事务有四大特性:
- 原子性:事务不可分割
- 一致性:事务执行的前后,数据完整性保持一致.
- 隔离性:一个事务执行的时候,不应该受到其他事务的打扰
- 持久性:一旦结束,数据就永久的保存到数据库.
二、事务的隔离级别
在实际的开发中,可能会出现以下几种由数据操作引起的异常情况:
如果不考虑隔离性:
- 脏读:一个事务读到另一个事务未提交数据
- 不可重复读:一个事务读到另一个事务已经提交数据(update)导致一个事务多次查询结果不一致
- 虚读:一个事务读到另一个事务已经提交数据(insert)导致一个事务多次查询结果不一致
事务存在几种隔离级别:
- 未提交读:以上情况都有可能发生。
- 已提交读:避免脏读,但不可重复读,虚读是有可能发生。
- 可重复读:避免脏读,不可重复读,但是虚读有可能发生。
- 串行化:避免以上所有情况.
三、spring中的事务
后端分层开发中,事务应用于Service层中。spring提供了事务管理API
平台事务管理器.
PlatformTransactionManager
- getTransaction(TransactionDefinition definition) :开启事务
- rollback(TransactionStatus status) :回滚事务
- commit(TransactionStatus status) :提交事务
Spring为不同的持久化框架提供了不同PlatformTransactionManager接口实现
使用Spring JDBC或iBatis 进行持久化数据时使用(重点)
- org.springframework.jdbc.datasource.DataSourceTransactionManager
使用Hibernate进行持久化数据时使用
- org.springframework.orm.hibernate.HibernateTransactionManager
使用JPA进行持久化时使用
- org.springframework.orm.jpa.JpaTransactionManager
当持久化机制是Jdo时使用
- org.springframework.jdo.JdoTransactionManager
使用一个JTA实现来管理事务,在一个事务跨越多个资源时必须使用
- org.springframework.transaction.jta.JtaTransactionManager
事务定义
TransactionDefinition
ISOLation_XXX:事务隔离级别.
ISOLATION_DEFAULT:默认级别. Mysql --> repeatable_read | Oracle -->> read_commited
级别如下:
- ISOLATION_READ_UNCOMMITTED : 读未提交
- ISOLATION_READ_COMMITTED : 读已提交
- ISOLATION_REPEATABLE_READ :可重复读
- ISOLATION_SERIALIZABLE :串行化
PROPAGATION_XXX:事务的传播行为.
事务的传播行为是用来解决实际开发中的问题
传播行为是指:解决业务层调用事务的关系。
- PROPAGATION_REQUIRED: 支持当前事务,如果不存在 就新建一个
A,B 如果A有事务,B使用A的事务,如果A没有事务,B就开启一个新的事务.(A,B是在一个事务中。)
- PROPAGATION_SUPPORTS: 支持当前事务,如果不存在,就不使用事务
A,B 如果A有事务,B使用A的事务,如果A没有事务,B就不使用事务.
- PROPAGATION_MANDATORY: 支持当前事务,如果不存在,抛出异常
A,B 如果A有事务,B使用A的事务,如果A没有事务,抛出异常.
- PROPAGATION_REQUIRES_NEW: 如果有事务存在,挂起当前事务,创建一个新的事务
A,B 如果A有事务,B将A的事务挂起,重新创建一个新的事务.(A,B不在一个事务中.事务互不影响.)
- PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果有事务存在,挂起当前事务
A,B 非事务的方式运行,A有事务,就会挂起当前的事务.
-
PROPAGATION_NEVER: 以非事务方式运行,如果有事务存在,抛出异常
-
PROPAGATION_NESTED: 如果当前事务存在,则嵌套事务执行
基于SavePoint技术.
A,B A有事务,A执行之后,将A事务执行之后的内容保存到SavePoint.B事务有异常的话,用户需要自己设置事务提交还是回滚.
- 常用:(重点)
PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
PROPAGATION_NESTED
事务状态
TransactionStatus
-
是否有保存点
-
是否是一个新的事务
-
事务是否已经提交
关系:
关系:PlatformTransactionManager通过TransactionDefinition设置事务相关信息管理事务,管理事务过程中,产生一些事务状态,状态由TransactionStatus记录。
四、使用XML文件配置事务
spring支持声明式事务管理,只需要配置文件,不需要额外的编写代码
演示如下:
1、 搭建环境
创建account表并插入两条数据
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`money` double DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
INSERT INTO `account` VALUES ('1', '1000');
INSERT INTO `account` VALUES ('2', '1000');
新建项目略
导入pom.xml依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wmj</groupId>
<artifactId>SpringTX</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- spring相关依赖,版本必须保持一致 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.24</version>
</dependency>
<!--spring数据连接支持 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.24</version>
</dependency>
<!--aop支持 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
</dependency>
<!-- 事务 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.24</version>
</dependency>
<!-- mybatis相关依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
<!-- myabtis整合spring依赖,必须导入 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
<!--德鲁伊数据连接池支持 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.9</version>
</dependency>
<!--lombok注解支持 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<!--log4j日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
2、 配置及业务编写
配置mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置日志 -->
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
</configuration>
配置日志
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
配置数据源
db.username = 帐号
db.password = 密码
db.url = jdbc:mysql:///库名?serverTimezone=Asia/Shanghai&characterEncoding=UTF8
db.driverClassName = com.mysql.cj.jdbc.Driver
搭建后端业务代码
控制层
package com.wmj.controller;
import com.wmj.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
/**
* --效率,是成功的核心关键--
*
* @Author lzl
* @Date 2023/2/13 16:04
*/
@Controller
public class TestController {
@Autowired
private TestService service;
public void testTx(Integer id1,Integer id2,Double money){
service.transferMoney(id1,id2,money);
}
}
业务层接口及其实现类
package com.wmj.service;
/**
* --效率,是成功的核心关键--
*
* @Author lzl
* @Date 2023/2/13 16:10
*/
public interface TestService {
void transferMoney(Integer id1, Integer id2, Double money);
}
package com.wmj.service.impl;
import com.wmj.mapper.TestMapper;
import com.wmj.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* --效率,是成功的核心关键--
*
* @Author lzl
* @Date 2023/2/13 16:11
*/
@Service
public class TestServiceImpl implements TestService {
@Autowired
private TestMapper mapper;
@Override
public void transferMoney(Integer id1, Integer id2, Double money) {
mapper.dmoney(id1,money);
int a = 1/0;
mapper.amoney(id2,money);
}
}
持久层接口及其xml数据访问
package com.wmj.mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
/**
* --效率,是成功的核心关键--
*
* @Author lzl
* @Date 2023/2/13 16:12
*/
@Repository
public interface TestMapper {
Integer dmoney(@Param("id1") Integer id1, @Param("money") Double money);
Integer amoney(@Param("id2") Integer id2, @Param("money") Double money);
}
<?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">
<mapper namespace="com.wmj.mapper.TestMapper">
<update id="dmoney">
update account set money = money - #{money} where id = #{id1}
</update>
<update id="amoney">
update account set money = money + #{money} where id = #{id2}
</update>
</mapper>
配置spring核心配置文件application.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: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/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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- bean definitions here -->
<!-- 扫描包下的注解 -->
<context:component-scan base-package="com.wmj"></context:component-scan>
<!-- 引入db.properties配置文件 -->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!-- 配置数据库链接 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${db.username}"></property>
<property name="password" value="${db.password}"></property>
<property name="url" value="${db.url}"></property>
<property name="driverClassName" value="${db.driverClassName}"></property>
</bean>
<!-- 配置SqlSession -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- dataSource属性(必选属性) -->
<property name="dataSource" ref="dataSource"></property>
<!-- configLocation属性配置mybatis-config.xml(非必选属性)-->
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<!-- mapperLocations属性用于配置Mapper.xml文件所在位置 (非必选属性)-->
<property name="mapperLocations" value="classpath:mapper/*.xml"></property>
</bean>
<!-- 扫描Mapper -->
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.wmj.mapper"></property>
</bean>
<!-- 事务核心管理器,封装了所有事务操作. 依赖于连接池 -->
<bean name="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 企业中配置CRUD方法一般使用方法名+通配符*的形式配置通知,此时类中的方法名要和配置的方法名一致 -->
<!-- 以方法为单位,指定方法应用什么事务属性
isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
-->
<tx:method name="transferMoney" isolation="REPEATABLE_READ"
propagation="REQUIRED" read-only="false" />
</tx:attributes>
</tx:advice>
<!-- 配置织入 -->
<aop:config>
<!-- 配置切点表达式 -->
<aop:pointcut expression="execution(* com.wmj.service.impl.*ServiceImpl.*(..))"
id="txPc" />
<!-- 配置切面 : 通知+切点 advice-ref:通知的名称 pointcut-ref:切点的名称 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPc" />
</aop:config>
</beans>
3、 测试
package com.test;
import com.wmj.controller.TestController;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* --效率,是成功的核心关键--
*
* @Author lzl
* @Date 2023/2/13 16:02
*/
public class Demo1 {
@Test
public void testTX(){
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("application.xml");
TestController controller = (TestController)applicationContext.getBean("testController");
controller.testTx(1,2,200.00);
}
}
执行结果:
当没有遇到异常时,转账能正常执行,不在演示,此处演示当遇到异常时的事务效果
代码执行前数据库:
当遇到异常,代码终止执行,此时数据库
转账并没有进行提交,此时说明我们的事务配置成功!
五、使用注解形式进行事务开发
spring给我们提供了事务注解开发的方式,大大减少了代码编写,十分方便
创建项目配置基本与上方相同,我们将application.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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->
<!-- 导入外部配置文件 db.properties -->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!-- 配置数据源对象 -->
<bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
<!-- 导入 db.properties 中的值-->
<property name="username" value="${db.username}"></property>
<property name="password" value="${db.password}"></property>
<property name="url" value="${db.url}"></property>
<property name="driverClassName" value="${db.driver}"></property>
</bean>
<!-- 扫描对应包下的注解 -->
<context:component-scan base-package="com.wmj"></context:component-scan>
<!-- 配置sqlSessionFactory -->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 必选配置 -->
<property name="dataSource" ref="datasource"></property>
<!-- 非必选属性,根据自己需求去配置 -->
<!-- 导入 mybatis-config.xml -->
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<!-- 导入 Mapper.xml 文件,classpath后面不能有空格 -->
<property name="mapperLocations" value="classpath:mapper/*.xml"></property>
</bean>
<!-- 扫描 Mapper 接口,生成代理对象 -->
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定扫描的具体位置 -->
<property name="basePackage" value="com.wmj.mapper"></property>
</bean>
<!-- 配置事务 -->
<!-- 事务平台管理器,封装了所有的事务操作,依赖数据源 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"></property>
</bean>
<!-- 开启注解驱动事务支持 -->
<tx:annotation-driven></tx:annotation-driven>
</beans>
将业务实现类改造如下:
package com.wmj.service.impl;
import com.wmj.mapper.AccountMapper;
import com.wmj.service.AccountService;
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;
/**
* @Transactional 配置事务
* 可以写在类上,也可以写在方法上
*/
//@Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED,readOnly = false)
@Service
package com.wmj.service.impl;
import com.wmj.mapper.TestMapper;
import com.wmj.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* --效率,是成功的核心关键--
*
* @Author lzl
* @Date 2023/2/13 16:11
*/
/**
* @Transactional 配置事务
* 可以写在类上,也可以写在方法上
*/
//@Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED,readOnly = false)
@Service
public class TestServiceImpl implements TestService {
@Autowired
private TestMapper mapper;
@Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED,readOnly = false)
@Override
public void transferMoney(Integer id1, Integer id2, Double money) {
mapper.dmoney(id1,money);
int a = 1/0;
mapper.amoney(id2,money);
}
}
测试略
总结
本次主要记录了spring的事务支持,及其两种开发方式。再会!!!