目錄
5.1 Spring事務管理概述
Spring事務管理簡化了傳統的事務管理流程,減少了開發量
5.1.1 事務管理的核心接口
在Spring的所有JAR包中包含一個名爲Spring-tx-4.3.6.RELEASE的JAR包,該包就是Spring提供的用於事務管理的依賴包。在該JAR包的org.Springframework.transaction包中有3個接口文件:
1. PlatformTransactionManager
PlatformTransactionManager接口是Spring提供的平臺事務管理器,主要用於管理事務。該接口中提供了3個事務操作的方法
- TransactionStatus getTransaction(TransactionDefinition definition):用於獲取事務狀態信息。該方法會根據TransactionDefinition參數返回一個TransactionStatus對象。TransactionStatus對象表示一個事務,被關聯在當前執行的線程上。
- void commit(TransactionStatus status):用於提交事務。
- void rollback(TransactionStatus status):用於回滾事務。
TransactionDefinition和TransactionStatus,PlatformTransactionManager接口只是代表事務管理的接口,並不知道底層是如何管理事務的,它只需要事務管理提供上面的3個方法,但具體如何管理事務則由它的實現類來完成。
PlatformTransactionManager,PlatformTransactionManager接口有許多不同的實現類,常見的幾個實現類如下。
- org.springframework.jdbc.datasource.DataSourceTransactionManager:用於配置JDBC數據源的事務管理器。
- org.springframework.orm.Hibernate4.HibernateTransactionManager:用於配置Hibernate的事務管理器。
- org.springframework.transaction.jta.JtaTransactionManager:用於配置全局事務管理器。
當底層採用不同的持久層技術時,系統只需使用不同的PlatformTransactionManager實現類即可。
這裏使用的是MyBatis
2. TransactionDefinition
TransactionDefinition接口是事務定義(描述)的對象,該對象中定義了事務規則,並提供了獲取事務相關信息的方法
- string getName():獲取事務對象名稱。
- int getlsolationLeve():獲取事務的隔離級別
- int getPropagationBehavior():獲取事務的傳播行爲。
- int setTimeout():獲取事務的超時時間。
- boolean isReadOnly():獲取事務是否只讀。
上述方法中,事務的傳播行爲是指在同一個方法中,不同操作前後所使用的事務。傳播行爲有很多種
3. TransactionStatus
TransactionStatus接口是事務的狀態,描述了某一時間點上事務的狀態信息。該接口中包含6個方法
- void flush():刷新事務。
- boolean hasSavepoint():獲取是否存在保存點。
- boolean isCompleted():獲取事務是否完成。
- boolean isNewTransaction():獲取是否是新事務。
- boolean isRollbackOnly():獲取是否回滾。
- void setRollbackOnly():設置事務回滾。
5.1.2 事務管理的方式
Spring中的事務管理分爲兩種方式
- 傳統的編程序事務管理
編程序事務管理:通過編寫代碼實現的事務管理,包括定義事務的開始、正常執行後的事務提交和異常時的事務回滾。 - 聲明式事務管理
聲明式事務管理:通過AOP技術實現的事務管理,其主要思想是將事務管理作爲一個“切面”代碼單獨編寫,然後通過AOP技術將事務管理的“切面”代碼植入業務目標類中。
聲明式事務管理最大的優點在於開發者無須通過編程的方式來管理事務,只需在配置文件中進行相關的事務規則聲明,就可以將事務規則應用到業務邏輯中。
5.2 聲明式事務管理
Spring的聲明式事務管理可以通過兩種方式來實現:
- 基於XML的方式
- 基於Annotation的方式
5.2.1 基於XML方式的聲明式事務
基於XML方式的聲明式事務管理是通過在配置文件中配置事務規則的相關聲明來實現的。
- Spring 2.0以後,提供了tx命名空間來配置事務,tx命名空間下提供了< tx:advice>元素來配置事務的通知(增強處理)。當使用< tx:advice>元素配置了事務的增強處理後,就可以通過編寫的AOP配置讓Spring自動對目標生成代理。
配置< tx:advice>元素時,通常需要指定id和transaction-manager屬性,其中id屬性是配置文件中的唯一標識,transaction-manager屬性用於指定事務管理器。除此之外,還需要配置一個< tx:attributes>子元素,該子元素可通過配置多個< tx:method>子元素來配置執行事務的細節。
關於< tx:method>元素的屬性描述如圖
實例一,基於Xml方式的聲明式事務
-
創建項目(模擬一個會員贈送積分的功能,要求在贈送積分時通過Spring對事務進行控制,即是進行異常發現並處理),引入jar包。
-
修改數據庫中的數據表user,增加字段jf(積分),設置初始積分全部爲1000,
-
在User類中增加jf成員變量和對應的構造方法
package com.ssm.jdbc;
public class User {
private Integer id;
private String username;
private String password;
private Integer jf;
public Integer getJf() {
return jf;
}
public void setJf(Integer jf) {
this.jf = jf;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String toString() {
return "User [id=" + id + ", username=" + username + ", password=" + password + ", jf=" + jf + "]";
}
}
- 在UserDao中增加一個對積分的操作的方法
package com.ssm.jdbc;
import java.util.List;
public interface UserDao {
public int addUser(User user);
public int updateUser(User user);
public int deleteUser(int id);
//通過id查詢用戶
public User findUserById(int id);
//查詢所有用戶
public List<User> findAllUser();
//贈送積分
public void transfer(String outUser, String inUser, Integer jf);
}
- 在實現類中實現這個方法
package com.ssm.jdbc;
import java.util.List;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
public class UserDaoImpl implements UserDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public int addUser(User user) {
String sql="insert into user(username,password) value(?,?)";
Object[] obj=new Object[]{
user.getUsername(),
user.getPassword()
};
int num=this.jdbcTemplate.update(sql,obj);
return num;
}
public int updateUser(User user) {
String sql="update user set username=?,password=? where id=?";
Object[] params=new Object[]{
user.getUsername(),
user.getPassword(),
user.getId()
};
int num=this.jdbcTemplate.update(sql,params);
return num;
}
public int deleteUser(int id) {
String sql="delete from user where id=?";
int num=this.jdbcTemplate.update(sql,id);
return num;
}
//通過id查詢用戶數據信息
public User findUserById(int id) {
String sql="select * from user where id=?";
RowMapper<User> rowMapper=new BeanPropertyRowMapper<User>(User.class);
return this.jdbcTemplate.queryForObject(sql,rowMapper,id);
}
//查詢所有用戶數據信息
public List<User> findAllUser() {
String sql="select * from user";
RowMapper<User> rowMapper=new BeanPropertyRowMapper<User>(User.class);
return this.jdbcTemplate.query(sql,rowMapper);
}
//贈送積分
public void transfer(String outUser, String inUser, Integer jf) {
//接收積分
this.jdbcTemplate.update("update user set jf=jf+? where username=?",jf,inUser);
//模擬系統運行時的突發性問題
int i=1/0;
//贈送積分
this.jdbcTemplate.update("update user set jf =jf-? where username=?", jf, outUser);
}
}
在上述代碼中,使用了兩個update()方法對user表中的數據執行接收積分和贈送積分的更新操作。在兩個操作之間添加了一行代碼“int i=1/0;”來模擬系統運行時的突發性問題。如果沒有事務控制,那麼在transfer()方法執行後,接收積分用戶的積分會增加,而贈送積分用戶的積分會因爲系統出現問題而不變,這顯然是有問題的;如果增加了事務控制,那麼在transfer()方法操作執行後,接收積分用戶的積分和贈送積分用戶的積分在問題出現前後都應該保持不變。
- 修改配置文件,增加編寫事務管理的相關配置代碼
<?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-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!--1.配置數據源 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--數據庫驅動 -->
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<!--連接數據庫的ur1 -->
<property name="url" value="jdbc:mysql://localhost:3306/db_spring" />
<!--連接數據庫的用戶名 -->
<property name="username" value="root" />
<!--連接數據庫的密碼 -->
<property name="password" value="root" />
</bean>
<!--2.配置JDBC模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--默認必須使用數據源 -->
<property name="dataSource" ref="dataSource" />
</bean>
<!--3.定義id爲userDao的Bean -->
<bean id="userDao" class="com.ssm.jdbc.UserDaoImpl">
<!--將 jdbcTemplate注入到 userDao實例中 -->
<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
<!--4.事務管理器,依賴於數據源 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--5.編寫通知:對事務進行增強(通知),需要編寫對切入點和具體執行事務細節 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" isolation="DEFAULT" read-only="false"/>
</tx:attributes>
</tx:advice>
<!--6.編寫aop,讓spring自動對目標生成代理,需要使用AspectJ的表達式 -->
<aop:config>
<!--切入點 -->
<aop:pointcut expression="execution(* com.ssm.jdbc.*.*(..))" id="txPointCut" />
<!--切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
</beans>
- 創建測試類
@Test
public void xmlTest(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao=(UserDao)applicationContext.getBean("userDao");
userDao.transfer("zhangsan","lisi", 100);
System.out.println("贈送積分成功!");
}
如果事務代碼起作用,那麼在整個贈送積分方法執行完畢後,zhangsan和lisi的積分應該都是原來的值。
執行完測試方法後,JUnit控制檯的顯示結果如圖。
從中可以看到,JUnit控制檯中報出了“/ y zero”的算術異常信息。
在執行贈送積分操作後,查看user表中的數據,zhangsan和lisi的積分沒有發生變化,這說明Spring中的事務管理配置已經生效。
5.2.2 基於Annotation方式的聲明式事務
Spring的聲明式事務管理還可以通過Annotation的方式實現
第一步
在Spring容器中註冊事務註解驅動
< tx:annotation-driven transaction-managers
ntransactionManager"/>
第二步
在需要使用事務的Spring Bean類或者Bean類的方法上添加註解@Transactional。
如果將註解添加在Bean類上,就表示事務的設置對整個Bean類的所有方法都起作用;如果將註解添加在Bean類中的某個方法上,就表示事務的設置只對該方法有效。
使用@Transactional註解時,可以通過其參數配置事務詳情。
@Transactional註解可配置的參數信息如圖
從圖可以看出,@Transactional註解與< tx:method>元素中的事務屬性基本是對應的,並且其含義也基本相似。
實例二,基於註解方式的聲明式事務
在上例中直接進行修改
- 創建新的配置文件applicationContext-annotation.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-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!--1.配置數據源 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--數據庫驅動 -->
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<!--連接數據庫的ur1 -->
<property name="url" value="jdbc:mysql://localhost:3306/db_spring" />
<!--連接數據庫的用戶名 -->
<property name="username" value="root" />
<!--連接數據庫的密碼 -->
<property name="password" value="root" />
</bean>
<!--2.配置JDBC模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--默認必須使用數據源 -->
<property name="dataSource" ref="dataSource" />
</bean>
<!--3.定義id爲userDao的Bean -->
<bean id="userDao" class="com.ssm.jdbc.UserDaoImpl">
<!--將 jdbcTemplate注入到 userDao實例中 -->
<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
<!--4.事務管理器,依賴於數據源 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--5.註冊事務管理器驅動 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
注意1
如果使用了註解式開發,就需要在配置文件中開啓註解處理器,指定掃描哪些包下的註解。這裏沒有開啓註解處理器是因爲在配置文件中已經配置了UserDaoImpl類的Bean,而@Transactional註解就配置在該Bean類中,所以可以直接生效。
- 在UserDaoImpl實現類中的transfer方法上添加註解,添加後
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false)
public void transfer(String outUser, String inUser, Integer jf) {
//接收積分
this.jdbcTemplate.update("update user set jf=jf+? where username=?",jf,inUser);
//模擬系統運行時的突發性問題
int i=1/0;
//贈送積分
this.jdbcTemplate.update("update user set jf =jf-? where username=?", jf, outUser);
}
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false)
注意2
在實際開發中,事務的配置信息(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false)通常是在Spring的配置文件中完成的,而在業務層類上只需使用@Transactional註解即可,不需要配置@Transactional註解的屬性。
3.創建測試方法
@Test
public void annotationTest(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext-annotation.xml");
UserDao userDao=(UserDao)applicationContext.getBean("userDao");
userDao.transfer("zhangsan","lisi", 200);
System.out.println("贈送積分成功!");
}
執行結果如上