SSM-Spring(四)-事务管理、多配置文件
事务管理
- 使用场景
- 在service层,通常一个方法内部可能会有几个dao操作,这些操作要么一起成功;如果中间一个操作失败,之前操作成功的都需要回滚
- 要实现这种操作就需要使用事务:开启事务、提交事务、回滚事务
-
可以通过创建一个类(TxInterceptor),然后实现MethodInterceptor接口,然后在xml中通过AOP给service层的每个类的每个方法都插入事务管理
public class TxInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { // 开启事务 Object result = null; try { result = invocation.proceed(); // 提交事务 } catch (Exception e) { // 回滚事务 } return result; } } //xml中AOP切入略
- 但是这样做还是比较麻烦,Spring中有自带的添加事务的方法
Spring声明式事务
- 使用Spring的声明式事务,可以极其容易地进行事务管理
- 只需要在XML中进行声明配置
- 一般是在Serice层进行事务管理
- 举例使用:(该demo是一个mybatis整合项目)
- SkillServiceImpl层有个test方法,该方法中使用了多个dao处理数据
-
applicationContext.xml中整合mybaitis那块如下
<context:property-placeholder location="db.properties"/> <!-- 数据源(Druid) --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driverClass}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!-- 创建SqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 数据源 --> <property name="dataSource" ref="dataSource"/> <!-- 这个包底下的类会自动设置别名(一般是领域模型) 这个包下面所有的别名就是类名 比如com.zh.domain.Skill;别名是Skill --> <property name="typeAliasesPackage" value="com.zh.domain"/> <!-- 映射文件的位置 --> <property name="mapperLocations"> <array> <value>mappers/*.xml</value> </array> </property> </bean> <!-- 扫描所有的dao 该对象可以获取到Seqsession 可以获取到所有的dao --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 设置SqlSessionFactoryBean的id --> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> <!-- 设置dao的包 --> <property name="basePackage" value="com.zh.dao"/> </bean> <!--skillDao是上面扫描后的结果,也放在容器中--> <bean id="skillService" class="com.zh.service.impl.SkillServiceImpl"> <property name="dao" ref="skillDao"/> </bean>
-
给serice层配置事务管理方面如下
<!-- 事务管理器 --> <bean id="txMgr" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--需要设置一个数据源,可以直接用mybatis中使用的那个--> <property name="dataSource" ref="dataSource"/> </bean> <!-- 附加代码:在方法前后添加事务管理代码 Spring写好的 --> <tx:advice id="txAdvice" transaction-manager="txMgr"> <!--指定哪些方法,需要进行如何事务操作 --> <tx:attributes> <!--只针对以list开头的方法--> <tx:method name="list*"/> <!--要切入的具体方法--> <tx:method name="list"/> <tx:method name="save"/> <tx:method name="update"/> <tx:method name="test"/> </tx:attributes> </tx:advice> <!-- 切面 --> <aop:config> <!--切入点:针对所有的业务层service添加事务 --> <aop:pointcut id="pc" expression="within(com.zh.service..*)"/> <!-- 要切入的附加代码 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/> </aop:config>
tx:method的常用属性
tx:method
标签可以配置很多常用属性-
name的值也可以配置如下:
*: 代表所有方法 list* 代表list开头的方法
propagation
- 可以通过propagation属性设置事务的传播行为,用于指定当事务嵌套时,如何管理事务
- 当Service调用Service时,会出现事务嵌套,比如SkillServiceImpl层的test方法内部还调用了它自己的update方法,事务嵌套如下
//service调用test方法时会开启事务,内部调用update时也会开启事务,然后处理完成提交事务,提交test开启的事务 public void test(Skill skill) throws Exception { // 当前的事务情况 update(null); }
-
事务嵌套的问题
txt1 ---begin txt2---begin tet2---end ----出现异常--- xt3---begin tet3---end txt1 ---end
- 上面的问题,一旦出现异常,txt2事务已经提交了,不会进行回滚
- 那么如何让整个txt1为原子性的呢?一旦内部某个出现异常,让之前已经提交的独立事务进行回滚呢? 可以使用propagation属性
-
propagation常用取值
取值 当前有事务 当前无事务 常见使用场景 REQUIRED(默认) 支持当前事务 开启新事务 增、删、改 SUPPOTRS 支持当前事务 不开启新事务 查询 MANDATORY 支持当前事务 抛出异常 REQUIRES_NEW 挂起当前事务、开启新事务 开启新事务 日志记录 NOT_SUPPORTED 挂起当前事务 不开启新事务 NEVER 抛出异常 不开启新事务
- 举例:
<tx:method name="update*" propagation="REQUIRED"/>
- 表示test方法内部在执行update方法时,update方法会首先检查test是否已经开启事务
- 如果已经开启(当前有事务),那么就不在开启新事务,直接使用(支持当前事务 )
- 如果没有(当前无事务 ),则开启新的事务(开启新事务 )
- 这样test的外层事务就是一个原子,一旦内部任何一个故障,全部回滚,解决嵌套问题
- 可以用在增、删、改
<tx:method name="update*" propagation="SUPPOTRS"/>
- test开启事务,则直接使用
- 反之,不会开启新事务
- 可以使用查
- REQUIRES_NEW
- test开启事务,执行update时,暂停test事务,并开启自己的事务
- test没有开启事务,则开启新事务
- 通常使用于日志记录
-
建议配置
<!-- 1. 先让所有方法使用默认值,REQUIRED 2. 再指定某些方法使用:SUPPOTRS --> <tx:method name="*" /> <tx:method name="list*" propagation="SUPPORTS"/> <tx:method name="get*" propagation="SUPPORTS"/>
read-only
- 如果一个事务只执行读操作,那么数据库可能会采取某些优化措施
- read-only设置为true:告诉数据库这是个只读事务
- 只适用于REQUIRED或者REQUIRES_NEW(必须保证有事务)
-
举例
<tx:method name="list*" propagation="REQUIRED" read-only="true"/>
timeout
- 单位是秒,默认是-1(按照数据库默认情况处理),超时就会抛出异常
-
给某个方法配置一个超时时长
<tx:method name="list*" timeout="4"/>
rollback-for、no-rollback-for
- 默认情况下,RuntimeException、Error会导致事务回滚(非检查型异常),而Exception(检查型异常)不会
- rollback-for:设置哪些异常会导致事务回滚(在RuntimeException、Error的基础上增加一些异常)
- no-rollback-for:设置哪些异常不会导致事务回滚(在Exception的基础上增加一些异常)
-
举例
<!-- 指定所有方法遇见Exception异常也要回滚--> <tx:method name="*" rollback-for="Exception"/> <tx:method name="save*" rollback-for="java.io.IOException,InterruptedException" no-rollback-for="RuntimeException,Error"/>
isolation(面试题)
- isolation用于设置事务的隔离级别
-
可以设置的值有
DEFAULT(默认值,按照数据库的默认设置,一般用这个就可以) READ_UNCOMMITTED READ_COMMITTED REPEATABLE_READ SERIALIZABLE
- 事务的隔离级别:如果多个事务同时操作同一份数据,可能引发以下问题
- 脏读:一个事务读取到了另一个事务没有提交的数据
- 多个事务访问同一张表,某个事务还没有提交数据
- 不可重复读:一个事务范围内两个相同的查询却返回了不同数据
- 同一个事务2个查询中间有其他事务提交修改了数据,导致第一次、第二次查询结果不一样
- 幻读:一个事务发现了之前本来确认不存在的数据
- 第一个事务第一次查询有n条数据,再次查询发现有n+x条数据
- 第一次、二次中间有其他事务出入数据并提交
- 脏读:一个事务读取到了另一个事务没有提交的数据
-
可以设置隔离级别来解决上述问题(由上到下,性能依次降低)
READ UNCOMMITTED 什么也解决不了 READ COMMITTED:可以防止脏读 REPEATABLE READ:可以防止脏读、不可重复读( 的默认隔离级别) SERIALIZABLE:可以防止脏读、不可重复读、幻读
- 查询当前数据库隔离级别的SQL语句:
SELECT @@TX_ISOLATION
- 设置数据库的隔离级别:
SET GLOBAL TRANSACTION ISOLATION LEVEL 隔离级别
多个配置文件
- 如果Spring配置文件的内容过多,可以采取多个配置文件的形式,将各个模块单独放一个配置文件:service、mybatis、事务等
- 举例
-
有3个配置文件:
//1. applicationContext.xml <bean id="person" class="com.zh.domain.Person"/> //2. applicationContext-mybatis.xml <bean id="dog" class="com.zh.domain.Dog"/> //3. applicationContext-tx.xml <bean id="user" class="com.zh.domain.User"/>
-
方法一:在人口配置文件applicationContext.xml分别倒入其他配置文件,外部使用不变
<!--方法一:--> <import resource="applicationContext-mybatis.xml"/> <import resource="applicationContext-tx.xml"/> <bean id="person" class="com.zh.domain.Person"/>
-
方法二:入口配置文件不变,在外部使用时同时加载多个配置文件
//方式1:一次性传入多个配置文件 ApplicationContext ctx = new ClassPathXmlApplicationContext( "applicationContext.xml", "applicationContext-mybatis.xml", "applicationContext-tx.xml"); //方式2:以applicationContext开头的所有文件都加载 ApplicationContext ctx = new ClassPathXmlApplicationContext( "classpath*:applicationContext*.xml"); System.out.println(ctx.getBean("person")); System.out.println(ctx.getBean("dog")); System.out.println(ctx.getBean("user"));
-