文章目录
1. Spring5 事务
什么是事务?
事务是数据库操作最基本单元。在逻辑上,就是一组操作。要么都成功,如果有一个失败那么都失败。
事务的四个特性:
原子性,一致性,隔离性,持久性。
我们经常遇到的一个案例,就是银行转账的问题。
就像下面一个转账的时候,就应该一个是加钱,一个是减钱,才对。但是如果中间出现问题了!那么就使用事务来做一个回滚操作!
案例如下:
先创建一个数据库来测试使用:
之后,在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>org.example</groupId>
<artifactId>spring5_atguigu_transaction</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.14</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.13</version>
</dependency>
<!--spring tx 不知道干啥的。-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--spring orm 不知道干啥的-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
</dependencies>
</project>
配置好beans.xml文件,注意在idea中beans.xml命名时,前方有空格是不会报错的!一定注意不要有空格。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.itholmes"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/user_db?ServerTimezone=Aisa/shanghai"/>
<property name="username" value="root"/>
<property name="password" value="0818"/>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
创建dao层一个接口一个实现类,来操作数据库中的内容:
package com.itholmes.dao;
public interface UserDao {
public void addMoney();
public void reduceMoney();
}
package com.itholmes.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class UserDaoImpl implements UserDao{
@Autowired
private JdbcTemplate jdbcTemplate;
//少钱(lucy转账100)
@Override
public void reduceMoney() {
String sql = "update t_account set money=money-? where username=?";
jdbcTemplate.update(sql, 100, "lucy");
}
//多钱(mery收账100)
@Override
public void addMoney() {
String sql = "update t_account set money=money+? where username=?";
jdbcTemplate.update(sql, 100, "mery");
}
}
再创建service层中的UserService类,这里为了方便不设置接口来实现了,都一样:
package com.itholmes.service;
import com.itholmes.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
//注入dao
@Autowired
private UserDao userDao;
//在service业务逻辑层中调用转账方法
public void accountMoney() {
//lucy转账100
userDao.reduceMoney();
//模拟异常,比如断电了,断网了等等。
int i = 1 / 0;
//mery收账100
userDao.addMoney();
}
}
测试类来测试:
import com.itholmes.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService bean = context.getBean("userService", UserService.class);
bean.accountMoney();
}
}
就像上面的效果,这个时候就需要在service使用事务来处理了!!!事务在哪层都可以,但是我们最好是使用在业务逻辑service层的。
2. Spring5 事务操作
通常我们事务是添加到Service层(业务逻辑层)。
在Spring 进行事务管理操作有两种方式:
- 编程式事务管理
- 声明式事务管理(使用最多)
编程式事务管理就是使用代码一步一步来进行事务的操作,这种方式很麻烦!并且代码非常冗余。
就像try-catch方法一样,来处理如下面情况:
声明式事务管理又分为基于注解方式(最常用)和基于xml配置文件方式。
而在Spring中进行声明式事务管理,底层使用的是AOP原理,很合理!AOP就是面向切面编程,在不改变源代码的情况下,可以增强代码功能,那么平时面试问到Spring中使用AOP的底层原理有哪些,就可以回到Spring的声明式事务管理了。
Spring事务管理API:
- 提供一个PlatformTransactionManager接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类。
PlatformTransactionManager接口的结构层次了解:
我们可以通过ctrl + H来查看结构类的层次。
3. Spring5 声明式事务管理(注解方式)
第一步:在Spring配置文件来配置事务管理器。
因为,刚刚上面说到,不同框架对应实现了PlatformTransactionManager的不同实现类。
这里说的事务管理器,听起来很高大尚,实际上就是在beans.xml文件中创建对应的实现类就可,就像下面id为transactionManager的bean标签内容。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.itholmes"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/user_db?ServerTimezone=Aisa/shanghai"/>
<property name="username" value="root"/>
<property name="password" value="0818"/>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--创建事务管理器,因为我们使用的是jdbcTemplate,因此我们要创建DataSourceTransactionManager事务管理器。-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--通过查看DataSourceTransactionManager源码不难看出,有个setDataSource方法的,因此要注入数据源dataSource!-->
<!--
注入数据源datasource 。
这里容易混淆,name的dataSource是指向源码内部的setDataSource方法(注入),
ref的dataSource指向上面的德鲁伊druid的dataSource!
-->
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
第二步:在Spring配置文件,开启事务注解。
- 1.在spring配置文件beans.xml中引入名称空间tx。
(这里的tx,就是我们之前导入的spring tx包。Spring-tx模块负责在spring框架中实现事务管理功能。以aop切面的方式将事务注入到业务代码中,并实现不同类型的事务管理器。)
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd"
- 2.开启事务的注解,在配置文件beans.xml中开启事务注解。
<!--开启事务注解-->
<!--transaction-manager来指定哪一个事务管理器。-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
完整版beans.xml效果如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans 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"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/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">
<context:component-scan base-package="com.itholmes"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/user_db?ServerTimezone=Aisa/shanghai"/>
<property name="username" value="root"/>
<property name="password" value="0818"/>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--创建事务管理器,因为我们使用的是jdbcTemplate,因此我们要创建DataSourceTransactionManager事务管理器。-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--通过查看DataSourceTransactionManager源码不难看出,有个setDataSource方法的,因此要注入数据源dataSource!-->
<!--
注入数据源datasource 。
这里容易混淆,name的dataSource是指向源码内部的setDataSource方法(注入),
ref的dataSource指向上面的德鲁伊druid的dataSource!
-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启事务注解-->
<!--transaction-manager来指定哪一个事务管理器。-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
第三步:在service类上面(或者service类里面方法上面) 添加事务注解。
- @Transactional注解,既可以添加到类上面也可以添加到方法上面。
- 添加到类上面,那么这个类里面所有的方法都给他们添加事务。
- 添加到方法上面,那么仅仅是给这个方法添加事务。
package com.itholmes.service;
import com.itholmes.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class UserService {
//注入dao
@Autowired
private UserDao userDao;
//在service业务逻辑层中调用转账方法
public void accountMoney() {
//一旦设定了@Transactional注解下面的步骤会自动执行,因为是AOP切面编程原理。
//第一步:开启事务
//第二部:进行业务操作
//lucy转账100
userDao.reduceMoney();
//模拟异常,比如断电了,断网了等等。
int i = 1 / 0;
//mery收账100
userDao.addMoney();
//如果上面步骤都没有发生异常,则我们就要进行事务提交了。
//第三部:提交事务
//如果出现错误,出现异常。
//第四部:事务回滚
}
}
这样我们再通过test测试类来测试, 能否达到事务回滚的效果。
4. Spring5 声明式事务管理 参数配置
4.1 参数配置 介绍
@Transactional,在这个注解里面可以配置事务相关参数。
idea中可以使用ctrl + p来查看参数。
事务参数中,我们常用的如下几种:
- 参数propagation:事务传播行为。
- 参数ioslation:事务隔离级别。
- 参数timeout:超时时间。
- 参数readOnly:是否只读。
- 参数rollbackFor:回滚。
- 参数noRollbackFor:不回滚。
4.2 参数propagation 事务传播行为
参数propagation:事务传播行为。
- 多事务方法直接进行调用,这个过程中事务是如何进行管理的。
对于上面几个名词需要理解:
什么是事务方法?
对数据库表数据进行变化的操作就叫做事务方法,也就是查询的相关操作就是不是事务操作!
什么是事务传播行为?
事务传播行为大体分为下面几种情况:就是一个有事务的方法调用了一个没有事务的方法;一个没有事务的方法调用了一个有事务的方法;一个有事务的方法调用了一个也有事务的方法。这种过程就叫做事务传播行为。
事务的传播行为分为7种,图片如下:
事务的传播行为分为7种,但是最常用的是以下两种(重要,记住!):
(图片统一对照第一张)
-
REQUIRED(required , 也是默认的):
-
REQUIRED_NEW(required_new):
参数效果如下图:
4.3 参数ioslation 事务隔离级别
事务的隔离级别,首先理解隔离两个字,其实所谓的隔离就是多个事物之间不会产生影响!
事务的特性之一就是隔离性,多事务操作之间不会产生影响。
如果事务不考虑隔离性会产生很多问题!例如:脏读,不可重复读,虚读(幻读)。
-
脏读:一个未提交事务读取到另一个未提交事务的数据。
(脏读是一个致命问题,已经影响到了我们期望的结果) -
不可重复读:一个未提交的事务读取到了另一个提交的事务所修改的数据。
(不可重复度是一种现象,这个现象是允许产生的,只不过需要做一些统一操作等等,它本身不能算是一个问题。) -
虚读(幻读):一个未提交事务读取到另一个提交事务添加的数据。
(和不可重复度差不多,只不多这里是添加了数据。)
因此,我们Spring中,通过设置事务隔离性ioslation ,来解决上面这些问题:
在mysql中,我们如果不设置隔离级别的话,默认是第三种级别 REPEATABLE READ 可重复读。
参数效果如下图:
4.4 其他参数
参数timeout:超时时间。
这个就好理解了,就是事务开启后需要在一定时间内进行事务提交,不然就会回滚,意思就是超时了!
spring中timeout参数的默认值为 -1 ,就是不超时。
设置时间以秒为单位进行的。
参数readOnly:是否只读。
- 读:查询操作。
- 写:添加删除修改操作。
这里的读,意思就是查询操作了。
参数readOnly默认值一般为false,false表示可以查询,修改删除等操作。
设置为true,就只能进行读操作,也就是查询操作。
参数rollbackFor:回滚。
- 设置出现哪些异常进行事务回滚。
参数noRollbackFor:不回滚。
- 设置出现哪些一场不进行事务回滚。
这就很好理解了,出现哪些异常需要回滚,哪些异常不需要回滚的效果。
5. Spring5 声明式事务管理(xml方式)
声明式事务管理,xml方式步骤如下:
- 第一步:配置事务管理器。
- 第二步:配置通知。
- 第三步:配置切入点和切面。
<?xml version="1.0" encoding="UTF-8"?>
<beans 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"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/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
">
<context:component-scan base-package="com.itholmes"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/user_db?ServerTimezone=Aisa/shanghai"/>
<property name="username" value="root"/>
<property name="password" value="0818"/>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--第一步,配置事务管理器。-->
<!--创建事务管理器,因为我们使用的是jdbcTemplate,因此我们要创建DataSourceTransactionManager事务管理器。-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--通过查看DataSourceTransactionManager源码不难看出,有个setDataSource方法的,因此要注入数据源dataSource!-->
<!--
注入数据源datasource 。
这里容易混淆,name的dataSource是指向源码内部的setDataSource方法(注入),
ref的dataSource指向上面的德鲁伊druid的dataSource!
-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--第二步:配置通知-->
<tx:advice id="txadvice">
<!--配置事务参数-->
<tx:attributes>
<!--指定那种规则的方法上面添加事务-->
<!--这里的accountMoney就是我们UserService中想要添加事务的方法accountMoney。-->
<tx:method name="accountMoney" propagation="REQUIRED"/><!--也可以指定我们上面所说的参数属性值等等。-->
<!--
上面也可以写成下面的效果:
<tx:method name="account*"/>
-->
</tx:attributes>
</tx:advice>
<!--第三步:配置切入点和切面。-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pointcut" expression="execution(* com.itholmes.service.UserService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pointcut"/>
</aop:config>
</beans>
6. Spring 5 声明式事务管理(完全注解方式)
第一步:创建TxConfig类,通过使用注解让TxConfig类完全替代xml配置文件:
package com.itholmes.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
//@Configuration注解:表示该类是一个配置类
//@ComponentScan注解:相当于xml文件中的<context:component-scan base-package="com.itholmes"/>。
//@EnableTransactionManagement注解:该注解的作用是开始事务
@Configuration
@ComponentScan(basePackages = "com.itholmes")
@EnableTransactionManagement
public class TxConfig {
//创建数据库的连接池 与 xml配置文件中的内容是一一对应的
//@Bean注解用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理。(和xml配置中的bean标签的作用是一样的)
@Bean
public DruidDataSource getDruidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
//xml配置文件方式也是set注入方式,因此这里使用set也是对应配置文件中的。
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/user_db?ServerTimezone=Aisa/shanghai");
dataSource.setUsername("root");
dataSource.setPassword("0818");
return dataSource;
}
//创建JdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
//上面的DataSource dataSource参数就是到ioc容器中根据类型找到上面德鲁伊数据库连接池的dataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//创建事务管理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
第二步:添加@Transactional注解,添加事务给service层进行数据库交互的类。
UserService类:
package com.itholmes.service;
import com.itholmes.dao.UserDao;
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;
@Service
@Transactional(readOnly = false,timeout = 10,propagation = Propagation.REQUIRED,isolation = Isolation.SERIALIZABLE)
public class UserService {
//注入dao
@Autowired
private UserDao userDao;
//在service业务逻辑层中调用转账方法
public void accountMoney() {
//第一步:开启事务
//第二部:进行业务操作
//lucy转账100
userDao.reduceMoney();
//模拟异常,比如断电了,断网了等等。
int i = 1 / 0;
//mery收账100
userDao.addMoney();
//如果上面步骤都没有发生异常,则我们就要进行事务提交了。
//第三部:提交事务
//如果出现错误,出现异常。
//第四部:事务回滚
}
}
第三步:写测试类,Test3类来测试事务是否回滚:
注意,因为我们不使用xml配置文件方式了,而是使用类和注解的方式,因此我们要使用AnnotationConfigApplicationContext(TxConfig.class);来获得context的容器。
import com.itholmes.config.TxConfig;
import com.itholmes.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test3 {
public static void main(String[] args) {
//这里就需要使用AnnotationConfigApplicationContext对象来使用了
ApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class);
//注意这里的userService不能写成UserService会报错!
UserService service = context.getBean("userService", UserService.class);
service.accountMoney();
}
}