概念
-
事务是数据库操作的最基本单元,逻辑上一组操作,要么都成功,如果有一个失败则所有操作都要回滚到操作之前的状态。
-
事务的 ACID 特性:
(1)原子性
(2)一致性
(3)隔离性
(4)持久性
Spring 进行事务管理
- 事务添加到 JavaEE 三层结构里面的 Service 层(业务逻辑层)中。
- Spring 进行事务管理操作有两种实现方式:编程式事务管理、声明式事务管理(常用)
- 声明式事务管理:
(1)基于注解方式实现(常用)
(2)基于 xml 配置文件方式实现 - Spring 进行声明式事务管理,底层使用 AOP 原理
API
Spring 提供 PlatformTransactionManager 接口,实现对事务的管理操作,对于不同的框架,提供不同的实现类,如:不整合其他外部框架时,使用 DataSourceTransactionManager 实现类;整合 MyBatis 框架时,使用 DataSourceTransactionManager 实现类。
注解实现
步骤
(1)在 Spring 配置文件中配置事务管理器
<!-- 1.创建事务管理器对象 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入与JdbcTemplate相同的数据库连接池对象 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
(2)开启事务注解
<!-- 使用 tx(事务)名称空间 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
(3)在要添加事务的类或方法上添加@Transactional
注解
@Transactional
public class UserService {
}
注意:@Transactional 注解可以添加在类上面,也可以添加在方法上面
(1)若添加在类上面,表示为这个类内所有的方法都添加事务
(2)若添加在方法上面,表示为这个方法添加事务
@Transactional 参数
(1)propagation:传播行为。在事务操作中,多个方法之间相互调用时,如何进行事务处理。
Spring 中的 7 种传播行为:默认使用 REQUIRED
REQUIRED:如果 A 方法有事务,调用 B 方法后,B 使用 A 的事务;如果 A 方法没有事务,调用 B 方法之后,开启一个新的事务。
REQUIRED_NEW:不论 A 方法有无事务,调用 B 方法后,都开启一个新的事务。
(2)isolation:隔离级别。
4 种隔离级别,MySQL 默认使用 REPEATABLE_READ
(3)timeout:超时时间。
事务需要在一定时间内进行提交,如果在规定时间内未提交要进行回滚,默认值为 -1,表示不超时,设置时间以秒单位。
(4)readOnly:是否只读。
- 读:查询操作,写:添加、修改、删除操作
- 默认值为 false,表示可以查询、添加、修改、删除
- 设置 readOnly 值为 true,表示只能进行查询
(5)rollbackFor:回滚。
设置出现哪些异常要进行回滚,参数形式为rollbackFor = 异常类.class
(6)noRollbackFor:不回滚。
设置出现哪些异常不回滚,参数形式为rollbackFor = 异常类.class
实现
jdbctransaction.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: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/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">
<!-- 加载 数据库连接池配置文件 -->
<context:property-placeholder location="classpath:druid2.property"/>
<!-- 创建 数据库连接池对象 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!-- 注入属性 -->
<property name="driverClassName" value="${druid.driverClassName}"></property>
<property name="url" value="${druid.url}"></property>
<property name="username" value="${druid.username}"></property>
<property name="password" value="${druid.password}"></property>
</bean>
<!-- 创建 JdbcTemplate 对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启组件扫描,用注解创建对象以及注入属性 -->
<context:component-scan base-package="com.mcc.spring5.jdbc.transaction"></context:component-scan>
<!-- 进行事务操作 -->
<!-- 1.创建事务管理器对象 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入与JdbcTemplate相同的数据库连接池对象 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2.引入 tx 名称空间 -->
<!-- 3.开启事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
UserService.java
package com.mcc.spring5.jdbc.transaction;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
@Service
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ, timeout = -1, readOnly = false)//设置传播行为、隔离级别、超时时间、是否只读
public class UserService {
@Autowired
private UserDAO userDao;
//转账的方法
public void accountMoney() {
//lucy减少100
userDao.reduceMoney();
//模拟异常
int i = 1/0;
//mary增加100
userDao.addMoney();
}
}
UserDAO.java
package com.mcc.spring5.jdbc.transaction;
public interface UserDAO {
void reduceMoney();
void addMoney();
}
UserDAOImpl.java
package com.mcc.spring5.jdbc.transaction;
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;
@Override
public void reduceMoney() {
String sql = "update `user_account` set `money`=`money`-? where `username` = ?";
jdbcTemplate.update(sql,100,"lucy");
}
@Override
public void addMoney() {
String sql = "update `user_account` set `money`=`money`+? where `username` = ?";
jdbcTemplate.update(sql,100,"mary");
}
}
测试
@Test
public void testAnnotation() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("jdbctransaction.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.accountMoney();
context.close();
}
XML 实现
在 spring 配置文件中进行配置:
(1)配置事务管理器
(2)配置事务通知<tx:advice id=""></tx:advice>
(3)配置切入点和切面<aop:config></aop:config>
jdbctransactionXML.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/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">
<!-- 加载 数据库连接池配置文件 -->
<context:property-placeholder location="classpath:druid2.property"/>
<!-- 创建 数据库连接池对象 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!-- 注入属性 -->
<property name="driverClassName" value="${druid.driverClassName}"></property>
<property name="url" value="${druid.url}"></property>
<property name="username" value="${druid.username}"></property>
<property name="password" value="${druid.password}"></property>
</bean>
<!-- 创建 JdbcTemplate 对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启组件扫描,用注解创建对象以及注入属性 -->
<context:component-scan base-package="com.mcc.spring5.jdbc.transaction"></context:component-scan>
<!-- 进行事务操作 -->
<!-- 1.创建事务管理器对象 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入与JdbcTemplate相同的数据库连接池对象 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2.配置事务通知 -->
<tx:advice id="txAdvice">
<!-- 配置事务参数 -->
<tx:attributes>
<!-- 为何种方法添加事务操作 -->
<tx:method name="accountMoney" propagation="REQUIRED"/><!-- name="account*" 表示所有以account开始的方法 -->
</tx:attributes>
</tx:advice>
<!-- 3.配置切入点和切面 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut expression="execution(* com.mcc.spring5.jdbc.transaction.UserService.*(..))" id="pointcut"/>
<!-- 配置切面,注意:使用的是专门用于配置事务的切面标签 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
</aop:config>
</beans>
完全注解开发
使用 配置类 代替 xml 配置文件.
@Bean
:使用在方法上,在加载配置类时,自动执行该方法,获取返回值对象,保存到 IOC 容器中。- 如何保证使用的是同一个数据库连接池对象?
将 DataSource 对象以参数形式传入到其他方法中,保证使用的是同一个数据库连接池对象。
原因:在加载配置类时,由于 @Bean 注解的作用,会自动调用方法,因为方法的形参为 DataSource 对象,Spring 会自动在 IOC 容器中寻找 DataSource 对象,并传入方法中。
TxConfig.java
package com.mcc.spring5.jdbc.transaction.config;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
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 com.alibaba.druid.pool.DruidDataSource;
//Spring配置类
@Configuration//声明配置类
@EnableTransactionManagement//开启事务注解
@ComponentScan(basePackages = {"com.mcc.spring5.jdbc.transaction"})//开启组件扫描
public class TxConfig {
//创建数据库连接池对象
@Bean
public DruidDataSource getDruidDataSource() {
//加载配置文件
Properties prop = new Properties();
InputStream inputStream = TxConfig.class.getClassLoader().getResourceAsStream("druid2.property");
try {
prop.load(inputStream);
} catch (IOException e) {
throw new RuntimeException("Error load file named druid2.property");
}
//创建druid对象
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(prop.getProperty("druid.driverClassName"));
dataSource.setUrl(prop.getProperty("druid.url"));
dataSource.setUsername(prop.getProperty("druid.username"));
dataSource.setPassword(prop.getProperty("druid.password"));
return dataSource;
}
//创建JdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DruidDataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//创建事务管理器对象
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DruidDataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
测试
@Test
public void testConfig() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.accountMoney();
context.close();
}