Spring事務的本質其實就是數據庫對事務的支持,在沒有spring提供事務管理之前,純JDBC事務管理機制是利用java.sql.Connection對象完成對事務的提交;示例如下:
public static void main(String[] args) throws SQLException {
//1.獲取連接
Connection conn = null;
try {
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/xx_db", "root", "root");
//2.開啟事務:將自動提交設置為false
conn.setAutoCommit(false);
//3.執行CRUD操作
//4.當兩個操作成功后手動提交
conn.commit();
} catch (Exception e) {
//一旦其中一個操作出錯都將回滾,所有操作都不成功
if(conn!=null){conn.rollback(); }
e.printStackTrace();
} finally {
//5.關閉連接
if(conn!=null){conn.close();}
}
}
而使用Spring的事務管理功能后,我們可以不再寫步驟 1、2 、4、5 的代碼,而是由Spring 自動完成,即通過AOP,Spring擦除了大量的try…catch…finally語句、打開關閉數據庫和事務回滾提交等冗余代碼。
接下來我們主要來講下Spring數據庫事務管理:
1.事務管理器的設計與配置
Spring提供能夠事務管理器的模板是org.springframework.transaction.support.TransactionTemplate,其源碼里主要包含一個PlatformTransactionManager接口,事務的創建、提交、回滾都是通過這個接口完成的,默認事務異常時會回滾,我們也可以通過配置修改在某些異常發生時不回滾事務;
PlatformTransactionManager是一個事務管理器,也是根管理器,其實在spring中有多種事務管理器,例如DataSourceTransactionManager,HibernateTransactionManager,WebSphereTransactionManager,JtaTransactionManager 等等;PlatformTransactionManager接口源碼如下:
public interface PlatformTransactionManager {
//獲取事務狀態
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
//提交事務
void commit(TransactionStatus status) throws TransactionException;
//回滾事務
void rollback(TransactionStatus status) throws TransactionException;
}
了解了事務管理的設計思路,接下來就以DataSourceTransactionManager為例配置事務管理器(MyBatis框架):
(1)XML配置
-XML的命名空間里引入事務命名空間:
http://www./schema/tx
http://www./schema/tx/spring-tx-4.0.xsd
-XML中定義一個數據庫連接池的
-配置數據源事務管理器,注入數據庫連接池,內容如下:
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
經過如上三步配置,Spring就知道已將數據庫事務委托給事務管理器了;
(2)Java配置
@Configuration
@ComponentScan("cn.infocore.transaction.*")
@EnableTransactionManagement //使用事務驅動管理器
public class JavaConfig implements TransactionManagementConfigurer{
private DataSource ds=null;
//配置數據源
@Bean(name="ds")
public DataSource initDB(){
if(ds!=null){
return ds;
}
Properties props=new Properties();
props.setProperty("driverClassName", "com.mysql.jdbc.Driver");
props.setProperty("url", "jdbc:mysql://localhost:3306/xx_db");
props.setProperty("username", "root");
props.setProperty("password", "root");
props.setProperty("maxActive", "200");
props.setProperty("maxIdle", "20");
props.setProperty("maxWait", "30000");
try {
ds=BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return ds;
}
//配置JdbcTemplate
@Bean(name="jdbcTemplate")
public JdbcTemplate initJdbcTemplate(){
JdbcTemplate jdbc=new JdbcTemplate();
jdbc.setDataSource(initDB());
return jdbc;
}
//實現接口方法,使得返回數據庫事務管理
@Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
DataSourceTransactionManager manager=new DataSourceTransactionManager();
manager.setDataSource(initDB());
return manager;
}
}
2.在spring中可以使用聲明式事務和編程式事務,后者由于會產生冗余代碼,現已幾乎不用;聲明式事務又可分為XML配置和注解事務,XML也已不常用,目前主流事務處理方法是@Transactional;
(1)編程式事務:事務的定義獲取、SQL執行、事務的提交回滾都由開發者自己實現;唯一的優點就是代碼流程清晰,但不推薦使用,代碼就不詳述了;
(2)聲明式事務:一種約定型事務,spring給了一個約定,AOP技術;當業務正常或異常時,spring會讓事務管理器提交或回滾事務;介于聲明式事務是重點,因此會詳細分析,先看下流程圖和Transactional源碼:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
//定義事務管理器,值是IoC容器中的一個beanId,這個bean需實現接口PlatformTransactionManager
@AliasFor("transactionManager")
String value() default "";
//同上
@AliasFor("value")
String transactionManager() default "";
//傳播行為
Propagation propagation() default Propagation.REQUIRED;
//隔離級別
Isolation isolation() default Isolation.DEFAULT;
//超時時間,單位秒,會引發異常,默認會導致事務回滾
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
//是否開啟只讀事務,默認false
boolean readOnly() default false;
//回滾事務的異常類定義:當產生所定義異常時,才會回滾
Class<? extends Throwable>[] rollbackFor() default {};
//回滾事務異常類名定義,同上,只是這個是使用類名稱定義
String[] rollbackForClassName() default {};
//當產生哪些異常時不回滾事務
Class<? extends Throwable>[] noRollbackFor() default {};
//當產生哪些類名稱定義的異常時,不回滾事務
String[] noRollbackForClassName() default {};
}
Transactional源碼所定義的屬性都會被spring放到事務定義類TransactionDefinition中,接下來就是如何使用了;以最常使用的@Transactional注解為例:
-XML配置事務攔截器:定義事務屬性與作用類
<!--配置注解驅動,加入下面一行,就可以使用@Transactional配置事務了-->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 事務攔截器:攔截正則表達式匹配的方法 -->
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" value="transactionManager" /> <!-- 事務管理器 -->
<property name="transactionAttributes"><!-- 配置事務屬性 -->
<props>
<!-- key代表業務方法的正則表達式,內容是配置各類事務定義參數 -->
<prop key="insert*">PROPAGATION_REQUIRED,ISOLATION_READ_UNCOMMITTED</prop>
<prop key="select*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
<!-- 事務攔截器:攔截哪些類 -->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list><value>*ServiceImpl</value></list>
</property>
<property name="interceptorNames">
<list><value>transactionInterceptor</value></list>
</property>
</bean>
在定義完事務管理器、攔截器后,接下來就是編寫業務代碼了,示例如下:
@Autowired
private RoleDao roleDao=null;
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,timeout=3)
public int insertRole(Role role){
return roleDao.insert(role);
}
3.數據庫相關
(1)數據庫事務ACID特性:
原子性Atomicity:整個事務中所有操作要么全部成功要么全部失敗,中間異常會回滾;
一致性Consistency:事務在完成時,必須是所有的數據都保持一致狀態;
隔離性Isolation:并發事務執行之間無影響,在一個事務內部的操作對其他事務是不產生影響,這需要事務隔離級別來指定隔離性;
持久性Durability:一旦事務完成,數據庫的改變必須是持久化的;
主要說一下隔離性,這個涉及到隔離級別,需要設置隔離級別是因為當同一數據被多個事務同時訪問時,壓制丟失更新的產生;注意只是壓制,而不是消除,因為考慮到性能問題,過多的鎖會導致大量線程被掛起和恢復,從而導致系統緩慢;
第一類丟失更新:操作同一數據,一個事務回滾而另一個事務操作后提交而引發數據不一致;目前大部分數據庫已克服這類丟失;
第二類丟失更新:操作同一數據,多個事務同時提交而引發數據不一致;此類丟失目前有如下4中隔離級別:
(1)未提交讀READ UNCOMMITTED
允許一個事務讀取另一個事務未提交的數據;最低隔離,最危險(讀取到另一個事務的未提交數據操作后,另一個事務回滾了,就出現臟讀),應用不大;優點是并發能力高,適合對數據一致性沒有高要求而追求高并發的場景;
(2)讀寫提交READ COMMITTED
一個事務只能讀取另外一個事務已提交數據;會出現不可重復讀(針對一條記錄)問題;
(3)可重復讀
針對(2)中出現的不可重復讀,如某個數據被一個事務讀取,另一個事務再去讀取只能阻塞直到前一個事務提交;會出現幻讀(針對多條記錄)問題;
(4)串行化SERIALIZABLE
所有SQL按照順序執行,最高隔離;
總之,(1)可能出現臟讀、不可重復讀、幻讀;(2)可能出現不可重復讀、幻讀;(3)可能出現幻讀;所以使用時,要結合性能和數據一致性一起考慮,一般會以讀寫提交為主;Oracle只支持讀寫提交和串行化,默認讀寫提交;MySQL都支持,默認可重復讀;當業務并發量不是很大的情況,可以使用串行化來保證數據一致性;
(2)傳播行為
方法之間調用事務采取的策略問題;一般情況下,數據庫事務都是要么全部成功要么全部失敗的機制,但也會出現其他情況,例如批量操作,只需要回滾失敗操作即可,這就涉及到事務的傳播行為;
傳播行為有如下7種:
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED):需要事務,默認,如當前存在事務,就沿用,否則新建一個事務運行子方法;
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS):支持事務,如當前存在事務,就沿用,不存在就繼續采用無事務方式;
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY):必須使用事務,如當前存在事務,就沿用,否則拋出異常;
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW):新建事務,無論當前事務是否存在,都新建新事務,新事務有自己獨立的隔離級別與鎖等特性;
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED):不支持事務,當前存在事務時,掛起事務;
NEVER(TransactionDefinition.PROPAGATION_NEVER):不支持事務,當前存在事務時,拋出異常,否則繼續采用無事務方式;
NESTED(TransactionDefinition.PROPAGATION_NESTED):當前方式調用子方法時,子方法發生異常,只回滾子方法SQL,新事務沿用當前事務的隔離級別和鎖等機制;如果數據庫支持保存點技術,就啟用,否則就等價于REQUIRES_NEW;
4.一些注意點
(1)@Transactional,可以注解在類或方法或接口上,注解在類上表示這個類的所有public非靜態方法都將啟用事務功能,推薦放在實現類上;其底層實現是Spring AOP技術,而AOP使用的是動態代理,那對于靜態方法和非public方法,注釋@Transactional是失效的;
(2)@Transactional自調用是失效的,即在@Transactional注解的方法或類中調用自己類中定義的方法是失效的,是因為AOP的原理是動態代理,自調用是類自身的調用,而不是代理對象去調用,就不會產生AOP,Spring就不能把代碼織入約定流程;
解決方法:
-用一個Service去調用另一個Servic,即再寫一個Service;
-從Spring IoC容器中獲取代理對象getBean()去啟用AOP;
(3)典型錯誤
–使用帶有事務的Service定義的方法時(有@Transactional注解),多次調用,并不在同一個事務里,spring每次都會創建新的數據庫事務,完成后就會釋放,因此這種操作是不可能解決都成功或失敗的提交或回滾的;
–過長時間占用事務:使用數據庫事務過程中,有占用時間比較久的且與數據庫事務無關的操作(讀寫文件、通信連接等),在并發請求多且性能要求高的環境就會出現請求卡頓的情況,嚴重的可能會出現宕機;建議將這類操作提取出來,在事務關閉后執行,這樣就可以避免長時間占用事務導致系統性能降低;
–錯誤捕捉異常:主要是開發者在代碼中使用try…catch…捕獲了異常,導致spring在數據庫事務所約定的流程中再也得不到任何異常信息了,因此就會提交事務,并不會因為異常而回滾了;
解決方法:在catch里
-手動拋出異常:throw new RuntimException(ex);
-手動事務回滾: TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
–異常種類:Spring管理事務默認是RuntimeException或者Error,對于非運行時異常來說,可以通過設置rollbackfor(發生指定異常時回滾)、rollbackForClassName、noRollbackFor(發生指定異常時不會滾)、noRollbackForClassName來指定在什么異常的情況下依舊提交事務,在什么異常下回滾事務;例如:@Transactional(rollbackFor = Exception.class),表示發生非運行時異常時回滾;
來源:http://www./content-4-24736.html
|