问题引出:
当我在进行某一个业务开发时,需要先在主表存入一条数据逻辑上上是在主表存入一条数据以后从表再存入主表的附属信息。但是再出现运行过程
中出现了异常导致主表的数据存入进去了从表却没有信息。举个例子在进行转账的时候往往会从一个账户转出一定金额到另一个
账户,不允许一个人的
账户的余额已经减少了钱而另一个人的账户却没有收到转账的情况,那么再这种时候在业务层对
数据库进行操作的
时候就必须保证要么转成功(甲转出
了500元,乙的账户多了500元)要么全部失败(甲没有转出乙也没有收到),
这种保证了数据的完整性和一致性的特性正好符合事务的特性。下面就模拟一个转账的例子来看一看Spring中的事务带来的效果。
首先描述一下业务:我在数据库中建立了一张账户表account里面有Id,balance两个字段。有一个操作account的Dao层
AccountDao具有转入
rollIn(int id,double money)和转出rollOut(int id,double money)两个方法。业务层AccountService封装
了一个转账的方法。
AccountDao层代码:
public interface AccountDao {
void rollIn(int id,double money);
void rollOut(int id,double money);
}
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public void rollIn(int id, double money) {
String sql = "update account set balance = balance + ? where id = ?";
this.getJdbcTemplate().update(sql,money,id);
}
@Override
public void rollOut(int id, double money) {
String sql = "update account set balance = balance - ? where id = ?";
this.getJdbcTemplate().update(sql,money,id);
}
}
AccountService层代码:
public interface AccountService {
void transfer(int in,int out,double money);
}
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(int in, int out, double money) {
accountDao.rollOut(out, money);
accountDao.rollIn(in, money);
}
}
测试层代码:
public class SpringTest {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
if(ac != null){
AccountService accountService = (AccountService) ac.getBean("accountService");
if(accountService != null){
accountService.transfer(1, 2, 500d);
}
}
}
}
上述代码是在没有异常的情况下进行的,没有出现转丢的情况。现在人为的修改业务层的代码,模拟出现的异常。
public void transfer(int in, int out, double money) {
accountDao.rollOut(out, money);
String temp = null;
temp.toCharArray();
accountDao.rollIn(in, money);
}
当代码执行到temp.toCharArray()的时候将会出现NullPointerExceprion异常,下面的账户转入操作将不会被执行,就出现了转丢钱的情况。
首先来看看Spring事务相关的三个关键类:
- PlatformTransactionManager:事务管理器接口,对于实现者,Spring提供了众多根据持久层框架而适配的派生类,如 HibernateTransactionManager、DataSourceTransactionManager、JtaTransactionManager等众多的派生类,具体参考官方API。
- TransactionDefintion:事务定义信息,包括事务的隔离级别、传播行为、超时、只读等。
- TransactionStatus:事务具体运行状态。
事务管理器接口PlatformTransactionManager
事务定义信息TansactionDefintion
事务隔离级别
传播行为
编程式事务:
- 在AccountService中注入TransactionTemplate来简化事务管理的操作
- 在TransactionTemplate中注入TransactionManager(真正的事务管理类)
- 在TransactionManager中注入数据源(拿到连接对象)
改造后的配置文件:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:oxm="http://www.springframework.org/schema/oxm"
xmlns:p="http://www.springframework.org/schema/p"
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/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据源 -->
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="0" />
<!-- 连接池最大使用连接数量 -->
<property name="maxActive" value="20" />
<!-- 连接池最大空闲 -->
<property name="maxIdle" value="20" />
<!-- 连接池最小空闲 -->
<property name="minIdle" value="0" />
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="60000" />
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class=" org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 参考JDBC进行事务管理的时候首先需要connection.setAutoCommit(false);具体的事务提交应该拿到连接对象,
故在此注入数据源的信息 -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务管理的模板 注入模板是为了简化事务管理的底层操作 真正进行事务管理的是TransactionManager -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<!-- 在模板中注入事务管理器 -->
<property name="transactionManager" ref="transactionManager"/>
</bean>
<!-- 配置业务 -->
<bean id="accountService" class="AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="transactionTemplate" ref="transactionTemplate"/>
</bean>
<!-- 配置数据库操作Dao层 -->
<bean id="accountDao" class="AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
改造后的service代码
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
/*提供set方法供属性注入*/
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
@Override
public void transfer(final int in, final int out, final double money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
accountDao.rollOut(out, money);
String temp = null;
temp.toCharArray();
accountDao.rollIn(in, money);
}
});
}
}
执行结果发现异常抛出,数据库里面的数据无仍和变化,既没有转出也没有转入。
声明式事务。