600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > SSM框架中使用Spring的@Transactional注解进行事务管理

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

时间:2018-08-07 20:51:41

相关推荐

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

一 介绍

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

数据库的事务管理可以有效地保护数据的完整性(PS:关于数据库的事务管理基础可以参考我以前写过的这篇文章:/385.html),但是原生态的事务操作需要写不少的代码,无疑是非常麻烦的。在使用了Spring框架的应用中,我们可以使用@Transactional 注解方便地进行事务操作,如事务的回滚等。接下来我将以SSM框架中的事务注解操作进行举例说明:

二 测试项目的搭建

(1)项目结构和用到的jar包:

(2)测试使用到的SQL文件:

SETFOREIGN_KEY_CHECKS=0;--------------------------------Tablestructureforuser------------------------------DROPTABLEIFEXISTS`user`;CREATETABLE`user`(`id`int(11)NOTNULLAUTO_INCREMENT,`name`varchar(32)DEFAULTNULL,`password`varchar(64)DEFAULTNULL,`email`varchar(64)DEFAULTNULL,`birthday`dateDEFAULTNULL,`money`decimal(15,2)DEFAULTNULL,PRIMARYKEY(`id`))ENGINE=InnoDBAUTO_INCREMENT=4DEFAULTCHARSET=utf8;--------------------------------Recordsofuser------------------------------INSERTINTO`user`VALUES('1','admin','123456','admin@','2000-01-02','1000.00');INSERTINTO`user`VALUES('2','test','1234','test@','1990-12-12','2500.00');INSERTINTO`user`VALUES('3','xxxx','xx','xx@','1723-06-21','4000.00');

(3)使用mybatis-generator结合Ant脚本快速自动生成Model、Mapper等文件:

关于这方面可以参考我以前写过的一篇文章,这里就不多说了,传送门:/431.html

(4)一些基础配置文件:

i)web.xml:

<web-appxmlns="/xml/ns/javaee"xmlns:xsi="/2001/XMLSchema-instance"xsi:schemaLocation="/xml/ns/javaee/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.Drivermaster.jdbc.url=jdbc:mysql://127.0.0.1:3306/transaction#usermaster.jdbc.username=rootmaster.jdbc.password=root

iii)context.xml:

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

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

iv)jsp-dispatcher.xml:

<?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="/schema/beans"xmlns:xsi="/2001/XMLSchema-instance"xmlns:context="/schema/context"xmlns:cache="/schema/cache"xmlns:mvc="/schema/mvc"xsi:schemaLocation="/schema/beans/schema/beans/spring-beans-4.0.xsd/schema/context/schema/context/spring-context-4.0.xsd/schema/cache/schema/cache/spring-cache-4.0.xsd/schema/mvc/schema/mvc/spring-mvc-4.0.xsd"default-lazy-init="true"><mvc:annotation-driven/><context:annotation-config/><!--激活Bean中定义的注解--><!--启动自动扫描该包下所有的Bean(例如@Controller)--><context:component-scanbase-package="cn.zifangsky.controller"annotation-config="true"/><!--定义视图解析器--><beanclass="org.springframework.web.servlet.view.InternalResourceViewResolver"><propertyname="prefix"><value>/WEB-INF/jsp/</value></property><propertyname="suffix"><value>.jsp</value></property></bean></beans>

v)sql-map-config.xml:

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

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

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

publicinterfaceUserManager{intdeleteByPrimaryKey(Integerid);intinsert(Userrecord);intinsertSelective(Userrecord);UserselectByPrimaryKey(Integerid);intupdateByPrimaryKeySelective(Userrecord);intupdateByPrimaryKey(Userrecord);}

ii)UserManagerImpl.java:

packagecn.zifangsky.manager.impl;importjava.math.BigDecimal;importjavax.annotation.Resource;importorg.apache.ibatis.jdbc.RuntimeSqlException;importorg.springframework.stereotype.Service;importorg.springframework.transaction.annotation.Transactional;importcn.zifangsky.manager.UserManager;importcn.zifangsky.mapper.UserMapper;importcn.zifangsky.model.User;@Service(value="userManagerImpl")publicclassUserManagerImplimplementsUserManager{@Resource(name="userMapper")privateUserMapperuserMapper;publicintdeleteByPrimaryKey(Integerid){return0;}publicintinsert(Userrecord){return0;}publicintinsertSelective(Userrecord){return0;}publicUserselectByPrimaryKey(Integerid){returnuserMapper.selectByPrimaryKey(id);}publicintupdateByPrimaryKeySelective(Userrecord){return0;}publicintupdateByPrimaryKey(Userrecord){return0;}}

iii)UserController.java:

packagecn.zifangsky.controller;importjava.math.BigDecimal;importjavax.annotation.Resource;importorg.springframework.stereotype.Controller;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RequestParam;importcn.zifangsky.manager.UserManager;importcn.zifangsky.model.User;@ControllerpublicclassUserController{@Resource(name="userManagerImpl")privateUserManageruserManager;@RequestMapping(value="/select")publicStringuser(@RequestParam(name="userId",required=false)IntegeruserId){Useruser=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@

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

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

/***转账**@paramsourceAccountId*源账户*@paramtargetAccountId*目标账户*@paramamount*转账金额*/voidtransferMoney(IntegersourceAccountId,IntegertargetAccountId,BigDecimalamount);

此方法目的是为了模拟转账操作

(2)在UserManagerImpl实现类中添加对用的实现方法:

@Transactional(rollbackFor=Exception.class)publicvoidtransferMoney(IntegersourceAccountId,IntegertargetAccountId,BigDecimalamount){UsersourceAccount=userMapper.selectByPrimaryKey(sourceAccountId);UsertargetAccount=userMapper.selectByPrimaryKey(targetAccountId);BigDecimalsourceMoney=sourceAccount.getMoney();BigDecimaltargetMoney=targetAccount.getMoney();//判断账户余额是否足够if(pareTo(amount)>0){sourceAccount.setMoney(sourceMoney.subtract(amount));targetAccount.setMoney(targetMoney.add(amount));//更新数据库userMapper.updateByPrimaryKeySelective(sourceAccount);thrownewRuntimeSqlException("手动模拟转账时出现异常");//userMapper.updateByPrimaryKeySelective(targetAccount);}}

可以看出,在这个方法上面申明了一个@Transactional,表明这个方法将要进行事务管理,同时需要说明的是rollbackFor参数定义了在出现了什么异常时进行事务的回滚,显然这里定义的就是所有的Exception都要进行事务回滚。与之相反的一个参数是norollbackFor,这里就不多说了。对于@Transactional注解我们不仅可以在一个方法上放置,而且可以在类上进行申明。如果在类级别上使用该注解,那么类中的所有公共方法都被事务化,否则就只有使用了@Transactional注解的公共方法才被事务化

在这个方法中为了模拟转账出现异常,因此在第一个账户进行更新后就手动抛出了一个异常

(3)在UserController类中添加一个模拟转账的方法:

@RequestMapping(value="/transfer")publicStringtransfer(@RequestParam(name="account1")Integeraccount1,@RequestParam(name="account2")Integeraccount2,@RequestParam(name="amount")Longamount){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 注解给去掉看看数据库中的这个金额在执行“转账”后又会不会发生改变?

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。