国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書(shū)等14項(xiàng)超值服

開(kāi)通VIP
Spring 事務(wù)管理高級(jí)應(yīng)用難點(diǎn)剖析: 第 1 部分

概述

Spring 最成功,最吸引人的地方莫過(guò)于輕量級(jí)的聲明式事務(wù)管理,僅此一點(diǎn),它就宣告了重量級(jí) EJB 容器的覆滅。Spring 聲明式事務(wù)管理將開(kāi)發(fā)者從繁復(fù)的事務(wù)管理代碼中解脫出來(lái),專(zhuān)注于業(yè)務(wù)邏輯的開(kāi)發(fā)上,這是一件可以被拿來(lái)頂禮膜拜的事情。但是,世界并未從此消停,開(kāi)發(fā)人員需要面對(duì)的是層出不窮的應(yīng)用場(chǎng)景,這些場(chǎng)景往往逾越了普通 Spring 技術(shù)書(shū)籍的理想界定。因此,隨著應(yīng)用開(kāi)發(fā)的深入,在使用經(jīng)過(guò) Spring 層層封裝的聲明式事務(wù)時(shí),開(kāi)發(fā)人員越來(lái)越覺(jué)得自己墜入了迷霧,陷入了沼澤,體會(huì)不到外界所宣稱(chēng)的那種暢快淋漓。本系列文章的目標(biāo)旨在整理并剖析實(shí)際應(yīng)用中種種讓我們迷茫的場(chǎng)景,讓陽(yáng)光照進(jìn)云遮霧障的山頭。


DAO 和事務(wù)管理的牽絆

很少有使用 Spring 但不使用 Spring 事務(wù)管理器的應(yīng)用,因此常常有人會(huì)問(wèn):是否用了 Spring,就一定要用 Spring 事務(wù)管理器,否則就無(wú)法進(jìn)行數(shù)據(jù)的持久化操作呢?事務(wù)管理器和 DAO 是什么關(guān)系呢?

也許是 DAO 和事務(wù)管理如影隨行的緣故吧,這個(gè)看似簡(jiǎn)單的問(wèn)題實(shí)實(shí)在在地存在著,從初學(xué)者心中涌出,縈繞在開(kāi)發(fā)老手的腦際。答案當(dāng)然是否定的!我們都知道:事務(wù)管理是保證數(shù)據(jù)操作的事務(wù)性(即原子性、一致性、隔離性、持久性,也即所謂的 ACID),脫離了事務(wù)性,DAO 照樣可以順利地進(jìn)行數(shù)據(jù)的操作。

下面,我們來(lái)看一段使用 Spring JDBC 進(jìn)行數(shù)據(jù)訪(fǎng)問(wèn)的代碼:


清單 1. UserJdbcWithoutTransManagerService.java
            package user.withouttm;            import org.springframework.beans.factory.annotation.Autowired;            import org.springframework.jdbc.core.JdbcTemplate;            import org.springframework.stereotype.Service;            import org.springframework.context.ApplicationContext;            import org.springframework.context.support.ClassPathXmlApplicationContext;            import org.apache.commons.dbcp.BasicDataSource;            @Service("service1")            public class UserJdbcWithoutTransManagerService {            @Autowired            private JdbcTemplate jdbcTemplate;            public void addScore(String userName,int toAdd){            String sql = "UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?";            jdbcTemplate.update(sql,toAdd,userName);            }            public static void main(String[] args) {            ApplicationContext ctx =            new ClassPathXmlApplicationContext("user/withouttm/jdbcWithoutTransManager.xml");            UserJdbcWithoutTransManagerService service =            (UserJdbcWithoutTransManagerService)ctx.getBean("service1");            JdbcTemplate jdbcTemplate = (JdbcTemplate)ctx.getBean("jdbcTemplate");            BasicDataSource basicDataSource = (BasicDataSource)jdbcTemplate.getDataSource();            //①.檢查數(shù)據(jù)源autoCommit的設(shè)置            System.out.println("autoCommit:"+ basicDataSource.getDefaultAutoCommit());            //②.插入一條記錄,初始分?jǐn)?shù)為10            jdbcTemplate.execute(            "INSERT INTO t_user(user_name,password,score) VALUES('tom','123456',10)");            //③.調(diào)用工作在無(wú)事務(wù)環(huán)境下的服務(wù)類(lèi)方法,將分?jǐn)?shù)添加20分            service.addScore("tom",20);            //④.查看此時(shí)用戶(hù)的分?jǐn)?shù)            int score = jdbcTemplate.queryForInt(            "SELECT score FROM t_user WHERE user_name ='tom'");            System.out.println("score:"+score);            jdbcTemplate.execute("DELETE FROM t_user WHERE user_name='tom'");            }            }            

jdbcWithoutTransManager.xml 的配置文件如下所示:


清單 2. jdbcWithoutTransManager.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:p="http://www.springframework.org/schema/p"            xsi:schemaLocation="http://www.springframework.org/schema/beans            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd            http://www.springframework.org/schema/context            http://www.springframework.org/schema/context/spring-context-3.0.xsd">            <context:component-scan base-package="user.withouttm"/>            <!-- 數(shù)據(jù)源默認(rèn)將autoCommit設(shè)置為true -->            <bean id="dataSource"            class="org.apache.commons.dbcp.BasicDataSource"            destroy-method="close"            p:driverClassName="oracle.jdbc.driver.OracleDriver"            p:url="jdbc:oracle:thin:@localhost:1521:orcl"            p:username="test"            p:password="test"/>            <bean id="jdbcTemplate"            class="org.springframework.jdbc.core.JdbcTemplate"            p:dataSource-ref="dataSource"/>            </beans>            

運(yùn)行 UserJdbcWithoutTransManagerService,在控制臺(tái)上打出如下的結(jié)果:

defaultAutoCommit:true            score:30            

在 jdbcWithoutTransManager.xml 中,沒(méi)有配置任何事務(wù)管理器,但是數(shù)據(jù)已經(jīng)成功持久化到數(shù)據(jù)庫(kù)中。在默認(rèn)情況下,dataSource 數(shù)據(jù)源的 autoCommit 被設(shè)置為 true ―― 這也意謂著所有通過(guò) JdbcTemplate 執(zhí)行的語(yǔ)句馬上提交,沒(méi)有事務(wù)。如果將 dataSource 的 defaultAutoCommit 設(shè)置為 false,再次運(yùn)行 UserJdbcWithoutTransManagerService,將拋出錯(cuò)誤,原因是新增及更改數(shù)據(jù)的操作都沒(méi)有提交到數(shù)據(jù)庫(kù),所以 ④ 處的語(yǔ)句因無(wú)法從數(shù)據(jù)庫(kù)中查詢(xún)到匹配的記錄而引發(fā)異常。

對(duì)于強(qiáng)調(diào)讀速度的應(yīng)用,數(shù)據(jù)庫(kù)本身可能就不支持事務(wù),如使用 MyISAM 引擎的 MySQL 數(shù)據(jù)庫(kù)。這時(shí),無(wú)須在 Spring 應(yīng)用中配置事務(wù)管理器,因?yàn)榧词古渲昧?,也是沒(méi)有實(shí)際用處的。

不過(guò),對(duì)于 Hibernate 來(lái)說(shuō),情況就有點(diǎn)復(fù)雜了。因?yàn)?Hibernate 的事務(wù)管理?yè)碛衅渥陨淼囊饬x,它和 Hibernate 一級(jí)緩存有密切的關(guān)系:當(dāng)我們調(diào)用 Session 的 save、update 等方法時(shí),Hibernate 并不直接向數(shù)據(jù)庫(kù)發(fā)送 SQL 語(yǔ)句,而是在提交事務(wù)(commit)或 flush 一級(jí)緩存時(shí)才真正向數(shù)據(jù)庫(kù)發(fā)送 SQL。所以,即使底層數(shù)據(jù)庫(kù)不支持事務(wù),Hibernate 的事務(wù)管理也是有一定好處的,不會(huì)對(duì)數(shù)據(jù)操作的效率造成負(fù)面影響。所以,如果是使用 Hibernate 數(shù)據(jù)訪(fǎng)問(wèn)技術(shù),沒(méi)有理由不配置 HibernateTransactionManager 事務(wù)管理器。

但是,不使用 Hibernate 事務(wù)管理器,在 Spring 中,Hibernate 照樣也可以工作,來(lái)看下面的例子:


清單 3.UserHibernateWithoutTransManagerService.java
            package user.withouttm;            import org.springframework.beans.factory.annotation.Autowired;            import org.springframework.jdbc.core.JdbcTemplate;            import org.springframework.stereotype.Service;            import org.springframework.context.ApplicationContext;            import org.springframework.context.support.ClassPathXmlApplicationContext;            import org.springframework.orm.hibernate3.HibernateTemplate;            import org.apache.commons.dbcp.BasicDataSource;            import user.User;            @Service("service2")            public class UserHibernateWithoutTransManagerService {            @Autowired            private HibernateTemplate hibernateTemplate;            public void addScore(String userName,int toAdd){            User user = (User)hibernateTemplate.get(User.class,userName);            user.setScore(user.getScore()+toAdd);            hibernateTemplate.update(user);            }            public static void main(String[] args) {            //參考UserJdbcWithoutTransManagerService相應(yīng)代碼            …            }            }            

此時(shí),采用 hiberWithoutTransManager.xml 的配置文件,其配置內(nèi)容如下:


清單 4.hiberWithoutTransManager.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:p="http://www.springframework.org/schema/p"            xsi:schemaLocation="http://www.springframework.org/schema/beans            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd            http://www.springframework.org/schema/context            http://www.springframework.org/schema/context/spring-context-3.0.xsd">            <!--省略掉包掃描,數(shù)據(jù)源,JdbcTemplate配置部分,參見(jiàn)jdbcWithoutTransManager.xml -->            …            <bean id="sessionFactory"            class=            "org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"            p:dataSource-ref="dataSource">            <property name="annotatedClasses">            <list>            <value>user.User</value>            </list>            </property>            <property name="hibernateProperties">            <props>            <prop key="hibernate.dialect">            org.hibernate.dialect.Oracle10gDialect            </prop>            <prop key="hibernate.show_sql">true</prop>            </props>            </property>            </bean>            <bean id="hibernateTemplate"            class="org.springframework.orm.hibernate3.HibernateTemplate"            p:sessionFactory-ref="sessionFactory"/>            </beans>            

運(yùn)行 UserHibernateWithoutTransManagerService,程序正確執(zhí)行,并得到類(lèi)似于 UserJdbcWithoutTransManagerService 的執(zhí)行結(jié)果,這說(shuō)明 Hibernate 在 Spring 中,在沒(méi)有事務(wù)管理器的情況下,依然可以正常地進(jìn)行數(shù)據(jù)的訪(fǎng)問(wèn)。


應(yīng)用分層的迷惑

Web、Service 及 DAO 三層劃分就像西方國(guó)家的立法、行政、司法三權(quán)分立一樣被奉為金科玉律,甚至有開(kāi)發(fā)人員認(rèn)為如果要使用 Spring 的事務(wù)管理就一定先要進(jìn)行三層的劃分。這個(gè)看似荒唐的論調(diào)在開(kāi)發(fā)人員中頗有市場(chǎng)。更有甚者,認(rèn)為每層必須先定義一個(gè)接口,然后再定義一個(gè)實(shí)現(xiàn)類(lèi)。其結(jié)果是:一個(gè)很簡(jiǎn)單的功能,也至少需要 3 個(gè)接口,3 個(gè)類(lèi),再加上視圖層的 JSP 和 JS 等,打牌都可以轉(zhuǎn)上兩桌了,這種誤解貽害不淺。

對(duì)將“面向接口編程”奉為圭臬,認(rèn)為放之四海而皆準(zhǔn)的論調(diào),筆者深不以為然。是的,“面向接口編程”是 Martin Fowler,Rod Johnson 這些大師提倡的行事原則。如果拿這條原則去開(kāi)發(fā)架構(gòu),開(kāi)發(fā)產(chǎn)品,怎么強(qiáng)調(diào)都不為過(guò)。但是,對(duì)于我們一般的開(kāi)發(fā)人員來(lái)說(shuō),做的最多的是普通工程項(xiàng)目,往往最多的只是一些對(duì)數(shù)據(jù)庫(kù)增、刪、查、改的功能。此時(shí),“面向接口編程”除了帶來(lái)更多的類(lèi)文件外,看不到更多其它的好處。

Spring 框架提供的所有附加的好處(AOP、注解增強(qiáng)、注解 MVC 等)唯一的前提就是讓 POJO 的類(lèi)變成一個(gè)受 Spring 容器管理的 Bean,除此以外沒(méi)有其它任何的要求。下面的實(shí)例用一個(gè) POJO 完成所有的功能,既是 Controller,又是 Service,還是 DAO:


清單 5. MixLayerUserService.java
            package user.mixlayer;            import org.springframework.beans.factory.annotation.Autowired;            import org.springframework.jdbc.core.JdbcTemplate;            import org.springframework.stereotype.Controller;            import org.springframework.web.bind.annotation.RequestMapping;            //①.將POJO類(lèi)通過(guò)注解變成Spring MVC的Controller            @Controller            public class MixLayerUserService {            //②.自動(dòng)注入JdbcTemplate            @Autowired            private JdbcTemplate jdbcTemplate;            //③.通過(guò)Spring MVC注解映URL請(qǐng)求            @RequestMapping("/logon.do")            public String logon(String userName,String password){            if(isRightUser(userName,password)){            String sql = "UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?";            jdbcTemplate.update(sql,20,userName);            return "success";            }else{            return "fail";            }            }            private boolean isRightUser(String userName,String password){            //do sth...            return true;            }            }            

通過(guò) @Controller 注解將 MixLayerUserService 變成 Web 層的 Controller,同時(shí)也是 Service 層的服務(wù)類(lèi)。此外,由于直接使用 JdbcTemplate 訪(fǎng)問(wèn)數(shù)據(jù),所以 MixLayerUserService 還是一個(gè) DAO。來(lái)看一下對(duì)應(yīng)的 Spring 配置文件:


清單 6.applicationContext.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:p="http://www.springframework.org/schema/p"            xmlns:aop="http://www.springframework.org/schema/aop"            xmlns:tx="http://www.springframework.org/schema/tx"            xsi:schemaLocation="http://www.springframework.org/schema/beans            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd            http://www.springframework.org/schema/context            http://www.springframework.org/schema/context/spring-context-3.0.xsd            http://www.springframework.org/schema/aop            http://www.springframework.org/schema/aop/spring-aop-3.0.xsd            http://www.springframework.org/schema/tx            http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">            <!--掃描Web類(lèi)包,通過(guò)注釋生成Bean-->            <context:component-scan base-package="user.mixlayer"/>            <!--①.啟動(dòng)Spring MVC的注解功能,完成請(qǐng)求和注解POJO的映射-->            <bean class="org.springframework.web.servlet.mvc.annotation            .AnnotationMethodHandlerAdapter"/>            <!--模型視圖名稱(chēng)的解析,即在模型視圖名稱(chēng)添加前后綴 -->            <bean class="org.springframework.web.servlet.view            .InternalResourceViewResolver"            p:prefix="/WEB-INF/jsp/" p:suffix=".jsp"/>            <!--普通數(shù)據(jù)源 -->            <bean id="dataSource"            class="org.apache.commons.dbcp.BasicDataSource"            destroy-method="close"            p:driverClassName="oracle.jdbc.driver.OracleDriver"            p:url="jdbc:oracle:thin:@localhost:1521:orcl"            p:username="test"            p:password="test"/>            <bean id="jdbcTemplate"            class="org.springframework.jdbc.core.JdbcTemplate"            p:dataSource-ref="dataSource"/>            <!--事務(wù)管理器 -->            <bean id="jdbcManager"            class="org.springframework.jdbc.datasource.DataSourceTransactionManager"            p:dataSource-ref="dataSource"/>            <!--②使用aop和tx命名空間語(yǔ)法為MixLayerUserService所有公用方法添加事務(wù)增強(qiáng) -->            <aop:config proxy-target-class="true">            <aop:pointcut id="serviceJdbcMethod"            expression="execution(public * user.mixlayer.MixLayerUserService.*(..))"/>            <aop:advisor pointcut-ref="serviceJdbcMethod"            advice-ref="jdbcAdvice" order="0"/>            </aop:config>            <tx:advice id="jdbcAdvice" transaction-manager="jdbcManager">            <tx:attributes>            <tx:method name="*"/>            </tx:attributes>            </tx:advice>            </beans>            

在 ① 處,我們定義配置了 AnnotationMethodHandlerAdapter,以便啟用 Spring MVC 的注解驅(qū)動(dòng)功能。而②和③處通過(guò) Spring 的 aop 及 tx 命名空間,以及 Aspject 的切點(diǎn)表達(dá)式語(yǔ)法進(jìn)行事務(wù)增強(qiáng)的定義,對(duì) MixLayerUserService 的所有公有方法進(jìn)行事務(wù)增強(qiáng)。要使程序能夠運(yùn)行起來(lái)還必須進(jìn)行 web.xml 的相關(guān)配置:


清單 7.web.xml
            <?xml version="1.0" encoding="GB2312"?>            <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"            xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee            http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">            <context-param>            <param-name>contextConfigLocation</param-name>            <param-value>classpath*:user/mixlayer/applicationContext.xml</param-value>            </context-param>            <context-param>            <param-name>log4jConfigLocation</param-name>            <param-value>/WEB-INF/classes/log4j.properties</param-value>            </context-param>            <listener>            <listener-class>            org.springframework.web.util.Log4jConfigListener            </listener-class>            </listener>            <listener>            <listener-class>            org.springframework.web.context.ContextLoaderListener            </listener-class>            </listener>            <servlet>            <servlet-name>user</servlet-name>            <servlet-class>            org.springframework.web.servlet.DispatcherServlet            </servlet-class>            <!--①通過(guò)contextConfigLocation參數(shù)指定Spring配置文件的位置 -->            <init-param>            <param-name>contextConfigLocation</param-name>            <param-value>classpath:user/mixlayer/applicationContext.xml</param-value>            </init-param>            <load-on-startup>1</load-on-startup>            </servlet>            <servlet-mapping>            <servlet-name>user</servlet-name>            <url-pattern>*.do</url-pattern>            </servlet-mapping>            </web-app>            

這個(gè)配置文件很簡(jiǎn)單,唯一需要注意的是 DispatcherServlet 的配置。默認(rèn)情況下 Spring MVC 根據(jù) Servlet 的名字查找 WEB-INF 下的 <servletName>-servlet.xml 作為 Spring MVC 的配置文件,在此,我們通過(guò) contextConfigLocation 參數(shù)顯式指定 Spring MVC 配置文件的確切位置。

將 org.springframework.jdbc 及 org.springframework.transaction 的日志級(jí)別設(shè)置為 DEBUG,啟動(dòng)項(xiàng)目,并訪(fǎng)問(wèn) http://localhost:8088/logon.do?userName=tom 應(yīng)用,MixLayerUserService#logon 方法將作出響應(yīng),查看后臺(tái)輸出日志:


清單 8 執(zhí)行日志
            13:24:22,625 DEBUG (AbstractPlatformTransactionManager.java:365) -            Creating new transaction with name            [user.mixlayer.MixLayerUserService.logon]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT            13:24:22,906 DEBUG (DataSourceTransactionManager.java:205) -            Acquired Connection [org.apache.commons.dbcp.PoolableConnection@6e1cbf]            for JDBC transaction            13:24:22,921 DEBUG (DataSourceTransactionManager.java:222) -            Switching JDBC Connection            [org.apache.commons.dbcp.PoolableConnection@6e1cbf] to manual commit            13:24:22,921 DEBUG (JdbcTemplate.java:785) -            Executing prepared SQL update            13:24:22,921 DEBUG (JdbcTemplate.java:569) -            Executing prepared SQL statement            [UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?]            13:24:23,140 DEBUG (JdbcTemplate.java:794) -            SQL update affected 0 rows            13:24:23,140 DEBUG (AbstractPlatformTransactionManager.java:752) -            Initiating transaction commit            13:24:23,140 DEBUG (DataSourceTransactionManager.java:265) -            Committing JDBC transaction on Connection            [org.apache.commons.dbcp.PoolableConnection@6e1cbf]            13:24:23,140 DEBUG (DataSourceTransactionManager.java:323) -            Releasing JDBC Connection [org.apache.commons.dbcp.PoolableConnection@6e1cbf]            after transaction            13:24:23,156 DEBUG (DataSourceUtils.java:312) -            Returning JDBC Connection to DataSource            

日志中粗體部分說(shuō)明了 MixLayerUserService#logon 方法已經(jīng)正確運(yùn)行在事務(wù)上下文中。

Spring 框架本身不應(yīng)該是復(fù)雜化代碼的理由,使用 Spring 的開(kāi)發(fā)者應(yīng)該是無(wú)拘無(wú)束的:從實(shí)際應(yīng)用出發(fā),去除掉那些所謂原則性的接口,去除掉強(qiáng)制分層的束縛,簡(jiǎn)單才是硬道理。


事務(wù)方法嵌套調(diào)用的迷茫

Spring 事務(wù)一個(gè)被訛傳很廣說(shuō)法是:一個(gè)事務(wù)方法不應(yīng)該調(diào)用另一個(gè)事務(wù)方法,否則將產(chǎn)生兩個(gè)事務(wù)。結(jié)果造成開(kāi)發(fā)人員在設(shè)計(jì)事務(wù)方法時(shí)束手束腳,生怕一不小心就踩到地雷。

其實(shí)這種是不認(rèn)識(shí) Spring 事務(wù)傳播機(jī)制而造成的誤解,Spring 對(duì)事務(wù)控制的支持統(tǒng)一在 TransactionDefinition 類(lèi)中描述,該類(lèi)有以下幾個(gè)重要的接口方法:

  • int getPropagationBehavior():事務(wù)的傳播行為
  • int getIsolationLevel():事務(wù)的隔離級(jí)別
  • int getTimeout():事務(wù)的過(guò)期時(shí)間
  • boolean isReadOnly():事務(wù)的讀寫(xiě)特性。

很明顯,除了事務(wù)的傳播行為外,事務(wù)的其它特性 Spring 是借助底層資源的功能來(lái)完成的,Spring 無(wú)非只充當(dāng)個(gè)代理的角色。但是事務(wù)的傳播行為卻是 Spring 憑借自身的框架提供的功能,是 Spring 提供給開(kāi)發(fā)者最珍貴的禮物,訛傳的說(shuō)法玷污了 Spring 事務(wù)框架最美麗的光環(huán)。

所謂事務(wù)傳播行為就是多個(gè)事務(wù)方法相互調(diào)用時(shí),事務(wù)如何在這些方法間傳播。Spring 支持 7 種事務(wù)傳播行為:

  • PROPAGATION_REQUIRED 如果當(dāng)前沒(méi)有事務(wù),就新建一個(gè)事務(wù),如果已經(jīng)存在一個(gè)事務(wù)中,加入到這個(gè)事務(wù)中。這是最常見(jiàn)的選擇。
  • PROPAGATION_SUPPORTS 支持當(dāng)前事務(wù),如果當(dāng)前沒(méi)有事務(wù),就以非事務(wù)方式執(zhí)行。
  • PROPAGATION_MANDATORY 使用當(dāng)前的事務(wù),如果當(dāng)前沒(méi)有事務(wù),就拋出異常。
  • PROPAGATION_REQUIRES_NEW 新建事務(wù),如果當(dāng)前存在事務(wù),把當(dāng)前事務(wù)掛起。
  • PROPAGATION_NOT_SUPPORTED 以非事務(wù)方式執(zhí)行操作,如果當(dāng)前存在事務(wù),就把當(dāng)前事務(wù)掛起。
  • PROPAGATION_NEVER 以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),則拋出異常。
  • PROPAGATION_NESTED 如果當(dāng)前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行。如果當(dāng)前沒(méi)有事務(wù),則執(zhí)行與 PROPAGATION_REQUIRED 類(lèi)似的操作。

Spring 默認(rèn)的事務(wù)傳播行為是 PROPAGATION_REQUIRED,它適合于絕大多數(shù)的情況。假設(shè) ServiveX#methodX() 都工作在事務(wù)環(huán)境下(即都被 Spring 事務(wù)增強(qiáng)了),假設(shè)程序中存在如下的調(diào)用鏈:Service1#method1()->Service2#method2()->Service3#method3(),那么這 3 個(gè)服務(wù)類(lèi)的 3 個(gè)方法通過(guò) Spring 的事務(wù)傳播機(jī)制都工作在同一個(gè)事務(wù)中。

下面,我們來(lái)看一下實(shí)例,UserService#logon() 方法內(nèi)部調(diào)用了 UserService#updateLastLogonTime() 和 ScoreService#addScore() 方法,這兩個(gè)類(lèi)都繼承于 BaseService。它們之間的類(lèi)結(jié)構(gòu)說(shuō)明如下:


圖 1. UserService 和 ScoreService

具體的代碼如下所示:


清單 9 UserService.java
            @Service("userService")            public class UserService extends BaseService {            @Autowired            private JdbcTemplate jdbcTemplate;            @Autowired            private ScoreService scoreService;            public void logon(String userName) {            updateLastLogonTime(userName);            scoreService.addScore(userName, 20);            }            public void updateLastLogonTime(String userName) {            String sql = "UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?";            jdbcTemplate.update(sql, System.currentTimeMillis(), userName);            }            }            

UserService 中注入了 ScoreService 的 Bean,ScoreService 的代碼如下所示:


清單 10 ScoreService.java
            @Service("scoreUserService")            public class ScoreService extends BaseService{            @Autowired            private JdbcTemplate jdbcTemplate;            public void addScore(String userName, int toAdd) {            String sql = "UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?";            jdbcTemplate.update(sql, toAdd, userName);            }            }            

通過(guò) Spring 的事務(wù)配置為 ScoreService 及 UserService 中所有公有方法都添加事務(wù)增強(qiáng),讓這些方法都工作于事務(wù)環(huán)境下。下面是關(guān)鍵的配置代碼:


清單 11 事務(wù)增強(qiáng)配置
            <!-- 添加Spring事務(wù)增強(qiáng) -->            <aop:config proxy-target-class="true">            <aop:pointcut id="serviceJdbcMethod"            <!-- 所有繼承于BaseService類(lèi)的子孫類(lèi)的public方法都進(jìn)行事務(wù)增強(qiáng)-->            expression="within(user.nestcall.BaseService+)"/>            <aop:advisor pointcut-ref="serviceJdbcMethod"            advice-ref="jdbcAdvice" order="0"/>            </aop:config>            <tx:advice id="jdbcAdvice" transaction-manager="jdbcManager">            <tx:attributes>            <tx:method name="*"/>            </tx:attributes>            </tx:advice>            

將日志級(jí)別設(shè)置為 DEBUG,啟動(dòng) Spring 容器并執(zhí)行 UserService#logon() 的方法,仔細(xì)觀察如下的輸出日志:


清單 12 執(zhí)行日志
            16:25:04,765 DEBUG (AbstractPlatformTransactionManager.java:365) -            Creating new transaction with name [user.nestcall.UserService.logon]:            PROPAGATION_REQUIRED,ISOLATION_DEFAULT  ①為UserService#logon方法啟動(dòng)一個(gè)事務(wù)            16:25:04,765 DEBUG (DataSourceTransactionManager.java:205) -            Acquired Connection [org.apache.commons.dbcp.PoolableConnection@32bd65]            for JDBC transaction            logon method...            updateLastLogonTime... ②直接執(zhí)行updateLastLogonTime方法            16:25:04,781 DEBUG (JdbcTemplate.java:785) - Executing prepared SQL update            16:25:04,781 DEBUG (JdbcTemplate.java:569) - Executing prepared SQL statement            [UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?]            16:25:04,828 DEBUG (JdbcTemplate.java:794) - SQL update affected 0 rows            16:25:04,828 DEBUG (AbstractPlatformTransactionManager.java:470) - Participating            in existing transaction   ③ScoreService#addScore方法加入到UserService#logon的事務(wù)中            addScore...            16:25:04,828 DEBUG (JdbcTemplate.java:785) - Executing prepared SQL update            16:25:04,828 DEBUG (JdbcTemplate.java:569) - Executing prepared SQL statement            [UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?]            16:25:04,828 DEBUG (JdbcTemplate.java:794) - SQL update affected 0 rows            16:25:04,828 DEBUG (AbstractPlatformTransactionManager.java:752) -            Initiating transaction commit            ④提交事務(wù)            16:25:04,828 DEBUG (DataSourceTransactionManager.java:265) - Committing JDBC transaction            on Connection [org.apache.commons.dbcp.PoolableConnection@32bd65]            16:25:04,828 DEBUG (DataSourceTransactionManager.java:323) - Releasing JDBC Connection            [org.apache.commons.dbcp.PoolableConnection@32bd65] after transaction            16:25:04,828 DEBUG (DataSourceUtils.java:312) - Returning JDBC Connection to DataSource            

從上面的輸入日志中,可以清楚地看到 Spring 為 UserService#logon() 方法啟動(dòng)了一個(gè)新的事務(wù),而 UserSerive#updateLastLogonTime() 和 UserService#logon() 是在相同的類(lèi)中,沒(méi)有觀察到有事務(wù)傳播行為的發(fā)生,其代碼塊好像“直接合并”到 UserService#logon() 中。接著,當(dāng)執(zhí)行到 ScoreService#addScore() 方法時(shí),我們就觀察到了發(fā)生了事務(wù)傳播的行為:Participating in existing transaction,這說(shuō)明 ScoreService#addScore() 添加到 UserService#logon() 的事務(wù)上下文中,兩者共享同一個(gè)事務(wù)。所以最終的結(jié)果是 UserService 的 logon(), updateLastLogonTime() 以及 ScoreService 的 addScore 都工作于同一事務(wù)中。


多線(xiàn)程的困惑

由于 Spring 的事務(wù)管理器是通過(guò)線(xiàn)程相關(guān)的 ThreadLocal 來(lái)保存數(shù)據(jù)訪(fǎng)問(wèn)基礎(chǔ)設(shè)施,再結(jié)合 IOC 和 AOP 實(shí)現(xiàn)高級(jí)聲明式事務(wù)的功能,所以 Spring 的事務(wù)天然地和線(xiàn)程有著千絲萬(wàn)縷的聯(lián)系。

我們知道 Web 容器本身就是多線(xiàn)程的,Web 容器為一個(gè) Http 請(qǐng)求創(chuàng)建一個(gè)獨(dú)立的線(xiàn)程,所以由此請(qǐng)求所牽涉到的 Spring 容器中的 Bean 也是運(yùn)行于多線(xiàn)程的環(huán)境下。在絕大多數(shù)情況下,Spring 的 Bean 都是單實(shí)例的(singleton),單實(shí)例 Bean 的最大的好處是線(xiàn)程無(wú)關(guān)性,不存在多線(xiàn)程并發(fā)訪(fǎng)問(wèn)的問(wèn)題,也即是線(xiàn)程安全的。

一個(gè)類(lèi)能夠以單實(shí)例的方式運(yùn)行的前提是“無(wú)狀態(tài)”:即一個(gè)類(lèi)不能擁有狀態(tài)化的成員變量。我們知道,在傳統(tǒng)的編程中,DAO 必須執(zhí)有一個(gè) Connection,而 Connection 即是狀態(tài)化的對(duì)象。所以傳統(tǒng)的 DAO 不能做成單實(shí)例的,每次要用時(shí)都必須 new 一個(gè)新的實(shí)例。傳統(tǒng)的 Service 由于將有狀態(tài)的 DAO 作為成員變量,所以傳統(tǒng)的 Service 本身也是有狀態(tài)的。

但是在 Spring 中,DAO 和 Service 都以單實(shí)例的方式存在。Spring 是通過(guò) ThreadLocal 將有狀態(tài)的變量(如 Connection 等)本地線(xiàn)程化,達(dá)到另一個(gè)層面上的“線(xiàn)程無(wú)關(guān)”,從而實(shí)現(xiàn)線(xiàn)程安全。Spring 不遺余力地將狀態(tài)化的對(duì)象無(wú)狀態(tài)化,就是要達(dá)到單實(shí)例化 Bean 的目的。

由于 Spring 已經(jīng)通過(guò) ThreadLocal 的設(shè)施將 Bean 無(wú)狀態(tài)化,所以 Spring 中單實(shí)例 Bean 對(duì)線(xiàn)程安全問(wèn)題擁有了一種天生的免疫能力。不但單實(shí)例的 Service 可以成功運(yùn)行于多線(xiàn)程環(huán)境中,Service 本身還可以自由地啟動(dòng)獨(dú)立線(xiàn)程以執(zhí)行其它的 Service。下面,通過(guò)一個(gè)實(shí)例對(duì)此進(jìn)行描述:


清單 13 UserService.java 在事務(wù)方法中啟動(dòng)獨(dú)立線(xiàn)程運(yùn)行另一個(gè)事務(wù)方法
            @Service("userService")            public class UserService extends BaseService {            @Autowired            private JdbcTemplate jdbcTemplate;            @Autowired            private ScoreService scoreService;            //① 在logon方法體中啟動(dòng)一個(gè)獨(dú)立的線(xiàn)程,在該獨(dú)立的線(xiàn)程中執(zhí)行ScoreService#addScore()方法            public void logon(String userName) {            System.out.println("logon method...");            updateLastLogonTime(userName);            Thread myThread = new MyThread(this.scoreService,userName,20);            myThread.start();            }            public void updateLastLogonTime(String userName) {            System.out.println("updateLastLogonTime...");            String sql = "UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?";            jdbcTemplate.update(sql, System.currentTimeMillis(), userName);            }            //② 封裝ScoreService#addScore()的線(xiàn)程            private class MyThread extends Thread{            private ScoreService scoreService;            private String userName;            private int toAdd;            private MyThread(ScoreService scoreService,String userName,int toAdd) {            this.scoreService = scoreService;            this.userName = userName;            this.toAdd = toAdd;            }            public void run() {            scoreService.addScore(userName,toAdd);            }            }            }            

將日志級(jí)別設(shè)置為 DEBUG,執(zhí)行 UserService#logon() 方法,觀察以下輸出的日志:


清單 14 執(zhí)行日志
            [main] (AbstractPlatformTransactionManager.java:365) - Creating new transaction with name            [user.multithread.UserService.logon]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT ①            [main] (DataSourceTransactionManager.java:205) - Acquired Connection            [org.apache.commons.dbcp.PoolableConnection@1353249] for JDBC transaction            logon method...            updateLastLogonTime...            [main] (JdbcTemplate.java:785) - Executing prepared SQL update            [main] (JdbcTemplate.java:569) - Executing prepared SQL statement            [UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?]            [main] (JdbcTemplate.java:794) - SQL update affected 0 rows            [main] (AbstractPlatformTransactionManager.java:752) - Initiating transaction commit            [Thread-2](AbstractPlatformTransactionManager.java:365) -            Creating new transaction with name [user.multithread.ScoreService.addScore]:            PROPAGATION_REQUIRED,ISOLATION_DEFAULT ②            [main] (DataSourceTransactionManager.java:265) - Committing JDBC transaction            on Connection [org.apache.commons.dbcp.PoolableConnection@1353249] ③            [main] (DataSourceTransactionManager.java:323) - Releasing JDBC Connection            [org.apache.commons.dbcp.PoolableConnection@1353249] after transaction            [main] (DataSourceUtils.java:312) - Returning JDBC Connection to DataSource            [Thread-2] (DataSourceTransactionManager.java:205) - Acquired Connection            [org.apache.commons.dbcp.PoolableConnection@10dc656] for JDBC transaction            addScore...            [main] (JdbcTemplate.java:416) - Executing SQL statement            [DELETE FROM t_user WHERE user_name='tom']            [main] (DataSourceUtils.java:112) - Fetching JDBC Connection from DataSource            [Thread-2] (JdbcTemplate.java:785) - Executing prepared SQL update            [Thread-2] (JdbcTemplate.java:569) - Executing prepared SQL statement            [UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?]            [main] (DataSourceUtils.java:312) - Returning JDBC Connection to DataSource            [Thread-2] (JdbcTemplate.java:794) - SQL update affected 0 rows            [Thread-2] (AbstractPlatformTransactionManager.java:752) - Initiating transaction commit            [Thread-2] (DataSourceTransactionManager.java:265) - Committing JDBC transaction            on Connection [org.apache.commons.dbcp.PoolableConnection@10dc656] ④            [Thread-2] (DataSourceTransactionManager.java:323) - Releasing JDBC Connection            [org.apache.commons.dbcp.PoolableConnection@10dc656] after transaction            

在 ① 處,在主線(xiàn)程(main)執(zhí)行的 UserService#logon() 方法的事務(wù)啟動(dòng),在 ③ 處,其對(duì)應(yīng)的事務(wù)提交,而在子線(xiàn)程(Thread-2)執(zhí)行的 ScoreService#addScore() 方法的事務(wù)在 ② 處啟動(dòng),在 ④ 處對(duì)應(yīng)的事務(wù)提交。

所以,我們可以得出這樣的結(jié)論:在 相同線(xiàn)程中進(jìn)行相互嵌套調(diào)用的事務(wù)方法工作于相同的事務(wù)中。如果這些相互嵌套調(diào)用的方法工作在不同的線(xiàn)程中,不同線(xiàn)程下的事務(wù)方法工作在獨(dú)立的事務(wù)中。


小結(jié)

Spring 聲明式事務(wù)是 Spring 最核心,最常用的功能。由于 Spring 通過(guò) IOC 和 AOP 的功能非常透明地實(shí)現(xiàn)了聲明式事務(wù)的功能,一般的開(kāi)發(fā)者基本上無(wú)須了解 Spring 聲明式事務(wù)的內(nèi)部細(xì)節(jié),僅需要懂得如何配置就可以了。

但是在實(shí)際應(yīng)用開(kāi)發(fā)過(guò)程中,Spring 的這種透明的高階封裝在帶來(lái)便利的同時(shí),也給我們帶來(lái)了迷惑。就像通過(guò)流言傳播的消息,最終聽(tīng)眾已經(jīng)不清楚事情的真相了,而這對(duì)于應(yīng)用開(kāi)發(fā)來(lái)說(shuō)是很危險(xiǎn)的。本系列文章通過(guò)剖析實(shí)際應(yīng)用中給開(kāi)發(fā)者造成迷惑的各種難點(diǎn),通過(guò)分析 Spring 事務(wù)管理的內(nèi)部運(yùn)作機(jī)制將真相還原出來(lái)。

在本文中,我們通過(guò)剖析了解到以下的真相:

  • 在沒(méi)有事務(wù)管理的情況下,DAO 照樣可以順利進(jìn)行數(shù)據(jù)操作;
  • 將應(yīng)用分成 Web,Service 及 DAO 層只是一種參考的開(kāi)發(fā)模式,并非是事務(wù)管理工作的前提條件;
  • Spring 通過(guò)事務(wù)傳播機(jī)制可以很好地應(yīng)對(duì)事務(wù)方法嵌套調(diào)用的情況,開(kāi)發(fā)者無(wú)須為了事務(wù)管理而刻意改變服務(wù)方法的設(shè)計(jì);
  • 由于單實(shí)例的對(duì)象不存在線(xiàn)程安全問(wèn)題,所以進(jìn)行事務(wù)管理增強(qiáng)的 Bean 可以很好地工作在多線(xiàn)程環(huán)境下。

下一篇 文章中,筆者將繼續(xù)分析 Spring 事務(wù)管理的以下難點(diǎn):

  • 混合使用多種數(shù)據(jù)訪(fǎng)問(wèn)技術(shù)(如 Spring JDBC + Hibernate)的事務(wù)管理問(wèn)題;
  • 進(jìn)行 Spring AOP 增強(qiáng)的 Bean 存在哪些特殊的情況。

參考資料

學(xué)習(xí)

討論

關(guān)于作者

陳雄華,2002 年畢業(yè)于廈門(mén)大學(xué)計(jì)算機(jī)與信息工程學(xué)院,獲碩士學(xué)位。擁有 10 多年的 Java 開(kāi)發(fā)、設(shè)計(jì)、架構(gòu)的經(jīng)驗(yàn)。技術(shù)研發(fā)之余,常將經(jīng)驗(yàn)所得行諸于文字,作者是國(guó)內(nèi)多個(gè)著名技術(shù)網(wǎng)站的專(zhuān)欄作者,在各大技術(shù)網(wǎng)站、報(bào)刊雜志發(fā)表過(guò)數(shù)十篇技術(shù)文章,廣受讀者好評(píng)。于 2005 年出版《精通 JBuilder 2005》,于 2007 年出版《精通 Spring 2.x -- 企業(yè)應(yīng)用開(kāi)發(fā)詳解》。

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶(hù)發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
Spring JDBC
Spring之事務(wù)管理TranscationManager(大合集)
手把手帶你實(shí)現(xiàn)事務(wù)消息
Spring邊學(xué)習(xí)邊總結(jié)
spring3之JdbcTemplate詳解
Spring HTTP Invoker例子
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服