对于数据库操作,仅仅会使用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来配置较为简单。而只需要在配置文件中开启事务管理,然后再需要事务的方法或者类上面使用注解来进行事务管理。