分享

深入spring事务管理

 印度阿三17 2018-09-26

Spring事务的本质其实就是数据库对事务的支持,在没有spring提供事务管理之前,纯JDBC事务管理机制是利用java.sql.Connection对象完成对事务的提交;示例如下:

public static void main(String[] args) throws SQLException {
	//1.获取连接
	Connection conn = null;
	try {  
		conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/xx_db", "root", "root");
		//2.开启事务:将自动提交设置为false   
		conn.setAutoCommit(false);                        
		//3.执行CRUD操作
		//4.当两个操作成功后手动提交 
		conn.commit();       
	} catch (Exception e) { 
		//一旦其中一个操作出错都将回滚,所有操作都不成功
		if(conn!=null){conn.rollback(); }
		e.printStackTrace();  
	} finally {
		//5.关闭连接
		if(conn!=null){conn.close();}
	}
}

而使用Spring的事务管理功能后,我们可以不再写步骤 1、2 、4、5 的代码,而是由Spring 自动完成,即通过AOP,Spring擦除了大量的try…catch…finally语句、打开关闭数据库和事务回滚提交等冗余代码。

接下来我们主要来讲下Spring数据库事务管理:
1.事务管理器的设计与配置
Spring提供能够事务管理器的模板是org.springframework.transaction.support.TransactionTemplate,其源码里主要包含一个PlatformTransactionManager接口,事务的创建、提交、回滚都是通过这个接口完成的,默认事务异常时会回滚,我们也可以通过配置修改在某些异常发生时不回滚事务;
PlatformTransactionManager是一个事务管理器,也是根管理器,其实在spring中有多种事务管理器,例如DataSourceTransactionManager,HibernateTransactionManager,WebSphereTransactionManager,JtaTransactionManager 等等;PlatformTransactionManager接口源码如下:

public interface PlatformTransactionManager {
	//获取事务状态
	TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
	//提交事务
	void commit(TransactionStatus status) throws TransactionException;
	//回滚事务
	void rollback(TransactionStatus status) throws TransactionException;
}

了解了事务管理的设计思路,接下来就以DataSourceTransactionManager为例配置事务管理器(MyBatis框架):
(1)XML配置
-XML的命名空间里引入事务命名空间:
http://www./schema/tx
http://www./schema/tx/spring-tx-4.0.xsd
-XML中定义一个数据库连接池的
-配置数据源事务管理器,注入数据库连接池,内容如下:

<bean id="transactionManager"
	class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource" />
</bean>

经过如上三步配置,Spring就知道已将数据库事务委托给事务管理器了;
(2)Java配置

@Configuration
@ComponentScan("cn.infocore.transaction.*")
@EnableTransactionManagement  //使用事务驱动管理器
public class JavaConfig implements TransactionManagementConfigurer{
	private DataSource ds=null;
	
	//配置数据源
	@Bean(name="ds")
	public DataSource initDB(){
		if(ds!=null){
			return ds;
		}
		Properties props=new Properties();
		props.setProperty("driverClassName", "com.mysql.jdbc.Driver");
		props.setProperty("url", "jdbc:mysql://localhost:3306/xx_db");
		props.setProperty("username", "root");
		props.setProperty("password", "root");
		props.setProperty("maxActive", "200");
		props.setProperty("maxIdle", "20");
		props.setProperty("maxWait", "30000");
		try {
			ds=BasicDataSourceFactory.createDataSource(props);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return ds;
	}
	
	//配置JdbcTemplate
	@Bean(name="jdbcTemplate")
	public JdbcTemplate initJdbcTemplate(){
		JdbcTemplate jdbc=new JdbcTemplate();
		jdbc.setDataSource(initDB());
		return jdbc;
	}

	//实现接口方法,使得返回数据库事务管理
	@Override
	public PlatformTransactionManager annotationDrivenTransactionManager() {
		DataSourceTransactionManager manager=new DataSourceTransactionManager();
		manager.setDataSource(initDB());
		return manager;
	}
}

2.在spring中可以使用声明式事务和编程式事务,后者由于会产生冗余代码,现已几乎不用;声明式事务又可分为XML配置和注解事务,XML也已不常用,目前主流事务处理方法是@Transactional;
(1)编程式事务:事务的定义获取、SQL执行、事务的提交回滚都由开发者自己实现;唯一的优点就是代码流程清晰,但不推荐使用,代码就不详述了;
(2)声明式事务:一种约定型事务,spring给了一个约定,AOP技术;当业务正常或异常时,spring会让事务管理器提交或回滚事务;介于声明式事务是重点,因此会详细分析,先看下流程图和Transactional源码:
在这里插入图片描述

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
	//定义事务管理器,值是IoC容器中的一个beanId,这个bean需实现接口PlatformTransactionManager
	@AliasFor("transactionManager")
	String value() default ""; 
	//同上
	@AliasFor("value")
	String transactionManager() default "";
	//传播行为
	Propagation propagation() default Propagation.REQUIRED;
	//隔离级别
	Isolation isolation() default Isolation.DEFAULT;
	//超时时间,单位秒,会引发异常,默认会导致事务回滚
	int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
	//是否开启只读事务,默认false
	boolean readOnly() default false;
	//回滚事务的异常类定义:当产生所定义异常时,才会回滚
	Class<? extends Throwable>[] rollbackFor() default {};
	//回滚事务异常类名定义,同上,只是这个是使用类名称定义
	String[] rollbackForClassName() default {};
	//当产生哪些异常时不回滚事务
	Class<? extends Throwable>[] noRollbackFor() default {};
	//当产生哪些类名称定义的异常时,不回滚事务
	String[] noRollbackForClassName() default {};
}

Transactional源码所定义的属性都会被spring放到事务定义类TransactionDefinition中,接下来就是如何使用了;以最常使用的@Transactional注解为例:
-XML配置事务拦截器:定义事务属性与作用类

<!--配置注解驱动,加入下面一行,就可以使用@Transactional配置事务了-->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 事务拦截器:拦截正则表达式匹配的方法 -->
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
	<property name="transactionManager" value="transactionManager" /> <!-- 事务管理器 -->
	<property name="transactionAttributes"><!-- 配置事务属性 -->
		<props>
			<!-- key代表业务方法的正则表达式,内容是配置各类事务定义参数 -->
			<prop key="insert*">PROPAGATION_REQUIRED,ISOLATION_READ_UNCOMMITTED</prop>
			<prop key="select*">PROPAGATION_REQUIRED,readOnly</prop>
		</props>
	</property>
</bean>
<!-- 事务拦截器:拦截哪些类 -->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
	<property name="beanNames">
		<list><value>*ServiceImpl</value></list>
	</property>
	<property name="interceptorNames">
		<list><value>transactionInterceptor</value></list>
	</property>
</bean>

在定义完事务管理器、拦截器后,接下来就是编写业务代码了,示例如下:

@Autowired
private RoleDao roleDao=null;

@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,timeout=3)
public int insertRole(Role role){
	return roleDao.insert(role);
}

3.数据库相关
(1)数据库事务ACID特性:
原子性Atomicity:整个事务中所有操作要么全部成功要么全部失败,中间异常会回滚;
一致性Consistency:事务在完成时,必须是所有的数据都保持一致状态;
隔离性Isolation:并发事务执行之间无影响,在一个事务内部的操作对其他事务是不产生影响,这需要事务隔离级别来指定隔离性;
持久性Durability:一旦事务完成,数据库的改变必须是持久化的;

主要说一下隔离性,这个涉及到隔离级别,需要设置隔离级别是因为当同一数据被多个事务同时访问时,压制丢失更新的产生;注意只是压制,而不是消除,因为考虑到性能问题,过多的锁会导致大量线程被挂起和恢复,从而导致系统缓慢;
第一类丢失更新:操作同一数据,一个事务回滚而另一个事务操作后提交而引发数据不一致;目前大部分数据库已克服这类丢失;
第二类丢失更新:操作同一数据,多个事务同时提交而引发数据不一致;此类丢失目前有如下4中隔离级别:
(1)未提交读READ UNCOMMITTED
允许一个事务读取另一个事务未提交的数据;最低隔离,最危险(读取到另一个事务的未提交数据操作后,另一个事务回滚了,就出现脏读),应用不大;优点是并发能力高,适合对数据一致性没有高要求而追求高并发的场景;
(2)读写提交READ COMMITTED
一个事务只能读取另外一个事务已提交数据;会出现不可重复读(针对一条记录)问题;
(3)可重复读
针对(2)中出现的不可重复读,如某个数据被一个事务读取,另一个事务再去读取只能阻塞直到前一个事务提交;会出现幻读(针对多条记录)问题;
(4)串行化SERIALIZABLE
所有SQL按照顺序执行,最高隔离;
总之,(1)可能出现脏读、不可重复读、幻读;(2)可能出现不可重复读、幻读;(3)可能出现幻读;所以使用时,要结合性能和数据一致性一起考虑,一般会以读写提交为主;Oracle只支持读写提交和串行化,默认读写提交;MySQL都支持,默认可重复读;当业务并发量不是很大的情况,可以使用串行化来保证数据一致性;

(2)传播行为
方法之间调用事务采取的策略问题;一般情况下,数据库事务都是要么全部成功要么全部失败的机制,但也会出现其他情况,例如批量操作,只需要回滚失败操作即可,这就涉及到事务的传播行为;
传播行为有如下7种:
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED):需要事务,默认,如当前存在事务,就沿用,否则新建一个事务运行子方法;
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS):支持事务,如当前存在事务,就沿用,不存在就继续采用无事务方式;
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY):必须使用事务,如当前存在事务,就沿用,否则抛出异常;
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW):新建事务,无论当前事务是否存在,都新建新事务,新事务有自己独立的隔离级别与锁等特性;
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED):不支持事务,当前存在事务时,挂起事务;
NEVER(TransactionDefinition.PROPAGATION_NEVER):不支持事务,当前存在事务时,抛出异常,否则继续采用无事务方式;
NESTED(TransactionDefinition.PROPAGATION_NESTED):当前方式调用子方法时,子方法发生异常,只回滚子方法SQL,新事务沿用当前事务的隔离级别和锁等机制;如果数据库支持保存点技术,就启用,否则就等价于REQUIRES_NEW;

4.一些注意点
(1)@Transactional,可以注解在类或方法或接口上,注解在类上表示这个类的所有public非静态方法都将启用事务功能,推荐放在实现类上;其底层实现是Spring AOP技术,而AOP使用的是动态代理,那对于静态方法和非public方法,注释@Transactional是失效的;
(2)@Transactional自调用是失效的,即在@Transactional注解的方法或类中调用自己类中定义的方法是失效的,是因为AOP的原理是动态代理,自调用是类自身的调用,而不是代理对象去调用,就不会产生AOP,Spring就不能把代码织入约定流程;
解决方法:
-用一个Service去调用另一个Servic,即再写一个Service;
-从Spring IoC容器中获取代理对象getBean()去启用AOP;
(3)典型错误
–使用带有事务的Service定义的方法时(有@Transactional注解),多次调用,并不在同一个事务里,spring每次都会创建新的数据库事务,完成后就会释放,因此这种操作是不可能解决都成功或失败的提交或回滚的;
–过长时间占用事务:使用数据库事务过程中,有占用时间比较久的且与数据库事务无关的操作(读写文件、通信连接等),在并发请求多且性能要求高的环境就会出现请求卡顿的情况,严重的可能会出现宕机;建议将这类操作提取出来,在事务关闭后执行,这样就可以避免长时间占用事务导致系统性能降低;
–错误捕捉异常:主要是开发者在代码中使用try…catch…捕获了异常,导致spring在数据库事务所约定的流程中再也得不到任何异常信息了,因此就会提交事务,并不会因为异常而回滚了;
解决方法:在catch里
-手动抛出异常:throw new RuntimException(ex);
-手动事务回滚: TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
–异常种类:Spring管理事务默认是RuntimeException或者Error,对于非运行时异常来说,可以通过设置rollbackfor(发生指定异常时回滚)、rollbackForClassName、noRollbackFor(发生指定异常时不会滚)、noRollbackForClassName来指定在什么异常的情况下依旧提交事务,在什么异常下回滚事务;例如:@Transactional(rollbackFor = Exception.class),表示发生非运行时异常时回滚;

来源:http://www./content-4-24736.html

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多