1、什么事务
事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操
作都失败
2、事务四个特性(ACID)
(1)原子性:一个事务操作不可分割,要么都成功,要么都失败
(2)一致性:操作之前和操作之后数据库都处于一致性状态
(3)隔离性:事务之间相互独立互不影响
(4)持久性:事务提交后对数据库的改变是永久的
3、环境搭建
1.创建数据库表,导入依赖,配置spring配置文件
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.19</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.19</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.19</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-expression -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.19</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.19</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
</dependencies>
<!-- 导入外部的jdbc.properties文件 -->
<context:property-placeholder location="classpath*:jdbc.properties"/>
<!-- 组件扫描-->
<context:component-scan base-package="com"/>
<!-- 定义数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="maxActive" value="10"/>
<property name="minIdle" value="5"/>
</bean>
<!--JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
2、创建 service,搭建 dao,完成对象创建和注入关系
service 注入 dao,在 dao 注入 JdbcTemplate,在 JdbcTemplate 注入 DataSource
@Service
public class UserService {
//注入 dao
@Autowired
private UserDao userDao;
}
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
}
3、在 dao 创建两个方法:多钱和少钱的方法,在 service 创建方法(转账的方法)
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//lucy 转账 100 给 mary
//少钱
@Override
public void reduceMoney() {
String sql = "update t_account set money=money-? where username=?";
jdbcTemplate.update(sql,100,"lucy");
}
//多钱
@Override
public void addMoney() {
String sql = "update t_account set money=money+? where username=?";
jdbcTemplate.update(sql,100,"mary");
}
}
@Service
public class UserService {
//注入 dao
@Autowired
private UserDao userDao;
//转账的方法
public void accountMoney() {
//lucy 少 100
userDao.reduceMoney();
//mary 多 100
userDao.addMoney();
}
}
4、问题
上面代码如果代码执行过程中出现异常,则可能出现只减不增的情况,这时在service层使用事务进行解决
4、Spring 事务管理介绍
1、事务
一般添加到 JavaEE 三层结构里面 Service 层(业务逻辑层),也可以添加到其他位置
2、在 Spring 进行事务管理操作
有两种方式:编程式事务管理和声明式事务管理(使用)
编程式事务管理太繁琐,每个事务内都要写相同的提交回滚
声明式事务管理配置后直接使用
3、声明式事务管理
(1)基于注解方式(使用)
1、在 spring 配置文件配置事务管理器
<!--创建事务管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
2、在 spring 配置文件,开启事务注解
引入名称空间 tx
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
开启事务注解
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"/>
3、在 service 类上面(或者 service 类里面方法上面)添加事务注解
(1)@Transactional,这个注解添加到类上面,也可以添加方法上面
(2)如果把这个注解添加类上面,这个类里面所有的方法都添加事务
(3)如果把这个注解添加方法上面,为这个方法添加事务
@Service
@Transactional//添加事务
public class UserService {
//注入 dao
@Autowired
private UserDao userDao;
//转账的方法
public void accountMoney() {
//lucy 少 100
userDao.reduceMoney();
//异常终止
int i=10/0;
//mary 多 100
userDao.addMoney();
}
}
异常终止数据库也不会有任何改变
4、可以创建配置类,使用配置类替代 xml 配置文件
@Configuration //配置类
@ComponentScan(basePackages = "com.atguigu") //组件扫描
@EnableTransactionManagement //开启事务
public class TxConfig {
//创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///user_db");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
//创建 JdbcTemplate 对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
//到 ioc 容器中根据类型找到 dataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//注入 dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//创建事务管理器
@Bean
public DataSourceTransactionManager
getDataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new
DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
(2)基于 xml 配置文件方式
1、在 spring 配置文件中进行配置
第一步 配置事务管理器
<!--1 创建事务管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
第二步 配置通知
<!--2 配置通知-->
<tx:advice id="txadvice">
<!--配置事务参数-->
<tx:attributes>
<!--指定哪种规则的方法上面添加事务-->
<tx:method name="accountMoney" propagation="REQUIRED"/>
<!--<tx:method name="account*"/>-->
</tx:attributes>
</tx:advice>
第三步 配置切入点和切面
<!--3 配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(*
com.atguigu.spring5.service.UserService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>
(3)声明式事务管理参数配置
1、@Transactional注解的参数及其描述
参数名称 | 描述 |
value | 用于指定需要使用的事务管理器,默认为"",其别名为transactionManager。 |
transactionManager | 指定事务的限定符值,可用于确定目标事务管理器,匹配特定的限定值(或者Bean的name值),默认为"",其别名为value。 |
isolation | 用于指定事务的隔离级别,默认为Isolation.DEFAULT(即底层事务的隔离级别)。 |
noRollbackFor | 用于指定遇到特定异常时强制不回滚事务。 |
noRollbackForClassName | 用于指定遇到特定的多个异常时强制不回滚事务。其属性值可以指定多个异常类名。 |
propagation | 用于指定事务的传播行为,默认为Propagation.REQUIRED。 |
read-only | 用于指定事务是否只读,默认为false。 |
rollbackFor | 用于指定遇到特定异常时强制回滚事务。 |
rollbackForClassName | 用于指定遇到特定的多个异常时强制回滚事务。其属性值可以指定多个异常类名。 |
timeout | 用于指定事务的超时时长,默认为TransactionDefinition.TIMEOUT_DEFAULT(即底层事务系统的默认时间)。 |
2、propagation:事务传播行为
多事务方法直接进行调用,这个过程中事务 是如何进行管理的,spring定义了7中传播行为
3、ioslation:事务隔离级别
(1)事务有特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题
(2)有三个读问题:脏读、不可重复读、虚(幻)读
(3)脏读:当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
(4)不可重复读:一个未提交事务多次读同一数据读取到另一提交事务修改数据,两次读取的结果不一样。
(5)虚读:一个未提交事务读取到另一提交事务添加数据。是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。
(6)解决:通过设置事务隔离级别,解决读问题
4、timeout:超时时间
(1)事务需要在一定时间内进行提交,如果不提交进行回滚
(2)默认值是 -1 ,设置时间以秒单位进行计算
5、readOnly:是否只读
(1)读:查询操作,写:添加修改删除操作
(2)readOnly 默认值 false,表示可以查询,可以添加修改删除操作
(3)设置 readOnly 值是 true,设置成 true 之后,只能查询
4、在 Spring 进行声明式事务管理,底层使用 AOP 原理
5、Spring 事务管理 API
Spring事务管理所涉及的3个核心接口,接下来对这3个接口的作用分别进行讲解。
1、 PlatformTransactionManager
PlatformTransactionManager接口是Spring提供的平台事务管理器,主要用于管理事务。该接口中提供了三个事务操作的方法,具体如下:
● TransactionStatus getTransaction(TransactionDefinition definition ):用于获取事务状态信息;
● void commit(TransactionStatus status):用于提交事务;
● void rollback(TransactionStatus status):用于回滚事务。
在上面的3个方法中,getTransaction(TransactionDefinition definition )方法会根据TransactionDefinition参数返回一个TransactionStatus对象,TransactionStatus对象就表示一个事务,它被关联在当前执行的线程上。
PlatformTransactionManager接口只是代表事务管理的接口,它并不知道底层是如何管理事务的,它只需要事务管理提供上面的3个方法,但具体如何管理事务则由它的实现类来完成。
PlatformTransactionManager接口有许多不同的实现类,常见的几个实现类如下:
● org.springframework.jdbc.datasource.DataSourceTransactionManager:用于配置JDBC数据源的事务管理器;
● org.springframework.orm.hibernate4.HibernateTransactionManager:用于配置Hibernate的事务管理器;
● org.springframework.transaction.jta.JtaTransactionManager:用于配置全局事务管理器。
当底层采用不同的持久层技术时,系统只需使用不同的PlatformTransactionManager实现类即可。
2、 TransactionDefinition
TransactionDefinition接口是事务定义(描述)的对象,该对象中定义了事务规则,并提供了获取事务相关信息的方法,具体如下:
● String getName():获取事务对象名称。
● int getIsolationLevel():获取事务的隔离级别。
● int getPropagationBehavior():获取事务的传播行为。
● int getTimeout():获取事务的超时时间。
● boolean isReadOnly():获取事务是否只读。
上述方法中,事务的传播行为是指在同一个方法中,不同操作前后所使用的事务。传播行为有很多种,具体如表1所示。
表1 传播行为的种类
属性名称 | 值 | 描述 |
PROPAGATION_REQUIRED | REQUIRED | 表示当前方法必须运行在一个事务环境当中,如果当前方法已处于事务环境中,则可以直接使用该方法;否则会开启一个新事务后执行该方法。 |
PROPAGATION_SUPPORTS | SUPPORTS | 如果当前方法处于事务环境中,则使用当前事务,否则不使用事务。 |
PROPAGATION_MANDATORY | MANDATORY | 表示调用该方法的线程必须处于当前事务环境中,否则将抛异常。 |
PROPAGATION_REQUIRES_NEW | REQUIRES_NEW | 要求方法在新的事务环境中执行。如果当前方法已在事务环境中,则先暂停当前事务,在启动新的事务后执行该方法;如果当前方法不在事务环境中,则会启动一个新的事务后执行方法。 |
PROPAGATION_NOT_SUPPORTED | NOT_SUPPORTED | 不支持当前事务,总是以非事务状态执行。如果调用该方法的线程处于事务环境中,则先暂停当前事务,然后执行该方法。 |
PROPAGATION_NEVER | NEVER | 不支持当前事务。如果调用该方法的线程处于事务环境中,将抛异常。 |
PROPAGATION_NESTED | NESTED | 即使当前执行的方法处于事务环境中,依然会启动一个新的事务,并且方法在嵌套的事务里执行;即使当前执行的方法不在事务环境中,也会启动一个新事务,然后执行该方法。 |
在事务管理过程中,传播行为可以控制是否需要创建事务以及如何创建事务,通常情况下,数据的查询不会影响原数据的改变,所以不需要进行事务管理,而对于数据的插入、更新和删除操作,必须进行事务管理。如果没有指定事务的传播行为,Spring默认传播行为是REQUIRED。
3、 TransactionStatus
TransactionStatus接口是事务的状态,它描述了某一时间点上事务的状态信息。该接口中包含6个方法,具体如下:
● void flush():刷新事务;
● boolean hasSavepoint():获取是否存在保存点;
● boolean isCompleted():获取事务是否完成;
● boolean isNewTransaction():获取是否是新事务;
● boolean isRollbackOnly():获取是否回滚;
● void setRollbackOnly():设置事务回滚