javascript
SSM框架中使用Spring的@Transactional注解进行事务管理
一 介紹
在企業級應用中,保護數據的完整性是非常重要的一件事。因此不管應用的性能是多么的高、界面是多么的好看,如果在轉賬的過程中出現了意外導致用戶的賬號金額發生錯誤,那么這樣的應用程序也是不可接受的
數據庫的事務管理可以有效地保護數據的完整性(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=rootiii)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注解进行事务管理的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: Chapter 1 First Sigh
 - 下一篇: Leetcode中Path的题目总结