一 介绍
在企业级应用中,保护数据的完整性是非常重要的一件事。因此不管应用的性能是多么的高、界面是多么的好看,如果在转账的过程中出现了意外导致用户的账号金额发生错误,那么这样的应用程序也是不可接受的
数据库的事务管理可以有效地保护数据的完整性(PS:关于数据库的事务管理基础可以参考我以前写过的这篇文章:http://www.zifangsky.cn/385.html),但是原生态的事务操作需要写不少的代码,无疑是非常麻烦的。在使用了Spring框架的应用中,我们可以使用@Transactional 注解方便地进行事务操作,如事务的回滚等。接下来我将以SSM框架中的事务注解操作进行举例说明:
二 测试项目的搭建
(1)项目结构和用到的jar包:
(2)测试使用到的SQL文件:
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) DEFAULT NULL,
`password` varchar(64) DEFAULT NULL,
`email` varchar(64) DEFAULT NULL,
`birthday` date DEFAULT NULL,
`money` decimal(15,2) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'admin', '123456', 'admin@qq.com', '2000-01-02', '1000.00');
INSERT INTO `user` VALUES ('2', 'test', '1234', 'test@zifangsky.cn', '1990-12-12', '2500.00');
INSERT INTO `user` VALUES ('3', 'xxxx', 'xx', 'xx@zifangsky.cn', '1723-06-21', '4000.00');
(3)使用mybatis-generator结合Ant脚本快速自动生成Model、Mapper等文件:
关于这方面可以参考我以前写过的一篇文章,这里就不多说了,传送门:http://www.zifangsky.cn/431.html
(4)一些基础配置文件:
i)web.xml:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:context/context.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:context/jsp-dispatcher.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>
void transferMoney(Integer sourceAccountId, Integer targetAccountId, BigDecimal amount);
此方法目的是为了模拟转账操作
(2)在UserManagerImpl实现类中添加对用的实现方法:
@Transactional(rollbackFor=Exception.class)
public void transferMoney(Integer sourceAccountId, Integer targetAccountId, BigDecimal amount) {
User sourceAccount = userMapper.selectByPrimaryKey(sourceAccountId);
User targetAccount = userMapper.selectByPrimaryKey(targetAccountId);
BigDecimal sourceMoney = sourceAccount.getMoney();
BigDecimal targetMoney = targetAccount.getMoney();
//判断账户余额是否足够
if(sourceMoney.compareTo(amount) > 0){
sourceAccount.setMoney(sourceMoney.subtract(amount));
targetAccount.setMoney(targetMoney.add(amount));
//更新数据库
userMapper.updateByPrimaryKeySelective(sourceAccount);
throw new RuntimeSqlException("手动模拟转账时出现异常");
// userMapper.updateByPrimaryKeySelective(targetAccount);
}
}
可以看出,在这个方法上面申明了一个@Transactional,表明这个方法将要进行事务管理,同时需要说明的是rollbackFor参数定义了在出现了什么异常时进行事务的回滚,显然这里定义的就是所有的Exception都要进行事务回滚。与之相反的一个参数是norollbackFor,这里就不多说了。对于@Transactional注解我们不仅可以在一个方法上放置,而且可以在类上进行申明。如果在类级别上使用该注解,那么类中的所有公共方法都被事务化,否则就只有使用了@Transactional注解的公共方法才被事务化
在这个方法中为了模拟转账出现异常,因此在第一个账户进行更新后就手动抛出了一个异常
(3)在UserController类中添加一个模拟转账的方法:
@RequestMapping(value = "/transfer")
public String transfer(@RequestParam(name = "account1") Integer account1,
@RequestParam(name = "account2") Integer account2, @RequestParam(name = "amount") Long amount) {
System.out.println("转账之前:");
System.out.println("账户一的资金:" + userManager.selectByPrimaryKey(account1).getMoney().longValue());
System.out.println("账户二的资金:" + userManager.selectByPrimaryKey(account2).getMoney().longValue());
// 转账
userManager.transferMoney(account1, account2, BigDecimal.valueOf(amount));
System.out.println("转账之后:");
System.out.println("账户一的资金:" + userManager.selectByPrimaryKey(account1).getMoney().longValue());
System.out.println("账户二的资金:" + userManager.selectByPrimaryKey(account2).getMoney().longValue());
return "success";
}
(4)效果测试:
项目运行后访问:http://localhost:8090/TransactionDemo/transfer.html?account1=1&account2=2&amount=500
可以发现项目会进行保存,这时我们查看数据库中看看账户1和账户2中的金额有没有发生变化:
可以看出,两者的金额都没有发生改变,说明事物的确进行了回滚。当然,有兴趣的同学可以把UserManagerImpl中那个 @Transactional 注解给去掉看看数据库中的这个金额在执行“转账”后又会不会发生改变?