Spring简单入门(五)——事务管理

对于数据库操作,仅仅会使用JdbcTemplate是不够的,还需要学习事务管理。首先,明确事务是干什么的?事务时保证一组操作要么都成功要么都失败。经典的例子是转账操作:先扣A的钱,再加B的钱,如果中间发生异常,我们知道系统是会抛出异常并且不会执行后面的非finally语句,因此就会导致A扣了钱,但是B没加钱(用户及其崩溃)因此就需要配置事务来保证。

(1)三个基本接口

对于所有的有事务管理功能的框架,其基本上都离不开这三个接口。

①PlatformTransactionManager事务管理器接口。这是最重要的接口。其定义了事务的回滚和提交方法。因此,所有的事务管理框架都提供了对其的实现类。

Spring JDBC或iBatis使用的是DataSourceTransactionManager

Hibernate3使用的是HibernateTransactionManager

②TransactionDefinition接口:这个接口就是定义了事务对象的信息。定义了获取名称、事务隔离级别、事务传播行为、超时时间、是否只读等等。这些变量具体有何含义,并不是本文重点,因此不做介绍。但是在下文配置过程中需要用到,因此予以提出

③TransactionStatus接口:定义了事务具体的状态运行

(2)准备环境

导入jar包、导入约束自然不提。首先我们需要一个Dao层类来定义数据库操作,还需要一个业务层类来完成转账操作,还需要一个客户端类。还有配置文件中对Bean的注册

public class Dao extends JdbcDaoSupport{
	public void update(String name,float money) {
		this.getJdbcTemplate().update("update acount set money = money + ? where name = ?",money,name);
	}
}
public class Service {
	Dao dao;
	public void setDao(Dao dao) {
		this.dao = dao;
	}
	public void transfer(String source,String target,float money) {
		dao.update(source, -money);
		int i=1/0;//异常
		dao.update(target, money);
	}
}
public class Main {
	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("Test/beans.xml");
		Service service = (Service) context.getBean("Service");
		service.transfer("aaa", "bbb", 100.0f);
	}
}
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
		<property name="url" value=""></property>
		<property name="username" value=""></property>
		<property name="password" value=""></property>
</bean>		
<bean id="dao" class="Test.Dao">
    <property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="Service" class="Test.Service">
	<property name="dao" ref="dao"></property>
</bean>

如果带有异常行,可以发现只有aaa扣了钱,bbb并没加钱。

(3)基于XML的事务管理

实际上,事务的管理是aop操作的应用。个人理解为如下伪代码:

startTransaction();//开启事务
try {
	doService();//正常操作,例如转账
	commitTransaction();//如果没有异常,则提交事务
} catch (Exception e) {
	rollbackTransaction();//如果发生异常,则回滚事务
} finally {
	closeTransaction();//无论怎么样,关闭事务
}

因此,就此伪代码可以看到,事务的不同操作,只是其不同的通知方式。因此事务配置的关键在于aop的配置,所以这也就是为啥并没有配置rollback()和commit(),只是配置了几个通知就完成了事务通知的原因

首先,需要配置事务管理器实现类,其要传入dataSource参数。因此,该接口定义了rollback和commit方法

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

接下来其实本质上都是aop的操作。首先配置aop

<aop:config>
	<aop:pointcut expression="execution(* Test.*.*(..))" id="pt1"/>
	<aop:advisor advice-ref="transAdvice" pointcut-ref="pt1"/>
</aop:config>

这里要讲下<aop:advisor>,其是用来配置织入过程的,即将配置的增强advice-ref与切入点匹配。所以接下来是配置增强

<tx:advice id="transAdvice" transaction-manager="transactionManager">
	<tx:attributes>
		<tx:method name="*"/>
	</tx:attributes>
</tx:advice>

前面讲原理时我们发现,只要指定了业务方法和事务管理类对象,那么就可以完成这个事务管理。因此,<tx:advice>的id属性是该增强的名称,主要用于<aop:advisor>的advice-ref属性。 而transaction-manager属性是指定事务管理者,来提供commit()和rollback()的具体实现。

子标签<tx:attributes>是个标识标签,表示对事务进行配置,没有属性

但是到此,并没有结束。我们看到我们注册aop,但是还有个关键问题,在于我们并没有指定原理中的doService()方法。即,对那个方法进行事务管理。因此,我们使用子类<tx:method>来指定,name属性,就是要进行事务管理的一个或者一类方法,*则表示切入点的所有方法。

至此,再运行主程序后,发现抛出异常后,aaa和bbb的钱数都没有变化。因此事务是有效的。

但是,似乎少了点什么。我们可以发现,第一部分介绍的三个接口我们实际上只使用了第一个接口。剩下两个接口并没有使用。第三个接口实际上是一些isXXX方法,似乎不用配置也合理。但是第二个接口TransactionDefinition是对事务的配置信息,理应指定。

当然,但是之所以没有显式指定,是因此事务对这些属性都有默认值。那你说我就要修改默认指定该如何做?通过上文我们可以发现<tx:method>方法中指定了事务到底作用再那个切入点上(name属性),那么该标签还有一些属性可以来配置事务的相关属性。例如:isolation:事务隔离级别、timeout:超时时间、propagation:事务传播行为、read-only:是否是只读事务

(4)基于Xml+注解的事务管理

对象的创建可以使用xml也可以使用注解生成。将事务的配置由xml换成注解。但是要再注册文件中开启事务管理的注解:

<tx:annotation-driven transaction-manager="transactionManager"/>

其实可以看到,上面对事务的配置,实际上就是在指定到底是哪一个方法为doService(),因此。要在那个方法上加上@Transactional。当然,该注解也可以加在类和接口上,表示类和接口中所有的方法都进行事务。

总的来看,基于xml+注解的方法比纯xml要简洁许多。

(5)基于纯注解的事务管理

基于xml+注解的方式是最简便的方式。而基于纯注解的事务管理相对繁琐。原理主要是如果采用注解方式,DataSource、TransactionManager、JdbcTemplate、JdbcDaoSupport等方法由于成员变量为系统类,因此无法添加注解来实现注入。只能怪通过写一个create方法来手动创建,再使用@Bean将其对象放入IoC库,即如下:

@Configuration
public class JdbcConfig {
	@Bean("Dao")  //获得JdbcDaoSupport的实现类
	public Dao createTemplate(DataSource dataSource) {
		Dao daoSupport = new Dao();
		daoSupport.setDataSource(dataSource);
		return daoSupport;
	}
	@Bean("dataSource") //获得数据源
	public DataSource createDataSource() {
		DriverManagerDataSource dataSource = new DriverManagerDataSource();
		dataSource.setDriverClass("");
		dataSource.setJdbcUrl("");
		dataSource.setUser("");
		dataSource.setPassword("");
		return dataSource;
	}
	@Bean("transactionManager") //获得事务管理者
	public DataSourceTransactionManager createTransactionManager(DataSource dataSource) {
		return new DataSourceTransactionManager(dataSource);
	}
}

而那些自定义的类比较简单,直接注解注入即可,对于需要事务的方法或者类,同上使用@Transactional

@Component("Service")
@Transactional
public class Service {
	@Resource(name = "Dao")
	Dao dao;
	public void setDao(Dao dao) {
		this.dao = dao;
	}
	public void transfer(String source,String target,float money) {
		dao.update(source, -money);
		int i=1/0;//异常
		dao.update(target, money);
	}
}

而纯注解是需要一个注解配置类的,在该配置文件中,需要开启注解@EnableTransactionManager,这样就完全取代了xml文件。

@Configuration
@EnableTransactionManagement
@ComponentScan("Test")
public class Config {}

至此就完成了基于注解实现事务管理。

总结:通过学习可以发现,使用xml+注解方式来实现事务管理的方式是最简便的。系统类对象的创建使用xml来配置较为简单。而只需要在配置文件中开启事务管理,然后再需要事务的方法或者类上面使用注解来进行事务管理。

经验分享 程序员 微信小程序 职场和发展