一 介绍

在企业级应用中,保护数据的完整性是非常重要的一件事。因此不管应用的性能是多么的高、界面是多么的好看,如果在转账的过程中出现了意外导致用户的账号金额发生错误,那么这样的应用程序也是不可接受的

数据库的事务管理可以有效地保护数据的完整性(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/javaeehttp://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>/*</url-pattern></filter-mapping></web-app>

ii)jdbc配置文件jdbc.properties:

master.jdbc.driverClassName=com.mysql.jdbc.Driver
master.jdbc.url=jdbc:mysql://127.0.0.1:3306/transaction
#user
master.jdbc.username=root
master.jdbc.password=root

iii)context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:cache="http://www.springframework.org/schema/cache"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jee="http://www.springframework.org/schema/jee"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsdhttp://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsdhttp://www.springframework.org/schema/cache  http://www.springframework.org/schema/cache/spring-cache-4.0.xsd  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"xmlns:aop="http://www.springframework.org/schema/aop"><context:component-scan base-package="cn.zifangsky.dao"annotation-config="true" /><context:component-scan base-package="cn.zifangsky.manager"annotation-config="true" /><bean id="propertyConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><property name="locations"><list><value>classpath:jdbc.properties</value></list></property></bean><!-- 配置数据源 --><bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"destroy-method="close"><property name="driverClass"><value>${master.jdbc.driverClassName}</value></property><property name="jdbcUrl"><value>${master.jdbc.url}</value></property><property name="user"><value>${master.jdbc.username}</value></property><property name="password"><value>${master.jdbc.password}</value></property><!--连接池中保留的最小连接数。 --><property name="minPoolSize"><value>5</value></property><!--连接池中保留的最大连接数。Default: 15 --><property name="maxPoolSize"><value>30</value></property><!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 --><property name="initialPoolSize"><value>10</value></property><!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 --><property name="maxIdleTime"><value>60</value></property><!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 --><property name="acquireIncrement"><value>5</value></property><!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements 属于单个 connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。 如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0 --><property name="maxStatements"><value>0</value></property><!--每60秒检查所有连接池中的空闲连接。Default: 0 --><property name="idleConnectionTestPeriod"><value>60</value></property><!--定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 --><property name="acquireRetryAttempts"><value>30</value></property><!--获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效 保留,并在下次调用 getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试 获取连接失败后该数据源将申明已断开并永久关闭。Default: false --><property name="breakAfterAcquireFailure"><value>true</value></property><!--因性能消耗大请只在需要的时候使用它。如果设为true那么在每个connection提交的 时候都将校验其有效性。建议 使用idleConnectionTestPeriod或automaticTestTable 等方法来提升连接测试的性能。Default: false --><property name="testConnectionOnCheckout"><value>false</value></property></bean><!-- SqlMap setup for iBATIS Database Layer --><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="configLocation" value="classpath:context/sql-map-config.xml" /><property name="dataSource" ref="dataSource" /></bean><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="cn.zifangsky.mapper" /><property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /></bean><bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"><constructor-arg index="0" ref="sqlSessionFactory" /></bean><!-- Transaction manager for a single JDBC DataSource --><bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource" /></bean><!-- 开启注解方式声明事务 --><tx:annotation-driven transaction-manager="transactionManager" /></beans>

在上面的配置中,使用了C3P0作为数据库连接池,同时定义了自动扫描注解,Mybatis相关配置以及申明式事务管理,如果对这些基础不太熟的话可以参考下我以前写过的一些文章

iv)jsp-dispatcher.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:cache="http://www.springframework.org/schema/cache"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsdhttp://www.springframework.org/schema/cache  http://www.springframework.org/schema/cache/spring-cache-4.0.xsd  http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd"default-lazy-init="true"><mvc:annotation-driven /><context:annotation-config />  <!-- 激活Bean中定义的注解 --><!-- 启动自动扫描该包下所有的Bean(例如@Controller) --><context:component-scan base-package="cn.zifangsky.controller"annotation-config="true" /><!-- 定义视图解析器 --><beanclass="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix"><value>/WEB-INF/jsp/</value></property><property name="suffix"><value>.jsp</value></property></bean></beans>

v)sql-map-config.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><settings><!-- 全局的映射器启用或禁用缓存。 --><setting name="cacheEnabled" value="true" /><!-- 全局启用或禁用延迟加载 --><setting name="lazyLoadingEnabled" value="true" /><!-- 允许或不允许多种结果集从一个单独的语句中返回 --><setting name="multipleResultSetsEnabled" value="true" /><!-- 使用列标签代替列名 --><setting name="useColumnLabel" value="true" /><!-- 允许JDBC支持生成的键 --><setting name="useGeneratedKeys" value="false" /><!-- 配置默认的执行器 --><setting name="defaultExecutorType" value="SIMPLE" /><!-- 设置超时时间 --><setting name="defaultStatementTimeout" value="25000" /></settings><mappers><mapper resource="sqlmaps/UserMapper.xml" /></mappers>
</configuration>

(5)测试搭建的项目环境:

i)在UserManager.java接口中添加几个基本的接口:

public interface UserManager {int deleteByPrimaryKey(Integer id);int insert(User record);int insertSelective(User record);User selectByPrimaryKey(Integer id);int updateByPrimaryKeySelective(User record);int updateByPrimaryKey(User record);
}

ii)UserManagerImpl.java:

package cn.zifangsky.manager.impl;import java.math.BigDecimal;import javax.annotation.Resource;import org.apache.ibatis.jdbc.RuntimeSqlException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import cn.zifangsky.manager.UserManager;
import cn.zifangsky.mapper.UserMapper;
import cn.zifangsky.model.User;@Service(value="userManagerImpl")
public class UserManagerImpl implements UserManager{@Resource(name="userMapper")private UserMapper userMapper;public int deleteByPrimaryKey(Integer id) {return 0;}public int insert(User record) {return 0;}public int insertSelective(User record) {return 0;}public User selectByPrimaryKey(Integer id) {        return userMapper.selectByPrimaryKey(id);}public int updateByPrimaryKeySelective(User record) {return 0;}public int updateByPrimaryKey(User record) {return 0;}
}

iii)UserController.java:

package cn.zifangsky.controller;import java.math.BigDecimal;import javax.annotation.Resource;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;import cn.zifangsky.manager.UserManager;
import cn.zifangsky.model.User;@Controller
public class UserController {@Resource(name = "userManagerImpl")private UserManager userManager;@RequestMapping(value = "/select")public String user(@RequestParam(name = "userId", required = false) Integer userId) {User user = userManager.selectByPrimaryKey(userId);System.out.println("用户名: " + user.getName());System.out.println("邮箱: " + user.getEmail());return "success";}}

iv)启动项目并进行测试:

项目启动后访问:http://localhost:8090/TransactionDemo/select.html?userId=2 ,如果发现控制台中输出如下则说明测试环境已经搭建成功了:

用户名: test
邮箱: test@zifangsky.cn

三 使用@Transactional注解管理事务示例

(1)在UserManager接口中添加一个如下方法:

/*** 转账* * @param sourceAccountId*            源账户* @param targetAccountId*            目标账户* @param amount*            转账金额*/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  注解给去掉看看数据库中的这个金额在执行“转账”后又会不会发生改变?

转载于:https://blog.51cto.com/983836259/1835807

SSM框架中使用Spring的@Transactional注解进行事务管理相关推荐

  1. Spring Boot 中使用 @Transactional 注解配置事务管理

    From: https://blog.csdn.net/nextyu/article/details/78669997 事务管理是应用系统开发中必不可少的一部分.Spring 为事务管理提供了丰富的功 ...

  2. Spring学习总结(33)—— 用 Spring 的 @Transactional 注解控制事务有哪些不生效的场景?

    数据库引擎不支持事务 这里以 MySQL 为例,其 MyISAM 引擎是不支持事务操作的,InnoDB 才是支持事务的引擎,一般要支持事务都会使用 InnoDB.根据 MySQL 的官方文档: htt ...

  3. 【SSM框架系列】Spring - JdbcTemplate声明式事务

    JdbcTemplate概述 以往使用jdbc时,每次都需要自己获取PreparedStatement,执行sql语句,关闭连接等操作.操作麻烦冗余,影响编码的效率. Spring把对数据库的操作在j ...

  4. SSM框架中 出现的406 (Not Acceptable)

    首先,需要清楚,http state 406代表什么意思: 406是HTTP协议状态码的一种,表示无法使用请求的特性来响应请求的网页.一般指客户端浏览器不接受所请求页面的MIME类型. 出现这样的错误 ...

  5. SSM框架终极篇——Spring、SpringMVC、MyBatis整合练习(超级详细)

    SSM框架终极篇--Spring.SpringMVC.MyBatis整合练习 一.准备工作 环境: 新版IDEA MySQL 5.7 Tomcat 9.0.2 Maven 要求: 熟练掌握MySQL数 ...

  6. Spring的Transactional注解

    Spring的Transactional注解主要有以下功能: 1. 标注在方法上,如果该方法掉了多个别的方法,每个方法都有对数据库做数据更改,如果这些更改需要保持一致性,这时就可以用到这个注解. 2. ...

  7. SSM框架整合(Spring+SpringMVC+MyBatis)

    输出结果 1.Maven Web项目创建 之前有写过Eclipse+Maven创建web项目的帖子,如果需要,请参考这里写链接内容 创建好项目之后因为入下图: 2.SSM整合 2.1 引入需要的JAR ...

  8. 【SSM框架系列】Spring IoC(控制反转) DI(依赖注入)

    Spring是什么 Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和 AOP(Aspect Orie ...

  9. Spring的@Transactional注解踩坑

    @Transactional介绍 Spring为开发人员提供了声明式事务的使用方式,即在方法上标记@Transactional注解来开启事务.大家在日常的开发中很多业务代码对数据进行操作的时候一定是希 ...

最新文章

  1. [转]url传递中文的解决方案总结
  2. 总结开发Silverlight项目准则(转)
  3. 11、HTML <head>标签
  4. linux unix系统区别,Unix和Linux操作系统有什么区别?看这里!
  5. [Python人工智能] 二十七.基于BiLSTM-CRF的医学命名实体识别研究(下)模型构建
  6. c语言圈子,C语言经典编程
  7. 计算机作文叙事,电脑争夺战叙事作文
  8. Tableau数据分析:NC Retail Order Data(英)Data Science Program Lab#1(GTI)
  9. pdfContentByte 类 图像和文本的绝对位置
  10. C语言--使用fopen、fgets、fprintf(标准IO)实现注册、登录、功能--(完整代码-分文件)
  11. python读取excel写入mysql_python读取excel写入mysql
  12. php案例纠错,PHP编程纠错指南
  13. String s = new String(“xyz“);创建了几个字符串对象?
  14. 关于Bitmapimage图片保存(png格式)
  15. 前端 - base64原理浅析
  16. 储油罐的变位识别与罐容表标定
  17. RGCF: Refined Graph Convolution Collaborative Filering withConcise and Expressive Embedding
  18. vuepress-theme-reco@1.x 解决博客首页 与 仓库README不兼容问题
  19. 谈谈玩游戏遇到的问题(巫师3)
  20. 爱普生Epson Stylus SX235W 一体机驱动

热门文章

  1. python中change的用法_vue中select的使用、默认选择、onchange/change事件等操作实例
  2. java filter 注解_Spring常用注解及自定义Filter的实现
  3. 社区生鲜领军者钱大妈携手神策数据,加速线上线下精准导流促增长
  4. InputStream 、 InputStreamReader 、 BufferedReader区别
  5. html _ 提取html片段内的纯文本
  6. oracle 物化视图 ORA-23413: 表 xxx.xx 不带实体化视图日志
  7. Sql2008的行列转换之行转列
  8. django 灵活的后台查询
  9. hadoop-2.4.0完全分布式集群搭建
  10. 慎用AXIS2(续)