一、简介
💬概述:事务时数据库操作最基本的单元,逻辑上表示一组操作要么都成功,如果出现失败就都失败
典型场景:银行转账
🔑事务的特点(ACID)
- 原子性(Atomicity):事务是最基本的数据库操作,不可再分(要么都成功,要么都失败)
- 一致性(Consistency):事务的整体保持不变,比如客户1和客户2两人的总余额为2千,那么他们两人无论怎么相互转账,两人的总额还是2千
- 隔离性(Isolation):各个事务之间不会相互影响
- 持久性(Durability):事务一旦提交,事务中的各个操作都会永久保存下来,即一旦提交事务,已修改的数据库信息就不能恢复
二、事务管理
2.1 概述
💡 事务管理一般添加到业务逻辑层(service层)
🔑底层原理:spring 事务管理底层是基于spring AOP 完成的,即AOP又是基于java的动态代理完成的
🔑事务管理方式
- 编程式事务管理(一般不采用):直接在事务的操作中添加
try...catch()...
语句捕获异常,在try{}
语句块中开始处开启事务,最后提交事务,在catch(){}
语句块(出现异常时)中回滚事务 - 声明式事务管理:利用spring事务管理相关的API,并使用注解或xml 文件进行配置即可
🔑事务管理API:spring提供一个接口PlatformTransactionManager
,作为事务管理器,针对不同的框架又有不同的实现类
DataSourceTransactionManager
实现类:适用于jdbc模板(jdbcTemplate)、spring整合Mybatis框架HibernateTransactionManager
实现类:适用于spring整合Hibernate框架
2.2 事务管理操作
💡 在进行spring事务管理前需要先完成jdbcTemplate的相关配置
🔑注解方式
① 在spring 配置文件中配置事务管理器(创建事务管理器对象),并注入dataSource属性
<bean id="tManager" class="org.springframework.jdbc.dataSource.DataSourceTransactionManager">
<!-- 注入数据源dataSource属性 -->
<property name="dataSource" ref="ds"></property>
</bean>
💡
<property>
标签中的ref属性值为连接池的唯一标识id
② 在spring 配置文件中开启事务注解:需要先开启tx 名称空间,然后使用<tx:annotation-driven>
<tx:annotation-driven transaction-manager="tManager"></tx:annotation-driven>
💡 transaction-manager的属性值就是事务管理器对象的唯一标识id
③ 添加@Transactional
注解
- 在类上添加:类中的全部方法都添加事务
- 在对应的方法上添加:只在该方法上添加事务
// 在类上添加
@Transactional
public class UserService {
// code...
}
/*+------------------------------------------------+*/
// 在类中对应的方法中添加
public class UserService {
@Transactional
public void add() {
// code...
}
}
④ 注解参数设置
4.1 设置传播行为——propagation
-
事务方法:对数据库数据进行修改的操作,如添加、修改、删除等
-
传播行为:多事务方法之间相互调用(直接调用)的过程中事务的管理方式,如下例子
// update()方法 public void update() { // code... } /*+---------------------------------------------------+*/ // add()方法中开启事务,并调用update()方法 @Transactional public void add() { // 调用update()方法 update(); // code... }
💡 事务方法不一定要开启事务(添加
@Transactional
注解),以上述为例,update()
方法没有开启事务,add()
方法开启了事务,但两个方法都是事务方法,所以add()
中调用update()
就是多事务方法之间的相互调用 -
spring 中事务传播行为:一共有7种,其中有两个比较常用(以上述例子为例)
Ⅰ.
REQUIRED
:如果add()
开启了事务,则在调用update()
方法后,update()
会使用当前add()
的事务完成操作;如果add()
没有开启事务,则在调用update()
方法后,update()
会创建一个新的事务来完成操作
Ⅱ.REQUIRED_NEW
:无论add()
是否有开启事务,在调用update()
方法后,都会创建一个新的事务来完成操作
4.2 设置隔离级别——isolation
:针对事务的并发操作中遇到的三种读问题设置隔离级别
-
三种读问题:脏读、不可重复读、幻读(虚读)
-
四种隔离级别以及对应问题的解决
隔离级别 脏读 不可重复读 幻读 读未提交(READ UNCOMMITTED) 有 有 有 读已提交(READ COMMITTED) 无 有 有 重复读(REPEATABLE READ) 无 无 有 串行化(SERIALIZABLE) 无 无 无 💡 Mysql中默认的隔离级别为重复读(REPEATABLE READ)
4.3 设置超时时间——timeout
:设定事务需要在一定时间内提交,如果超出这段时间还不进行提交就会回滚
- 时间的设定:超时时间
timeout
的属性值以秒为单位,默认值为-1,-1表示没有超时时间(即没有出现异常就正常提交事务,出现异常就回滚事务,而不是超时时间为0)
4.4 设置是否只读——readOnly
:设置对数据库数据的操作是否只读
- 只读:
readOnly
的属性值设为true,表示只能对数据库的数据进行查询操作 - 非只读:
readOnly
的属性值设为false,false是默认值,表示能对数据库数据进行CRUD操作
4.5 设置是否回滚:对异常进行选择性回滚
rollbackFor
:属性值为异常的类对象,表示遇到该异常就进行回滚,如Exception.class
noRollbackFor
:属性值为异常的类对象,表示遇到该异常就不进行回滚,如Exception.class
⑤ 测试类
public class TranTest {
/**
* 测试不使用事务的转账操作
*/
@Test
public void testUpdateMoney() {
// 创建工厂对象
ApplicationContext context = new ClassPathXmlApplicationContext("transaction.xml");
// 获取service(注意这里只能是service接口.class)
CustomService cService = context.getBean("cService", CustomService.class);
// 调用service方法
cService.updateMoney();
}
}
💡 测试类中通过工厂对象获取service对象时,在
getBean()
中注入的是service类型受事务开启的影响
- 没有开启事务:
getBean()
中的参数可以是service接口.class
,也可以是service实现类.class
,因为在没有开启事务时,spring会为我们注入IOC容器中的对应service类型的bean对象- 开启了事务:
getBean()
中的参数只能是service接口.class
,因为spring 管理事务是基于spring AOP,而AOP又是基于Java的动态代理实现的,由于存在service接口,spring 会采用JDK动态代理,即使用Proxy 类来实现动态代理。因此通过getBean()
获取的是接口的代理对象,通过这个代理对象来增强方法,实现事务控制。也就是说,如果开启事务(配置声明式事务),getBean()
中就只能注入service接口而不能注入service实现类(如果写实现类会报错!)
🔑xml 文件配置(从xml 配置中可以明显看出spring事务管理底层是基于spring AOP)
① 配置事务管理器(创建事务管理器对象,与注解方式一样)
<bean id="tManager" class="org.springframework.jdbc.dataSource.DataSourceTransactionManager">
<property name="dataSource" ref="ds"></property>
</bean>
② 配置通知(advice)
<!-- 配置通知 -->
<tx:advice id="txAdvice">
<!-- 配置事务管理和事务参数 -->
<tx:attributes>
<!-- 在对应方法(可以多个)上开启事务,并设置事务的参数 -->
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
❓
<tx:method>
标签中的name
属性:属性值为指定规则的方法名,如update*
就表示前面带有update的方法都开启事务
③ 配置切入点(pointcut)和切面(将通知应用到切入点上)
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut id="pt" expression="execution(* com.Key.service.UserService.*(..))"/>
<!-- 配置切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
</aop:config>
❓ 几个标签中属性说明
<aop:pointcut>
中的expression
属性值为切入点表达式,指定要开启事务的方法和类<aop:advisor>
中的advice-ref
和pointcut-ref
属性值分别为通知的唯一标识id和切入点的唯一标识id
2.3 完全注解开发⭐
① 创建配置类——TxConfig
② 在配置类上添加相关注解:@Configuration
(设定为配置类)、@ComponentScan(basePackages = {"com.Key")
(开启组件扫描)、@EnableTransactionManagement
(开启事务)
③ 在配置类中创建各个类对象:添加每个类的getXxx()
方法,返回对应的对象,并在方法上添加@Bean
注解
💡 如果需要在某个类的
getXxx()
方法中使用其他类对象,只需把该对象作为参数传入即可。因为在配置类中创建了类对应的getXxx()
方法,spring IOC容器中就会存在对应的Bean对象,所以在方法参数中添加需要使用的类对象,就能在方法中直接使用,而无需在方法中使用new创建(相当于使用@Autowired
注解根据类型创建对象类型的属性)
@Configuration
@Component(basePackage = {"com.Key"})
@EnableTransactionManagement
public class TxConfig {
// 创建数据库连接池对象
@Bean
public DruidDataSource getDruidDataSource() {
// 先new 一个对象
DruidDataSource ds = new DruidDataSource();
// 引入外部属性文件
Properties pro = new Properties();
pro.load(TxConfig.class.getClassLoader().getResourceAsStream("jdbc.properties"));
// 注入外部属性文件中的对应属性值(也可以不采用引入的外部属性文件,直接注入对应属性值)
ds.setDriverClassName(pro.getProperty("prop.driverClass"));
ds.setUrl(pro.getProperty("prop.url"));
ds.setUsername(pro.getProperty("prop.username"));
ds.setPassword(pro.getProperty("prop.password"));
// 最后返回注入好属性的对象
return ds;
}
// 创建jdbcTemplate 对象
@Bean
public JdbcTemplate getJdbcTemplate(DruidDataSource ds) {
// 先new 一个对象
JdbcTemplate jt = new JdbcTemplate();
// 注入dataSource 属性
jt.setDataSource(ds);
return jt;
}
// 创建事务管理器对象
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DruidDataSource ds) {
// 先new 一个对象
DataSourceTransactionManager tManager = new DataSourceTransactionManager();
// 注入dataSource 属性
tManager.setDataSource(ds);
return tManager;
}
}