一、声明式事务管理
1.1 事务概述
为保持数据的完整性和一致性,需要引入数据库事务的概念,事务的四个关键属性为:
- 原子性:事务中的所有操作要么都执行,要么都不执行。
- 一致性:从一个一致性状态到另一个一致性状态.
- 隔离性: 多个事务在并发执行过程中不会互相干扰.
- 持久性: 事务执行完成后,对数据的修改永久保存下来,不会因为各种系统错误或其他意外情况而收到影响.
1.2 Spring事务管理
使用原生的JDBC API进行事务管理时,需要:1)获取数据库连接Connection对象;2)取消事务的自动提交;3)执行操作;4)正常完成操作时手动提交事务;5)执行失败时回滚事务;6)关闭相关资源。存在代码冗余问题。
声明式事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。事务管理代码的底层模式时一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理。Spring在不同的事务管理API之上定义了一个抽象层,通过配置的方式使其生效,从而让应用程序开发人员不必了解事务管理API的底层实现细节,就可以使用Spring的事务管理机制。Spring既支持编程式事务管理,也支持声明式的事务管理。编程式事务管理可以理解为使用java代码进行事务的管理,如JDBC API的事务管理,而声明式事务管理则是看不到与业务无关的事务管理的代码,将事务管理代码看作一个切面。
1.2.1 Spring提供的事务管理器
Spring核心事务管理抽象式PlatformTransactionManager
。它为事务管理封装了一组独立于技术的方法,无论使用spring的哪种事务管理策略,事务管理器都是必须的。事务管理器可以以普通的bean的形式声明在Spring IOC容器中。
<!---数据源->
<context:property-placeholder location="classpath:db.properties"/>
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--事务管理器-->
<bean id=" dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="datasource" ref="dataSource"></property>
</bean>
<!--开启事务注解: transaction-manager用来指定事务管理器,如果事务管理器的id值式transactionManager时,可以省略不进行指定-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
配置完成后在service层的对应方法上使用注解@Transactional
,此注解既可以标注在方法上,也可以标注在类上。标注在类上时对当前类中的所有方法起作用。标注在类上只对当前方法起作用。
二、事务管理的属性设置
2.1 事务的传播行为
事务的传播行为:一个事务方法被另一个事务方法调用时,当前的事务如何使用事务。
(1)@Transactional(propagation=Propagation.REQUIREL)
:默认值,使用调用者的事务。
(2)@Transactional(propagation=Propagation.REQUIREL_NEW)
:将调用者的事务挂起,重新开启事务来使用。
2.2 事务的隔离级别
事务的4个隔离级别:@Transactional(propagation=Propagation.REQUIREL_NEW),isolation=Isolation.READ_COMMITTED
(1)读未提交:会出现脏读
(2)读已提交:会出现不可重复读
(3)可重复度:会出现幻读
(4)串行化:效率低
2.3 事务的回滚与不回滚
事务的回滚与不回滚:默认情况下,Spring会对所有的运行时异常进行事务回滚,当然也可以设置在出现何种异常时进行事务回滚。
@Transactional(propagation=Propagation.REQUIREL_NEW),isolation=Isolation.READ_COMMITTED,rollbackFor={自定义异常类1.class,自定义异常类2.class,...}
(1)rollbackFor:参数为class类型的数组,定义哪些异常时回滚;
(2)rollbackForClassName:参数为字符串类型的数组,定义哪些异常时回滚;
(3)noRollbackFor:参数为class类型的数组,定义哪些异常时不回滚;
(4)noRollbackForClassName:参数为字符串类型的数组,定义哪些异常时不回滚。
2.4 事务的只读设置
@Transactional(propagation=Propagation.REQUIREL_NEW),isolation=Isolation.READ_COMMITTED,readOnly=false
readOnly:true为只读,代表只会对数据库进行读取操作,不会有修改操作,当设置为此值时,spring会认为该事务中只会有读取操作,那么就不会对该事务加锁,但是实际上我们也可以进行修改操作,但是这时就会出现问题,所以不建议设置此值后在方法中进行修改操作。false:是默认值,非只读,代表既会对数据库进行读操作,也可能会对数据库进行修改操作,所以设置为此值时,Spring会认为该事务中既有读又有写,便会对事务加锁。
2.5 事务的超时设置
事务的超时设置:设置事务在强制回滚之前可以占用的时间。这个超时设置存在的意义在于为防止事务因为某些原因卡在某个地方无法继续下去,为防止阻塞设置超时进行回滚。@Transactional(propagation=Propagation.REQUIREL_NEW),isolation=Isolation.READ_COMMITTED,readOnly=false,timeout=3
,这里的3指的是3秒钟,超过3秒钟就强制回滚。
三、基于xml方式配置声明式事务
<!---数据源->
<context:property-placeholder location="classpath:db.properties"/>
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--事务管理器-->
<bean id=" dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="datasource" ref="dataSource"></property>
</bean>
<!--基于xml配置事务管理 事务管理器 事务属性设置-->
<tx:advice transaction-manager="dataSourceTransactionManafer" id="txAdvice">
<!--配置事务属性-->
<tx:attributes>
<!--配置哪些方法需要使用事务-->
<tx:method name="buyBook" isolation=“DEFAULT” propagation
="REQUIRED" read-only=“false” timeout=“3”/>
<tx:method name="checkOut" isolation=“DEFAULT” propagation
="REQUIRED" read-only=“false” timeout=“3”/>
<!--约定方法的名字-->
<!--查询操作:selectXXX-->
<tx:method name="select*" read-only=“true”/>
<!--修改操作:updateXXX-->
<tx:method name="update*" read-only=“true” />
<!--除了上述指定的方法之外的所有方法-->
<tx:method name="*" />
</tx:attributes>
</tx:advice>
<aop:config>
<!--切入点表达式-->
<aop:pointcut expression="execution(* com.xxx.service.*.*(..)) " id="txPointCut"/>
<!--切入点表达式与事务配置的结合-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut" />
</aop:config>