搭建Spring事务管理的环境
要进行编程式事务管理或者是声明式事务管理开发,都需要先将开发环境搭建完毕。
构造环境所需要到Jar包,包括Spring环境的基本包和搭建事务所需要的Jar包:
我们模拟的是银行转账的事务环境,也就是转入转出金钱,还需要搭建一个数据库环境。
建立一个User表,内含id,name,money三个字段:
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`money` double DEFAULT NULL,
PRIMARY KEY (`id`)
)
并且引入记录数据库连接信息的外部配置文件 jdbc.properties:
#定义连接参数
jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/an?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
jdbc.username=root
jdbc.password=root
搭建好了数据库,就开始创建Service的接口和实现类,用来操作转账的业务。到时候事务也是要布置在这一层面上。
Service接口:AccountService
/*
* 转账的业务层接口
* */
public interface AccountService {
public void transfer(String from, String to, double money);
}
Service接口的实现类:AccountServiceImpl
/*
* 转账的业务层的实现类
* */
public class AccountServiceImpl implements AccountService {
//注入Dao
@Resource(name="accountDao")
private AccountDao accountDao;
/*
* from:转出账号
* to:转入账号
* money:转账金额
* */
@Override
public void transfer(String from, String to, double money) {
accountDao.outMoney(from, money);
accountDao.inMoney(to,money);
}
}
配置好了Service业务层,接下来要配置的是Dao层的接口和实现类。
Dao的接口:AccountDao
/*
* 转账DAO的接口
* */
public interface AccountDao {
//转出金钱,减钱
public void outMoney(String from, double money);
//转入金钱,加钱
public void inMoney(String to, double money);
}
Dao接口的实现类:AccountDaoImpl
在实现类中,通过继承JdbcDaoSupport 类的方式,简化了连接数据库的操作,无需再每次定义DataSource和jdbcTemplate对象,再进行数据库的操作。
通过对JdbcDaoSupport 类的dataSource的XML方式属性注入,调用setDataSource方法,set方法中书写了创建jdbcTemplate对象的操作,从而通过getJdbcTemplate方法获取模板操作的对象。
/*
* 转账DAO的实现类
*
* */
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
// 由于AccountDaoImpl类继承了JdbcDaoSupport类,类中有dataSource和jdbcTemplate成员属性
// 然后使用JdbcDaoSupport类中的setDataSource的方法
//setDataSource方法中会使用注入的dataSource属性,创建jdbcTemplate模板对象,并将其赋值给jdbcTemplate成员
//我们就可以直接使用jdbcTemplate模板对象了。
/*
* from:转出账号
* money:转出金额
* */
@Override
public void outMoney(String from, double money) {
this.getJdbcTemplate().update("update account set money = money - ? where name = ?", money, from);
}
/*
*转入账号:to
* 转入金钱 :money
* */
@Override
public void inMoney(String to, double money) {
this.getJdbcTemplate().update("update account set money = money + ? where name = ?", money, to);
}
}
当将Service层和Dao层创建完毕了,就应该进行applicationContext.xml配置文件的配置了,将Service层和Dao层交给Spring来管理
同时将C3PO的连接池对象也交给Spring管理,并且引入数据库连接配置文件,为C3PO类注入相关属性。
并且开启注解的组件扫描
为了减少编写事务的代码量,还需要配置平台事务管理器(DataSourceTransactionManager)和事务管理的模板(TransactionTemplate)
配置文件:tx.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--开启注解的组件扫描-->
<context:component-scan base-package="tx"/>
<!--配置Service=========-->
<bean name="accountService" class="tx.AccountServiceImpl"/>
<!--配置dao-->
<!--由于AccountDaoImpl类继承了JdbcDaoSupport类,可以使用JdbcDaoSupport类中的setDataSource的方法-->
<!--使用注入的dataSource属性,创建jdbcTemplate模板对象-->
<bean name="accountDao" class="tx.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--引入数据库参数文件-->
<!--第二种方式,通过context标签引入,常用-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置C3PO的连接池对象-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--配置平台事务管理器-->
<!--同时为其注入一个连接对象,以便帮助我们进行事务操作-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
<!--配置事务管理的模板-->
<!--只是为了方便创建事务,底层实现还是由事务管理器实现-->
<!--所以需要给此模板注入事务管理器的属性-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>
最后编写一个测试类,基本的事务管理环境就搭建好了,就可以进行事务的开发测试了。
SpringTest:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:tx.xml")
public class SpringTest {
@Resource(name = "accountService")
private AccountService accountService;
@Test
public void test() {
accountService.transfer("kk","kk2",50);
}
}
编程式事务管理
环境搭建好后,需要在使用事务管理的地方添加一个事务管理的模板,也就是要在业务层的实现类 AccountServiceImpl 中添加一个成员,并且为其注入属性:
@Resource(name = "transactionTemplate")
private TransactionTemplate transactionTemplate;
然后在AccountServiceImpl书写事务操作:
public class AccountServiceImpl implements AccountService {
//注入Dao
@Resource(name="accountDao")
private AccountDao accountDao;
//注入事务模板
@Resource(name = "transactionTemplate")
private TransactionTemplate transactionTemplate;
/*
* from:转出账号
* to:转入账号
* money:转账金额
* */
@Override
public void transfer(String from, String to, double money) {
//采用匿名内部类的方式,创建一个事务,抛出异常将会回滚数据,
// 采用的是PROPAGATION_REQUIRED传播行为(保证多个操作在同一个事务中)
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
accountDao.outMoney(from, money);
//设置一个bug,数据会回滚,则说明已经启动了事务
int d = 1/0;
accountDao.inMoney(to,money);
}
});
}
}
声明式事务管理(通过配置完成)— AOP的思想
声明式事务管理应用的是Spring的AOP的思想。
在操作前开启事务,操作完毕后关闭事务(环绕通知),在遇到异常情况的时候回滚数据。相当于是对业务操作进行增强,而且不涉及到源代码的修改。
同样的,声明式事务管理有两种配置方式,同时需要引入AOP的jar包:
-
XML方式
首先,按照上面的环境配置,重新复制一份新的环境,恢复一下转账的环境。
复制一份tx2.xml,并删除事务管理模板(TransactionTemplate)的bean。
配置事务的增强(tx:advice),事务的增强是固定的一种通知,有特定的格式,是一种规则
<!--配置事务的增强-->
<!--需要注入一个事务管理器-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--事务管理的规则-->
<!--增强的方法名和传播行为,还可以设置超时时间和隔离级别等-->
<tx:method name="transfer" propagation="REQUIRED" />
<!--而实际开发中一般会使用通配符扫描有相同前缀的方法-->
<!--<tx:method name="save*" propagation="REQUIRED" isolation="DEFAULT"/>-->
<!--<tx:method name="update*" propagation="REQUIRED" isolation="DEFAULT"/>-->
<!--<tx:method name="del*" propagation="REQUIRED" isolation="DEFAULT"/>-->
<!--<tx:method name="find*" read-only="true"/>-->
</tx:attributes>
</tx:advice>
然后进行AOP的配置,也就是要将增强配置到目标类:
<!--AOP的配置-->
<!--将事务规则应用到指定的切入点-->
<aop:config>
<aop:pointcut id="pointcut1" expression="execution(* tx2.AccountServiceImpl.transfer(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
</aop:config>
当配置完成以后,就已经为目标方法设定好了事务,只要直接运行测试类,执行这个方法,就会自动的使用事务。
- 注解方式
注解方式在开始搭建的基本开发环境上只需要做三件事
1.配置事务管理器
<!--配置平台事务管理器-->
<!--同时为其注入一个连接对象,以便帮助我们进行事务操作-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
2.开启注解事务:
<!--开启注解事务-->
<tx:annotation-driven transaction-manager="transactionManager"/>
3.在目标类AccountServiceImpl上添加一个@Transactional 注解
//事务的各类属性,就在注解上配置即可,如传播行为,隔离级别,超时时间等等
@Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED)
public class AccountServiceImpl implements AccountService {
............
}
总结
对于上述三种Spring事务管理方式,有一个共同点,都是需要在xml配置文件中配置一个 事务管理器(DataSourceTransactionManager)
<!--配置平台事务管理器-->
<!--同时为其注入一个连接对象,以便帮助我们进行事务操作-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
对于声明式事务来说,两个方式都具有各自的好处
XML方式的好处在于只需要在配置文件中配置完成,就不需要在去理业务层的逻辑,只用再配置文件中修改事务的规则即可,缺点就是配置麻烦。
而注解方式的好处就是在于便捷,极少的代码,只需要三步,配置事务管理器,开启注解事务,类上注解(@Transactional ),就可以为目标类开启事务了(事务的各项属性,都是在注解上配置)。缺点就是不够灵活,在每次书写业务层类的时候,都必须要记得在类上添加注解。