赞
踩
问题描述:一个经典的转账问题
业务需求:数据表如下,现让tom向jerry转账100元,看一下能引发哪些问题
环境搭建:项目框架主要是spring,持久层框架暂时没用mybtis,用的是spring 的JdbcTemplate,连接池c3p0
项目结构:
applicationContext.xml文件主要配置:
<!--加载外部的properties配置文件--> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <!--配置元数据--> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"></property> <property name="jdbcUrl" value="${jdbc.url}"></property> <property name="user" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--注入依赖--> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="accountDao" class="com.szly.dao.Impl.AccountDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean> <bean id="accountService" class="com.szly.service.Impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean>
dao层实现
import com.szly.dao.AccountDao; import org.springframework.jdbc.core.JdbcTemplate; import java.sql.SQLException; public class AccountDaoImpl implements AccountDao { private JdbcTemplate jdbcTemplate; public AccountDaoImpl() { } public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override public void out(String outMan, int money) { jdbcTemplate.update("update account set balance=balance-? where username = ?", money, outMan); } @Override public void in(String inMan, int money) { jdbcTemplate.update("update account set balance=balance+? where username = ?", money, inMan); } }
service层实现
import com.szly.dao.AccountDao; import com.szly.service.AccountService; import org.springframework.jdbc.core.JdbcTemplate; import java.sql.Connection; import java.sql.SQLException; public class AccountServiceImpl implements AccountService { private AccountDao accountDao; private JdbcTemplate jdbcTemplate; public AccountServiceImpl() { } public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override public void transfer(String outMan, String inMan, int money) { Connection connection = null; try { connection = this.jdbcTemplate.getDataSource().getConnection(); // 取消事务的自动提交 connection.setAutoCommit(false); accountDao.out(outMan, money); // 制造个异常 int i = 1 / 0; accountDao.in(inMan, money); // 提交事务 connection.commit(); } catch (Exception e) { e.printStackTrace(); try { // 回滚事务 connection.rollback(); System.out.println("转账失败!"); } catch (Exception e1) { e1.printStackTrace(); } } } }
controller层实现
import com.szly.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AccountController {
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService = app.getBean(AccountService.class);
accountService.transfer("tom", "jerry", 100);
}
}
我们期望在调用transfer业务进行转账时,如果转账过程中遇到了异常,就进行事务的回滚。
所以取消了事务的自动提交,手动造了一个 / by zero的异常,当捕捉到异常时,将不会提交事务,而是进行事务的回滚。因为此时转账失败,所以我们期望的结果是tom和jerry都还有1000的余额。然而,结果和我们的预想并不一样。what ?100元不翼而飞,事务回滚竟然失败了?
我们再来查看一下数据库的当前的引擎是否支持业务
mysql-5.1版本之前默认引擎是MyISAM,之后是InnoDB,InnoDB是支持业务的。如果不是InnoDB,可以使用alter table 表名 engine=innodb;修改指定表的引擎。
此时的引擎也支持业务,但为什么就是回滚不成功呢?在网上找了好久,终于找到了错误所在。手动提交事务时不能保证事务一致性的! 因为jdbcTemplate.getDataSource().getConnection()获取的connection与每次jdbcTemplate.update用到的connection都是从连接池中获取的,不能保证是一个connection。下面我们来验证一下
对dao层AccountDaoImpl进行以下改造,打印一下当前Connection对象的地址
在AccountServiceImpl中也填加上这一行代码,这里先注释掉之前造的异常,让程序顺利执行完,便于观察结果
输出结果如下:
com.mchange.v2.c3p0.impl.NewProxyConnection@38364841
com.mchange.v2.c3p0.impl.NewProxyConnection@67e2d983
com.mchange.v2.c3p0.impl.NewProxyConnection@568bf312
发现三个地址都不相同,那也就说明这三个连接不是同一个连接,因此也就不能正常地手动控制事务了。那么该怎么解决呢?
我们可以使用Spring基于xml的声明式事务控制,下面对applicationContext.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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" 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"> <!--加载外部的properties配置文件--> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <!--配置元数据--> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"></property> <property name="jdbcUrl" value="${jdbc.url}"></property> <property name="user" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--注入依赖--> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="accountDao" class="com.szly.dao.Impl.AccountDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean> <!--目标对象,内部的转账方法就是切点--> <bean id="accountService" class="com.szly.service.Impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> <!--<property name="jdbcTemplate" ref="jdbcTemplate"></property>--> </bean> <!--配置平台事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!--通知 即事务的增强--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!--设置事务的属性信息--> <tx:attributes> <!--切点方法的事务参数配置--> <tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"></tx:method> </tx:attributes> </tx:advice> <!--配置事务的aop织入--> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.szly.service.Impl.*.*(..))"></aop:advisor> </aop:config> </beans>
可以将transfer方法中手动提交事务的代码全部删掉,只留下以下代码即可。这样做也就将事务管理的业务从service层中抽取了出来,交给spring来处理,从而也就降低了耦合性,便于后期维护,提高开发效率。
@Override
public void transfer(String outMan, String inMan, int money) {
accountDao.out(outMan, money);
int i = 1/0;
accountDao.in(inMan, money);
}
把数据表里的数据恢复原样后,再次进行测试,引发异常后,查看数据表里的数据没有发生任何变化,成功的进行了事务的控制
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。