事务是数据库操作的最基础单元,参考TCL
典型场景:转账业务
A→B转账100,A-100&&B+100,两者同时满足则完成转账操作,两者相互依赖,如果只有一个满足/不满足则是bug,因此这种业务必须以事务来控制
1. Spring事务管理API
2. 编程式事务管理
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private DataSourceTransactionManager txManager;
private void test(){
//默认事务定义:隔离级别,传播行为等参数,参考声明式事务管理参数
TransactionDefinition tf = new DefaultTransactionDefinition();
//开启事务
TransactionStatus ts = txManager.getTransaction(tf);
try {
//事务处理
boolean reduceFlag = userDao.reduceMoney(reduce);
int i = 1 / 0;
boolean addFlag = userDao.addMoney(add);
txManager.commit(ts);
}catch (Exception e){
//回滚
txManager.rollback(ts);
}
}
3. 声明式事务管理(AOP)
声明式事务管理需要在连接数据库后做三件事
-
注入事务管理器
-
在事务管理器中注入数据源
-
开启事务
3.1 注解方式
注解方式分为两种,一种是半注解方式;一种是完全注解方式
半注解方式
需要在xml配置文件中开启事务注解
-
配置事务管理器
<!--创建事务管理器--> <bean id ="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> </bean>
-
注入数据源
<!--创建事务管理器--> <bean id ="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--注入数据源--> <property name="dataSource" ref="dataSource"/> </bean>
-
开启事务注解
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"/>
-
使用注解
@Transactional:事务注解,可用于方法或类上
-
在方法上表示当前方法具有事务的特性
-
在类上表示当前类中所有方法都具有事务的特性
完全注解方式
-
配置类配置:
-
配置数据库连接池
//创建数据库连接池 @Bean public DruidDataSource getDruidDataSource(){ Properties pro=new Properties(); try { pro.load(new FileReader("src/jdbc.properties")); } catch (IOException e) { throw new RuntimeException(e); } DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(pro.getProperty("driverClass")); dataSource.setUrl(pro.getProperty("url")); dataSource.setUsername(pro.getProperty("name")); dataSource.setPassword(pro.getProperty("password")); return dataSource; }
-
注入JdbcTemplate对象
//创建JDBCTemplate对象:这里的参数dataSource是根据Spring注入了DataSource以后,从Spring容器中获取的参数 @Bean public JdbcTemplate getJdbcTemplate(DataSource dataSource){ JdbcTemplate jdbcTemplate=new JdbcTemplate(); jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; }
-
创建事务管理器对象
//创建事务管理器的对象 @Bean public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){ DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); //注入dataSource实例 dataSourceTransactionManager.setDataSource(dataSource); return dataSourceTransactionManager; }
-
开启事务
@EnableTransactionManagement public class SpringConfig{
调用测试
ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class); UserController userController = context.getBean("userController", UserController.class);
3.2 xml配置方式
需要在xml配置文件中声明命名空间
-
配置事务管理器
<!--创建事务管理器--> <bean id ="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> </bean>
<!--创建事务管理器--> <bean id ="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--注入数据源--> <property name="dataSource" ref="dataSource"/> </bean>
-
配置通知
<tx:advice id = "txAdvice"> <!--配置事务参数--> <tx:attributes> <tx:method name="swapBatch" rollback-for="Exception.class"/> </tx:attributes> </tx:advice>
-
配置切入点和切面
<!--配置切入点和切面:在哪个方法上使用该事务--> <aop:config> <!--切入点--> <aop:pointcut id="pt" expression="execution(* com.carl.service.impl.UserServiceImpl.swapBatch(..))"/> <!--切面--> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/> </aop:config>
4. 声明式事务管理的参数
参数 | 说明 | 参数值 | 参数值说明 | 使用场景 |
---|---|---|---|---|
value | 用于指定不同的事务管理器 | 事务管理器id | 在项目中使用的事务管理器id值 | 使用场景:同一个系统中存在不同的事务管理器,例如Spring中可声明两种事务管理器 |
propagation | 传播行为:当方法A是事务,方法B是事务,且方法A调用方法B时方法B的事务状态称为事务的传播行为 | Propagation.REQUIRED | 如果有事务在运行,当前的方法就在这个事务内运行,否则就启动一个新的事务,并在自己的事务内运行 | 如果方法A调用方法B,此时方法B有此注解参数,则如果当方法A中有事务,则方法B与方法A处在同一个事务内,否则如果方法A没有事务,则方法B会在自己的事务内运行 |
Propagation.REQUIRED_NEW | 当前方法必须启动新的事务,并在自己的事务内运行,如果有事务正在运行,应该将它挂起 | 如果方法A调用方法B,此时方法B中有此注解,则如果当方法A中有事务,方法B也必须在自己的事务中运行,方法A和方法B的事务互不影响,如果方法A触发事务在前,则方法B挂起等待方法A执行完成后再执行方法B | ||
Propagation.SUPPORTS | 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中 | 如果方法A调用方法B,此时方法B中有此注解,则如果当方法A中有事务,则方法B也在当前事务中统一运行,如果方法A中没有事务,则方法B也没有事务 | ||
Propagation.NOT_SUPPORTED | 当前的方法不应该运行在事务中,如果有运行的事务,当前方法挂起 | 如果方法A调用方法B,此时方法B中有此注解,则如果方法A中有事务,则执行方法A的事务,方法B将会等待方法A事务执行完成后执行,当前方法B不应该运行在事务中 | ||
Propagation.MANDATORY | 当前方法必须运行在事务的内部,如果没有正在运行的事务,就抛出异常 | 如果方法A调用方法B,此时方法B中有此注解,则如果方法A中有事务,方法B将会在当前事务内执行,如果方法A中没有事务,方法B将会抛出异常 | ||
Propagation.NEVER | 当前方法不应该运行在事务中,如果有运行的事务,就抛出异常 | 如果方法A调用方法B,此时方法B中有此注解,则如果方法A中有事务,方法B抛出异常,如果方法A中没有事务,方法B正常执行 | ||
Propagation.NESTED | 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则就启动一个新的事务,并在它自己的事务内运行 | 如果方法A调用方法B,此时方法B中有此注解,则如果方法A中有事务,则方法B作为该事务的嵌套事务执行,即作为当前事务的子事务,如果方法A中没有事务,则方法B自己创建新的事务运行 | ||
ioslation | 事务的隔离级别 | READ UNCOMMITTED | 读未提交 | 不会对当前问题做任何处理 |
READ COMMITTED | 读已提交 | 无脏读问题 | ||
REPEATABLE READ | 可重复读 | (MySQL默认隔离级别)无脏读&&无可重复读 | ||
SERIALIZABLE | 串行化 | 无脏读&&无可重复读&&无幻读 | ||
timeout | 超时时间 | timeout =2 | 秒为单位(默认为-1) | 当前事务在两秒内未提交则回滚并抛出异常 |
rollbackFor | 回滚 | rollbackFor = Exception.class | 设置出现哪些异常进行回滚 | 有些异常直接或间接影响数据,有必要进行回滚,通过该参数进行设置 |
noRollbackFor | 不回滚 | noRollbackFor = NullException.class | 设置出现哪些异常不需要回滚 | 有些异常不会影响数据的增删改查以及数据的真实性,通过该参数进行设置避免回滚 |
readOnly | 是否只读 | readOnly = true | 默认为false | true:表示当前事务只能进行查询操作 false:表示当前事务可以进行增删改查等操作 |
嵌套事务:是指子事务在父事务中执行
-
在进入子事务前,父事务会建立一个回滚点(save point),执行完子事务后父事务继续执行.
-
如果子事务回滚,父事务会回滚到save point,然后尝试后续的操作或业务,父事务之前的事务并不会受到影响
-
如果父事务回滚,子事务也会回滚,父事务结束之前,子事务不会提交.
-
父事务和子事务的提交顺序:子事务先提交,父事务后提交