我是小白,请各位大佬指教
配置文件中配置
spring中基于注解的声明式事务控制配置步骤
1、配置事务管理器
2、开启spring对注解事务的支持
3、在需要事务支持的地方使用 @Transactional注解
基于注解的配置虽然从代码多少上来看更简单,但是需要每次都在Service类上重新配置。所以一般不推荐使用。
spring中基于配置文件的声明式事务控制配置步骤
1、配置事务管理器
2、配置事务的通知
此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的
使用<tx:advice>标签配置事务通知
属性:
id:给事务通知起一个唯一标识
transaction-manager:给事务通知提供一个事务管理器引用
3、配置AOP中的通用切入点表达式
4、建立事务通知和切入点表达式的对应关系
5、配置事务的属性
是在事务的通知<tx:advice>标签的内部
配置事务的属性<tx:attributes>
isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
(一般会设置)propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
(一般会设置)read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
例:
基于配置文件 2.配置事务通知 begin
<tx:advice id="txAdvice" transaction-manager="transactionManager">
5.配置事物的属性
<tx:attributes>
<tx:method name="find*" read-only="true" propagation="SUPPORTS"/>
<tx:method name="insert" read-only="false" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
3.4.配置AOP增强(建立事务通知与切入点表达式的关系)
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* cn.itcast.service.impl.*ServiceImpl.*(..))"/>
</aop:config>
手动控制事务
//手动控制事务
public void insertTwo(){
//1.获取spring容器对象
WebApplicationContext contextLoader = ContextLoader.getCurrentWebApplicationContext();
//2.获取事务定义
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
//3.设置事物的传播行为 开启新事务(如果当前存在事务,将当前事务挂起)
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
//4.设置事务的隔离级别,此处是读已经提交
//definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
definition.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
//5.从spring容器对象中获取DataSourceTransactionManager,这个根据配置文件中配置的id名(transactionManager)
DataSourceTransactionManager transactionManager = (DataSourceTransactionManager) contextLoader.getBean("transactionManager");
//6.获取事务状态对象
TransactionStatus transactionStatus = (TransactionStatus) transactionManager.getTransaction(definition);
try{
User user = new User();
user.setPassword("123");
//5.具体数据库操作
for (int i = 0; i < 10; i++) {
user.setUsername("测试"+i);
userDao.insert(user);
}
//int i = 1/0;//定义一个异常 测试事务
//程序没有问题 执行提交操作
transactionManager.commit(transactionStatus);
}catch (Exception e){
e.printStackTrace();
//程序出现问题 执行回滚操作
transactionManager.rollback(transactionStatus);
}
}
<context:component-scan
base-package属性告诉spring要扫描的包
use-default-filters=”false” 会过滤@Service,@Component,@Responsitory,@Controller注解
<!--spring配置文件 配置包扫描 在主容器中不扫描@Controller注解。-->
<context:component-scan base-package="com.listen">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--SpringMVC配置文件中 配置包扫描 只扫描@Controller注解-->
<context:component-scan base-package="com.listen" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--spring配置文件中 配置事务管理器-->
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
注入数据源
<property name="dataSource" ref="数据源"></property>
</bean>
<!-- 开启注解支持 -->
<!-- proxy-target-class属性值决定是基于接口的还是基于类的代理被创建:
为true则是基于类的代理将起作用(需要cglib库)
为false或者省略这个属性,则标准的JDK 基于接口的代理将起作用。 -->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
// MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用
注解管理事务的属性
@Transactional(readOnly = true)只读事务
@Transactional(readOnly = false)读写事务(默认)
- @Transactional属性配置
1. value : String 可选的限定描述符,指定使用的事务管理器
2. propagation enum: Propagation 可选的事务传播行为设置
3. isolation enum: Isolation 可选的事务隔离级别设置
4. readOnly :boolean 读写或只读事务,默认读写
5. timeout :int (in seconds granularity) 事务超时时间设置
6. rollbackFor :Class对象数组,必须继承自Throwable 导致事务回滚的异常类数组
7. rollbackForClassName 类名数组,必须继承自Throwable 导致事务回滚的异常类名字数组
8. noRollbackFor : Class对象数组,必须继承自Throwable 不会导致事务回滚的异常类数组
9. noRollbackForClassName 类名数组,必须继承自Throwable 不会导致事务回滚的异常类名字数组
Propagation 事务的传播行为:
REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。
REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
NESTED:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务
isolation 事务隔离级别:
DEFAULT : 默认隔离级别,每种数据库支持的事务隔离级别不一样,如果Spring配置事务时将isolation设置为这个值的话,那么将使用底层数据库的默认事务隔离级别
READ_UNCOMMITTED :读未提交,即能够读取到没有被提交的数据,所以很明显这个级别的隔离机制无法解决脏读、不可重复读、幻读中的任何一种,因此很少使用
READ_COMMITED : 读已提交,即能够读到那些已经提交的数据,自然能够防止脏读,但是无法限制不可重复读和幻读
REPEATABLE_READ :重复读取,即在数据读出来之后加锁,明确数据读取出来就是为了更新用的,所以要加一把锁,防止别人修改它
REPEATABLE_READ: 的意思也类似,读取了一条数据,这个事务不结束,别的事务就不可以改这条记录,这样就解决了脏读、不可重复读的问题,但是幻读的问题还是无法解决。
SERLALIZABLE : 串行化,最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了
spring对于事务异常的处理
unchecked 运行期Exception spring默认会进行事务回滚 比如:RuntimeException
checked 用户Exception spring默认不会进行事务回滚 比如:Exception
添加@Transactional(noRollbackFor=RuntimeException.class)让spring对RuntimeException 不回滚事务
添加@Transactional(rollbackFor=Exception.class)让spring对于Exception进行事务的回滚,如果类加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚
当@Transactional不起作用时如何排查问题
1.首先要看数据库本身对应的库、表所设置的引擎是什么MyIsam不支持事务,如果需要,则必须改为InnnoDB (这一条与Oracle数据库无关)
2.@Transactional所注解的方法是否为public(不为public @Transactional不起作用)
3.如果使用了spring+mvc,则context:component-scan重复扫描问题可能会引起事务失败。(即父子容器)
spring配置文件中不扫描@Controller注解,springMVC配置文件中只扫描@Controller注解。
4.需要调用该方法,且需要支持事务特性的调用方法是在@Transactional所在的类的外面
注意:类内部的其他方法调用这个注解@Transactional的方法,事务是不会起着作用的。
5.注解事务范围的方法中,事物的回滚仅仅对于unchecked的异常有效。对于checked异常无效,也就是说事务回滚仅仅发生在出现RuntimeException(运行时异常)或Error的时候
如果希望一般的异常也能触发事务回滚,需要在注解了@Transactional的方法上,将@Transactional回滚参数设为:@Transactional(rollbackFor=Exception.class)
测试事务是否有效
可以在事务控制的代码中手动添加异常 int i=1/0;进行测试
也可以使用传播行为进行测试@Transactional(propagation = Propagation.NEVER) //propagation = Propagation.NEVER 如果当前存在事务就会报错
事务的四大特性
四大特性(ACID)
原子性
指事务包含的所有操作要么全部成功要么全部失败回滚
一致性
一个事务执行之前和执行之后都必须处于一致性状态
例如转账:用户A与用户B之间不管如何转账,事务结束后 总金额是不变的
隔离性
是指多个用户并发访问数据库 比如操作同一张表时,数据库为每一个用户开启的事务不能被其他事务的操作所干扰,多个并发事务之间要相互隔离
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行
持久性
是指一个事务一旦被提交,对数据库中数据的改变就是永久性的。即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
例如:我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,
即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误
不考虑事物的隔离性会发生的问题
脏读
指在一个事务处理过程中读取了一个未提交的事务中的数据
例如转账:A向B转账100元,执行完SQL语句后金额发生变化,用户查看账户金额确实发生了变化,但是只要事务不提交,则所有数据都会回滚,用户再去查看账户金额就会发现其实钱没有转
不可重复读
指对于数据库中的某个数据,一个事务范围内多次查询得到的数据不一致,这是由于在查询间隔,有事务修改并提交了数据
不可重复读与脏读的区别是:脏读是某一事务读取了另一事务未提交的脏数据,不可重复读是读取了前一事务修改后的数据
虚读(幻读)
是事务非独立执行时发生的一种现象
幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询
MySQL数据库为我们提供的四种隔离级别
Read uncommitted (读未提交):最低级别,任何情况都无法保证。
Repeatable read (可重复读):可避免脏读、不可重复读的发生。
Read committed (读已提交):可避免脏读的发生。
Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然级别越高,执行效率就越低
像Serializable这样的级别,就是以锁表的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。
在MySQL数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读);而在Oracle数据库中,只支持Serializable (串行化)级别和Read committed (读已提交)这两种级别,其中默认的为Read committed级别。