事务的简介
Spring事务的本质其实就是数据库对事务的支持,使用JDBC的事务管理机制,就是利用java.sql.Connection对象完成对事务的提交
定义: 事物就是数据库执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。
本质: 由一个或多个sql语句组成。这些sql语句在执行过程中被当作一个整体,要么全部的sql语句执行成功,要么全部失败。不存在一部分执行成功,一部分执行失败。
事务的四大特性
1. 原子性(Atomicity)
原子性是将事物进行的操作捆绑成一个不可分割的单元,事物中进行的数据操作要么全部成功,要么全部失败。
2. 一致性(Consistency)
一致性是指事物使数据库从一个一致性状态转换到另一个一致性状态。也就是数据库前后必须处于一致性状态。
3. 独立性(Isolation)
多个用户并发访问数据库,数据库为每个用户开启一个事物,每个事物相互独立,互不干扰。
事务的隔离性主要规定了各个事务之间相互影响的程度。隔离性概念主要面向对数据资源的并发访问(Concurrency),并兼顾影响事务的一致性。
4. 持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
事务的隔离级别
多个线程开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个线程在获取数据时的准确性,在数据库内部定义了四种隔离级别,用于解决三种隔离问题
事务的隔离级别按照从弱到强分别为“Read Uncommitted (未提交读)”,“Read Committed(提交读)”,“Repeatable Read(可重复读)”和“Serializable(序列化)”。三种问题 脏读 不可重复读 虚度(幻读)
1.Serializable 可避免脏读,不可重复读,虚度(幻读)等情况
2.Repeatable Read 可避免脏读、不可重复读情况的发生。(可重复读)不可以避免虚读(幻读)
3.Read committed:可避免脏读情况发生
4.Read Uncommitted 最低级别,以上问题均无法保证
设置隔离级别的设置
在mysql中设置
查看当前隔离级别: select @@tx_isolation
设置隔离级别: set session transaction isolation level
注:mysql中默认的事务隔离级别是 Repeatable read.
oracle 中默认的事务隔离级别是 Read committed
在jdbc中设置
Controller接口定义事务隔离级别的四个常量:
static int TRANSACTION_READ_COMMITTED
指示不可以发生脏读的常量;不可重复读和虚读可以发生。
static int TRANSACTION_READ_UNCOMMITTED
指示可以发生脏读 (dirty read)、不可重复读和虚读 (phantom read) 的常量。
static int TRANSACTION_REPEATABLE_READ
指示不可以发生脏读和不可重复读的常量;虚读可以发生。
static int TRANSACTION_SERIALIZABLE
指示不可以发生脏读、不可重复读和虚读的常量。
通过 void setTransactionIsolation(int level) 设置数据库隔离级别
安全性:serializable > repeatable read > read committed > read uncommitted
性能:serializable < repeatable read < read committed < read uncommitted
结论: 实际开发中,通常不会选择 serializable 和 read uncommitted ,
mysql默认隔离级别 repeatable read ,oracle默认隔离级别 read committed
Spring中事务的传播行为
Spring在TransactionDefinition接口中规定了7种类型的事务传播行为。事务传播行为是Spring框架独有的事务增强特性,他不属于的事务实际提供方数据库行为。这是Spring为我们提供的强大的工具箱,使用事务传播行可以为我们的开发工作提供许多便利
Spring事务针对的是代理对象,如果调用同一个类中直接调用方法,事务不生效(可以自己注入自己调用)
事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时事务如何传播
第一种: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
Spring配置XML方式进行事务管理(简单小案例)
支持声明式事务管理: 不需要手动编写代码,只需要配置即可
1.创建表
CREATE TABLE `t_account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
`money` double DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
INSERT INTO `t_account` VALUES ('1', 'aaa', '1000');
INSERT INTO `t_account` VALUES ('2', 'bbb', '1000');
2.导入依赖
<dependencies>
<!-- spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.20</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.20</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
</dependency>
<!-- tx 事务 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.20</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
<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.29</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.9</version>
</dependency>
<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>
3.创建AccountController
package com.LSZ.controller;
import com.LSZ.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class AccountController {
@Autowired
private AccountService accountService;
/**
* 转账:accountId1 给 accountId2 转 money
*/
public void transfer(Integer fromAccountId,Integer toAccountId,Double money){
accountService.transfer(fromAccountId,toAccountId,money);
}
}
4.创建AccountService
package com.LSZ.service;
public interface AccountService {
//转账
void transfer(Integer fromAccountId, Integer toAccountId, Double money);
}
5.创建AccountServiceImpl
package com.LSZ.service.impl;
import com.LSZ.mapper.AccountMapper;
import com.LSZ.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
/**
* 转账
* @param fromAccountId
* @param toAccountId
* @param money
*/
@Override
public void transfer(Integer fromAccountId, Integer toAccountId, Double money) {
//从账户转出
accountMapper.decreaseMoney(fromAccountId,money);
//手动模拟异常
//int i = 1/0;
//转入账户
accountMapper.increaseMoney(toAccountId,money);
}
}
6.创建AccountMapper
package com.LSZ.mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface AccountMapper {
/**
* 转出
*
* @param fromAccountId
* @param money
*/
void decreaseMoney(@Param("fromAccountId") Integer fromAccountId, @Param("money") Double money);
/**
* 转入
*
* @param toAccountId
* @param money
*/
void increaseMoney(@Param("toAccountId") Integer toAccountId, @Param("money") Double money);
}
7.创建AccountMapper.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">
<mapper namespace="com.LSZ.mapper.AccountMapper">
<!-- 转出方法 -->
<update id="decreaseMoney">
update t_account set money = money - #{money} where id = #{fromAccountId}
</update>
<!-- 转入方法 -->
<update id="increaseMoney">
update t_account set money = money + #{money} where id = #{toAccountId}
</update>
</mapper>
8.创建db.properties
db.username = root
db.password = root
db.url = jdbc:mysql://localhost:3306/LSZ?serverTimezone=Asia/Shanghai&characterEncoding=UTF8
db.driver = com.mysql.cj.jdbc.Driver
9.创建log4j.properties
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
10.创建mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置日志 -->
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
</configuration>
11.创建applicationContext.xml ( 注意导入 tx 约束 )
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans" 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-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd ">
<!-- 导入外部配置文件 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.qf"></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.qf.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="save*" isolation="REPEATABLE_READ"
propagation="REQUIRED" read-only="false" />
<tx:method name="add*" isolation="REPEATABLE_READ"
propagation="REQUIRED" read-only="false" />
<tx:method name="update*" isolation="REPEATABLE_READ"
propagation="REQUIRED" read-only="false" />
<tx:method name="modify*" isolation="REPEATABLE_READ"
propagation="REQUIRED" read-only="false" />
<tx:method name="delete*" isolation="REPEATABLE_READ"
propagation="REQUIRED" read-only="false" />
<tx:method name="remove*" isolation="REPEATABLE_READ"
propagation="REQUIRED" read-only="false" />
<tx:method name="get*" isolation="REPEATABLE_READ"
propagation="REQUIRED" read-only="true" />
<tx:method name="find*" isolation="REPEATABLE_READ"
propagation="REQUIRED" read-only="true" />
<tx:method name="transfer" isolation="REPEATABLE_READ"
propagation="REQUIRED" read-only="false" />
</tx:attributes>
</tx:advice>
<!-- 配置织入 -->
<aop:config>
<!-- 配置切点表达式 -->
<aop:pointcut expression="execution(* com.qf.service.impl.*ServiceImpl.*(..))" id="txPc" />
<!-- 配置切面 : 通知+切点 advice-ref:通知的名称 pointcut-ref:切点的名称 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPc" />
</aop:config>
</beans>
12.测试文件
package com.LSZ.test;
import com.qf.controller.AccountController;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringTest {
@Test
public void test_transfer(){
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
AccountController accountController =
(AccountController)applicationContext.getBean("accountController");
accountController.transfer(1,2,100d);
}
}
Spring的注解方式进行事务管理
1.创建bean.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.LSZ"></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.LSZ.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>
2.创建AccountServiceImpl
package com.LSZ.service.impl;
import com.LSZ.mapper.AccountMapper;
import com.LSZ.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
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
/**
* 转账
* @param fromAccountId
* @param toAccountId
* @param money
*/
@Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED,readOnly = false)
@Override
public void transfer(Integer fromAccountId, Integer toAccountId, Double money) {
//从账户转出
accountMapper.decreaseMoney(fromAccountId,money);
//手动模拟异常
int i = 1/0;
//转入账户
accountMapper.increaseMoney(toAccountId,money);
}
}
3.测试文件
package com.LSZ.test;
import com.LSZ.controller.AccountController;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringTest {
/**
* xml测试事务
*/
@Test
public void test_transfer(){
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
AccountController accountController =
(AccountController)applicationContext.getBean("accountController");
accountController.transfer(1,2,100d);
}
/**
* 注解测试事务
*/
@Test
public void test_transfer_annotation_driven(){
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("bean.xml");
AccountController accountController =
(AccountController)applicationContext.getBean("accountController");
accountController.transfer(1,2,100d);
}
}