精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Spring事務管理高級應用難點剖析

開發 后端
Spring的事務管理是被使用得最多的功能之一,雖然Spring事務管理已經幫助程序員將要做的事情減到了最小。但在實際開發中,如果使用不當,依然會造成數據連接泄漏等問題。

Spring最成功,最吸引人的地方莫過于輕量級的聲明式事務管理,僅此一點,它就宣告了重量級EJB容器的覆滅。Spring聲明式事務管理將開發者從繁復的事務管理代碼中解脫出來,專注于業務邏輯的開發上,這是一件可以被拿來頂禮膜拜的事情。

但是,世界并未從此消停,開發人員需要面對的是層出不窮的應用場景,這些場景往往逾越了普通Spring技術書籍的理想界定。因此,隨著應用開發的深入,在使用經過Spring層層封裝的聲明式事務時,開發人員越來越覺得自己墜入了迷霧,陷入了沼澤,體會不到外界所宣稱的那種暢快淋漓。本系列文章的目標旨在整理并剖析實際應用中種種讓我們迷茫的場景,讓陽光照進云遮霧障的山頭。

很少有使用Spring但不使用Spring事務管理器的應用,因此常常有人會問:是否用了Spring,就一定要用Spring事務管理器,否則就無法進行數據的持久化操作呢?事務管理器和DAO是什么關系呢?

也許是DAO和事務管理如影隨行的緣故吧,這個看似簡單的問題實實在在地存在著,從初學者心中涌出,縈繞在開發老手的腦際。答案當然是否定的!我們都知道:Spring事務管理是保證數據操作的事務性(即原子性、一致性、隔離性、持久性,也即所謂的ACID),脫離了事務性,DAO照樣可以順利地進行數據的操作。下面,我們來看一段使用SpringJDBC進行數據訪問的代碼:

清單1.UserJdbcWithoutTransManagerService.java

  1. packageuser.withouttm;  
  2.  
  3. importorg.springframework.beans.factory.annotation.Autowired;  
  4. importorg.springframework.jdbc.core.JdbcTemplate;  
  5. importorg.springframework.stereotype.Service;  
  6. importorg.springframework.context.ApplicationContext;  
  7. importorg.springframework.context.support.ClassPathXmlApplicationContext;  
  8. importorg.apache.commons.dbcp.BasicDataSource;  
  9.  
  10. @Service("service1")  
  11. publicclassUserJdbcWithoutTransManagerService{  
  12. @Autowired  
  13. privateJdbcTemplatejdbcTemplate;  
  14.  
  15. publicvoidaddScore(StringuserName,inttoAdd){  
  16. Stringsql="UPDATEt_useruSETu.score=u.score+?WHEREuser_name=?";  
  17. jdbcTemplate.update(sql,toAdd,userName);  
  18. }  
  19.  
  20. publicstaticvoidmain(String[]args){  
  21. ApplicationContextctx=  
  22. newClassPathXmlApplicationContext("user/withouttm/jdbcWithoutTransManager.xml");  
  23. UserJdbcWithoutTransManagerServiceservice=  
  24. (UserJdbcWithoutTransManagerService)ctx.getBean("service1");  
  25. JdbcTemplatejdbcTemplate=(JdbcTemplate)ctx.getBean("jdbcTemplate");  
  26. BasicDataSourcebasicDataSource=(BasicDataSource)jdbcTemplate.getDataSource();  
  27.  
  28. //①.檢查數據源autoCommit的設置  
  29. System.out.println("autoCommit:"+basicDataSource.getDefaultAutoCommit());  
  30.  
  31. //②.插入一條記錄,初始分數為10  
  32. jdbcTemplate.execute(  
  33. "INSERTINTOt_user(user_name,password,score)VALUES('tom','123456',10)");  
  34.  
  35. //③.調用工作在無事務環境下的服務類方法,將分數添加20分  
  36. service.addScore("tom",20);  
  37.  
  38. //④.查看此時用戶的分數  
  39. intscore=jdbcTemplate.queryForInt(  
  40. "SELECTscoreFROMt_userWHEREuser_name='tom'");  
  41. System.out.println("score:"+score);  
  42. jdbcTemplate.execute("DELETEFROMt_userWHEREuser_name='tom'");  
  43. }  

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

清單2.jdbcWithoutTransManager.xml

  1. xmlversionxmlversion="1.0"encoding="UTF-8"?> 
  2. <beansxmlnsbeansxmlns="http://www.springframework.org/schema/beans" 
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  4. xmlns:context="http://www.springframework.org/schema/context" 
  5. xmlns:p="http://www.springframework.org/schema/p" 
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans  
  7. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
  8. http://www.springframework.org/schema/context  
  9. http://www.springframework.org/schema/context/spring-context-3.0.xsd"> 
  10. <context:component-scanbase-packagecontext:component-scanbase-package="user.withouttm"/> 
  11.  
  12. <beanidbeanid="dataSource" 
  13. class="org.apache.commons.dbcp.BasicDataSource" 
  14. destroy-method="close" 
  15. p:driverClassName="oracle.jdbc.driver.OracleDriver" 
  16. p:url="jdbc:oracle:thin:@localhost:1521:orcl" 
  17. p:username="test" 
  18. p:password="test"/> 
  19. <beanidbeanid="jdbcTemplate" 
  20. class="org.springframework.jdbc.core.JdbcTemplate" 
  21. p:dataSource-ref="dataSource"/> 
  22. beans> 

運行UserJdbcWithoutTransManagerService,在控制臺上打出如下的結果:

  1. defaultAutoCommit:true  
  2. score:30 

在jdbcWithoutTransManager.xml中,沒有配置任何事務管理器,但是數據已經成功持久化到數據庫中。在默認情況下,dataSource數據源的autoCommit被設置為true――這也意謂著所有通過JdbcTemplate執行的語句馬上提交,沒有事務。如果將dataSource的defaultAutoCommit設置為false,再次運行UserJdbcWithoutTransManagerService,將拋出錯誤,原因是新增及更改數據的操作都沒有提交到數據庫,所以④處的語句因無法從數據庫中查詢到匹配的記錄而引發異常。

對于強調讀速度的應用,數據庫本身可能就不支持事務,如使用MyISAM引擎的MySQL數據庫。這時,無須在Spring應用中配置事務管理器,因為即使配置了,也是沒有實際用處的。

不過,對于Hibernate來說,情況就有點復雜了。因為Hibernate的事務管理擁有其自身的意義,它和Hibernate一級緩存有密切的關系:當我們調用Session的save、update等方法時,Hibernate并不直接向數據庫發送SQL語句,而是在提交事務(commit)或flush一級緩存時才真正向數據庫發送SQL。所以,即使底層數據庫不支持事務,Hibernate的事務管理也是有一定好處的,不會對數據操作的效率造成負面影響。所以,如果是使用Hibernate數據訪問技術,沒有理由不配置HibernateTransactionManager事務管理器。但是,不使用Hibernate事務管理器,在Spring中,Hibernate照樣也可以工作,來看下面的例子: #p#

清單3.UserHibernateWithoutTransManagerService.java

  1. packageuser.withouttm;  
  2.  
  3. importorg.springframework.beans.factory.annotation.Autowired;  
  4. importorg.springframework.jdbc.core.JdbcTemplate;  
  5. importorg.springframework.stereotype.Service;  
  6. importorg.springframework.context.ApplicationContext;  
  7. importorg.springframework.context.support.ClassPathXmlApplicationContext;  
  8. importorg.springframework.orm.hibernate3.HibernateTemplate;  
  9. importorg.apache.commons.dbcp.BasicDataSource;  
  10. importuser.User;  
  11.  
  12. @Service("service2")  
  13. publicclassUserHibernateWithoutTransManagerService{  
  14. @Autowired  
  15. privateHibernateTemplatehibernateTemplate;  
  16.  
  17. publicvoidaddScore(StringuserName,inttoAdd){  
  18. Useruser=(User)hibernateTemplate.get(User.class,userName);  
  19. user.setScore(user.getScore()+toAdd);  
  20. hibernateTemplate.update(user);  
  21. }  
  22.  
  23. publicstaticvoidmain(String[]args){  
  24. //參考UserJdbcWithoutTransManagerService相應代碼  
  25. …  
  26. }  

此時,采用hiberWithoutTransManager.xml的配置文件,其配置內容如下:

清單4.hiberWithoutTransManager.xml

  1. xmlversionxmlversion="1.0"encoding="UTF-8"?> 
  2. <beansxmlnsbeansxmlns="http://www.springframework.org/schema/beans" 
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  4. xmlns:context="http://www.springframework.org/schema/context" 
  5. xmlns:p="http://www.springframework.org/schema/p" 
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans  
  7. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
  8. http://www.springframework.org/schema/context  
  9. http://www.springframework.org/schema/context/spring-context-3.0.xsd"> 
  10.  
  11. …  
  12. <beanidbeanid="sessionFactory" 
  13. class=  
  14. "org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" 
  15. p:dataSource-ref="dataSource"> 
  16. <propertynamepropertyname="annotatedClasses"> 
  17. <list> 
  18. <value>user.Uservalue> 
  19. list> 
  20. property> 
  21. <propertynamepropertyname="hibernateProperties"> 
  22. <props> 
  23. <propkeypropkey="hibernate.dialect"> 
  24. org.hibernate.dialect.Oracle10gDialect  
  25. prop> 
  26. <propkeypropkey="hibernate.show_sql">trueprop> 
  27. props> 
  28. property> 
  29. bean> 
  30.  
  31. <beanidbeanid="hibernateTemplate" 
  32. class="org.springframework.orm.hibernate3.HibernateTemplate" 
  33. p:sessionFactory-ref="sessionFactory"/> 
  34. beans> 

運行UserHibernateWithoutTransManagerService,程序正確執行,并得到類似于UserJdbcWithoutTransManagerService的執行結果,這說明Hibernate在Spring中,在沒有事務管理器的情況下,依然可以正常地進行數據的訪問。

應用分層的迷惑

Web、Service及DAO三層劃分就像西方國家的立法、行政、司法三權分立一樣被奉為金科玉律,甚至有開發人員認為如果要使用Spring事務管理就一定先要進行三層的劃分。這個看似荒唐的論調在開發人員中頗有市場。更有甚者,認為每層必須先定義一個接口,然后再定義一個實現類。其結果是:一個很簡單的功能,也至少需要3個接口,3個類,再加上視圖層的JSP和JS等,打牌都可以轉上兩桌了,這種誤解貽害不淺。

對將“面向接口編程”奉為圭臬,認為放之四海而皆準的論調,筆者深不以為然。是的,“面向接口編程”是MartinFowler,RodJohnson這些大師提倡的行事原則。如果拿這條原則去開發架構,開發產品,怎么強調都不為過。但是,對于我們一般的開發人員來說,做的最多的是普通工程項目,往往最多的只是一些對數據庫增、刪、查、改的功能。此時,“面向接口編程”除了帶來更多的類文件外,看不到更多其它的好處。

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

清單5.MixLayerUserService.java

  1. packageuser.mixlayer;  
  2. importorg.springframework.beans.factory.annotation.Autowired;  
  3. importorg.springframework.jdbc.core.JdbcTemplate;  
  4. importorg.springframework.stereotype.Controller;  
  5. importorg.springframework.web.bind.annotation.RequestMapping;  
  6. //①.將POJO類通過注解變成SpringMVC的Controller  
  7. @Controller  
  8. publicclassMixLayerUserService{  
  9.  
  10. //②.自動注入JdbcTemplate  
  11. @Autowired  
  12. privateJdbcTemplatejdbcTemplate;  
  13.  
  14. //③.通過SpringMVC注解映URL請求  
  15. @RequestMapping("/logon.do")  
  16. publicStringlogon(StringuserName,Stringpassword){  
  17. if(isRightUser(userName,password)){  
  18. Stringsql="UPDATEt_useruSETu.score=u.score+?WHEREuser_name=?";  
  19. jdbcTemplate.update(sql,20,userName);  
  20. return"success";  
  21. }else{  
  22. return"fail";  
  23. }  
  24. }  
  25. privatebooleanisRightUser(StringuserName,Stringpassword){  
  26. //dosth...  
  27. returntrue;  
  28. }  

通過@Controller注解將MixLayerUserService變成Web層的Controller,同時也是Service層的服務類。此外,由于直接使用JdbcTemplate訪問數據,所以MixLayerUserService還是一個DAO。來看一下對應的Spring配置文件:

清單6.applicationContext.xml

  1. xmlversionxmlversion="1.0"encoding="UTF-8"?> 
  2. <beansxmlnsbeansxmlns="http://www.springframework.org/schema/beans" 
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  4. xmlns:context="http://www.springframework.org/schema/context" 
  5. xmlns:p="http://www.springframework.org/schema/p" 
  6. xmlns:aop="http://www.springframework.org/schema/aop" 
  7. xmlns:tx="http://www.springframework.org/schema/tx" 
  8. xsi:schemaLocation="http://www.springframework.org/schema/beans  
  9. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
  10. http://www.springframework.org/schema/context  
  11.  http://www.springframework.org/schema/context/spring-context-3.0.xsd  
  12.  http://www.springframework.org/schema/aop  
  13.  http://www.springframework.org/schema/aop/spring-aop-3.0.xsd  
  14.  http://www.springframework.org/schema/tx  
  15. http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> 
  16.  
  17. <context:component-scanbase-packagecontext:component-scanbase-package="user.mixlayer"/> 
  18.  
  19. <beanclassbeanclass="org.springframework.web.servlet.mvc.annotation  
  20.  .AnnotationMethodHandlerAdapter"/> 
  21.  
  22.  
  23. <beanclassbeanclass="org.springframework.web.servlet.view  
  24.  .InternalResourceViewResolver"  
  25. pp:prefix="/WEB-INF/jsp/"p:suffix=".jsp"/> 
  26.  
  27.  
  28. <beanidbeanid="dataSource" 
  29. class="org.apache.commons.dbcp.BasicDataSource" 
  30. destroy-method="close" 
  31. p:driverClassName="oracle.jdbc.driver.OracleDriver" 
  32. p:url="jdbc:oracle:thin:@localhost:1521:orcl" 
  33. p:username="test" 
  34. p:password="test"/> 
  35.  
  36. <beanidbeanid="jdbcTemplate" 
  37. class="org.springframework.jdbc.core.JdbcTemplate" 
  38. p:dataSource-ref="dataSource"/> 
  39.  
  40.  
  41. <beanidbeanid="jdbcManager" 
  42. class="org.springframework.jdbc.datasource.DataSourceTransactionManager" 
  43. p:dataSource-ref="dataSource"/> 
  44.  
  45.  
  46. <aop:configproxy-target-classaop:configproxy-target-class="true"> 
  47. <aop:pointcutidaop:pointcutid="serviceJdbcMethod" 
  48. expression="execution(public*user.mixlayer.MixLayerUserService.*(..))"/> 
  49. <aop:advisorpointcut-refaop:advisorpointcut-ref="serviceJdbcMethod" 
  50. advice-ref="jdbcAdvice"order="0"/> 
  51. aop:config> 
  52. <tx:adviceidtx:adviceid="jdbcAdvice"transaction-manager="jdbcManager"> 
  53. <tx:attributes> 
  54. <tx:methodnametx:methodname="*"/> 
  55. tx:attributes> 
  56. tx:advice> 
  57. beans> 

在①處,我們定義配置了AnnotationMethodHandlerAdapter,以便啟用SpringMVC的注解驅動功能。而②和③處通過Spring的aop及tx命名空間,以及Aspject的切點表達式語法進行事務增強的定義,對MixLayerUserService的所有公有方法進行事務增強。要使程序能夠運行起來還必須進行web.xml的相關配置:#p#

清單7.web.xml

  1. xmlversionxmlversion="1.0"encoding="GB2312"?> 
  2. <web-appversionweb-appversion="2.4"xmlns="http://java.sun.com/xml/ns/j2ee"  
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  4. xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee  
  5. http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> 
  6. <context-param> 
  7. <param-name>contextConfigLocationparam-name> 
  8. <param-value>classpath*:user/mixlayer/applicationContext.xmlparam-value> 
  9. context-param> 
  10. <context-param> 
  11. <param-name>log4jConfigLocationparam-name> 
  12. <param-value>/WEB-INF/classes/log4j.propertiesparam-value> 
  13. context-param> 
  14.  
  15. <listener> 
  16. <listener-class> 
  17. org.springframework.web.util.Log4jConfigListener  
  18. listener-class> 
  19. listener> 
  20. <listener> 
  21. <listener-class> 
  22. org.springframework.web.context.ContextLoaderListener  
  23. listener-class> 
  24. listener> 
  25.  
  26. <servlet> 
  27. <servlet-name>userservlet-name> 
  28. <servlet-class> 
  29. org.springframework.web.servlet.DispatcherServlet  
  30. servlet-class> 
  31.  
  32. <init-param> 
  33. <param-name>contextConfigLocationparam-name> 
  34. <param-value>classpath:user/mixlayer/applicationContext.xmlparam-value> 
  35. init-param> 
  36. <load-on-startup>1load-on-startup> 
  37. servlet> 
  38. <servlet-mapping> 
  39. <servlet-name>userservlet-name> 
  40. <url-pattern>*.dourl-pattern> 
  41. servlet-mapping> 
  42. web-app> 

這個配置文件很簡單,唯一需要注意的是DispatcherServlet的配置。默認情況下SpringMVC根據Servlet的名字查找WEB-INF下的-servlet.xml作為SpringMVC的配置文件,在此,我們通過contextConfigLocation參數顯式指定SpringMVC配置文件的確切位置。

將org.springframework.jdbc及org.springframework.transaction的日志級別設置為DEBUG,啟動項目,并訪問http://localhost:8088/logon.do?userName=tom應用,MixLayerUserService#logon方法將作出響應,查看后臺輸出日志:

清單8執行日志

  1. 13:24:22,625DEBUG(AbstractPlatformTransactionManager.java:365)-  
  2. Creatingnewtransactionwithname  
  3.  [user.mixlayer.MixLayerUserService.logon]:PROPAGATION_REQUIRED,ISOLATION_DEFAULT  
  4. 13:24:22,906DEBUG(DataSourceTransactionManager.java:205)-  
  5. AcquiredConnection[org.apache.commons.dbcp.PoolableConnection@6e1cbf]  
  6.  forJDBCtransaction  
  7. 13:24:22,921DEBUG(DataSourceTransactionManager.java:222)-  
  8. SwitchingJDBCConnection  
  9.  [org.apache.commons.dbcp.PoolableConnection@6e1cbf]tomanualcommit  
  10. 13:24:22,921DEBUG(JdbcTemplate.java:785)-  
  11. ExecutingpreparedSQLupdate  
  12. 13:24:22,921DEBUG(JdbcTemplate.java:569)-  
  13. ExecutingpreparedSQLstatement  
  14.  [UPDATEt_useruSETu.score=u.score+?WHEREuser_name=?]  
  15. 13:24:23,140DEBUG(JdbcTemplate.java:794)-  
  16. SQLupdateaffected0rows  
  17. 13:24:23,140DEBUG(AbstractPlatformTransactionManager.java:752)-  
  18. Initiatingtransactioncommit  
  19. 13:24:23,140DEBUG(DataSourceTransactionManager.java:265)-  
  20. CommittingJDBCtransactiononConnection  
  21.  [org.apache.commons.dbcp.PoolableConnection@6e1cbf]  
  22. 13:24:23,140DEBUG(DataSourceTransactionManager.java:323)-  
  23. ReleasingJDBCConnection[org.apache.commons.dbcp.PoolableConnection@6e1cbf]  
  24.  aftertransaction  
  25. 13:24:23,156DEBUG(DataSourceUtils.java:312)-  
  26. ReturningJDBCConnectiontoDataSource 

日志中粗體部分說明了MixLayerUserService#logon方法已經正確運行在事務上下文中。Spring框架本身不應該是復雜化代碼的理由,使用Spring的開發者應該是無拘無束的:從實際應用出發,去除掉那些所謂原則性的接口,去除掉強制分層的束縛,簡單才是硬道理。

事務方法嵌套調用的迷茫

Spring事務一個被訛傳很廣說法是:一個事務方法不應該調用另一個事務方法,否則將產生兩個事務。結果造成開發人員在設計事務方法時束手束腳,生怕一不小心就踩到地雷。其實這種是不認識Spring事務傳播機制而造成的誤解,Spring對事務控制的支持統一在TransactionDefinition類中描述,該類有以下幾個重要的接口方法:

◆intgetPropagationBehavior():事務的傳播行為;
◆intgetIsolationLevel():事務的隔離級別;
◆intgetTimeout():事務的過期時間;
◆booleanisReadOnly():事務的讀寫特性。

很明顯,除了事務的傳播行為外,事務的其它特性Spring是借助底層資源的功能來完成的,Spring無非只充當個代理的角色。但是事務的傳播行為卻是Spring憑借自身的框架提供的功能,是Spring提供給開發者最珍貴的禮物,訛傳的說法玷污了Spring事務框架最美麗的光環。所謂事務傳播行為就是多個事務方法相互調用時,事務如何在這些方法間傳播。Spring支持7種事務傳播行為:

◆PROPAGATION_REQUIRED如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中。這是最常見的選擇。

◆PROPAGATION_SUPPORTS支持當前事務,如果當前沒有事務,就以非事務方式執行。

◆PROPAGATION_MANDATORY使用當前的事務,如果當前沒有事務,就拋出異常。

◆PROPAGATION_REQUIRES_NEW新建事務,如果當前存在事務,把當前事務掛起。

◆PROPAGATION_NOT_SUPPORTED以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。

◆PROPAGATION_NEVER以非事務方式執行,如果當前存在事務,則拋出異常。

◆PROPAGATION_NESTED如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。

Spring默認的事務傳播行為是PROPAGATION_REQUIRED,它適合于絕大多數的情況。假設ServiveX#methodX()都工作在事務環境下(即都被Spring事務增強了),假設程序中存在如下的調用鏈:Service1#method1()->Service2#method2()->Service3#method3(),那么這3個服務類的3個方法通過Spring的事務傳播機制都工作在同一個事務中。

下面,我們來看一下實例,UserService#logon()方法內部調用了UserService#updateLastLogonTime()和ScoreService#addScore()方法,這兩個類都繼承于BaseService。它們之間的類結構說明如下:

UserService和ScoreService 
圖1.UserService和ScoreService

具體的代碼如下所示:

清單9UserService.java

  1. @Service("userService")  
  2. publicclassUserServiceextendsBaseService{  
  3. @Autowired  
  4. privateJdbcTemplatejdbcTemplate;  
  5. @Autowired  
  6. privateScoreServicescoreService;  
  7.  
  8. publicvoidlogon(StringuserName){  
  9. updateLastLogonTime(userName);  
  10. scoreService.addScore(userName,20);  
  11. }  
  12.  
  13. publicvoidupdateLastLogonTime(StringuserName){  
  14. Stringsql="UPDATEt_useruSETu.last_logon_time=?WHEREuser_name=?";  
  15. jdbcTemplate.update(sql,System.currentTimeMillis(),userName);  
  16. }  

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

清單10ScoreService.java

  1. @Service("scoreUserService")  
  2. publicclassScoreServiceextendsBaseService{  
  3. @Autowired  
  4. privateJdbcTemplatejdbcTemplate;  
  5. publicvoidaddScore(StringuserName,inttoAdd){  
  6. Stringsql="UPDATEt_useruSETu.score=u.score+?WHEREuser_name=?";  
  7. jdbcTemplate.update(sql,toAdd,userName);  
  8. }  

通過Spring的事務配置為ScoreService及UserService中所有公有方法都添加事務增強,讓這些方法都工作于事務環境下。下面是關鍵的配置代碼:#p#

清單11事務增強配置

  1.  
  2. <aop:configproxy-target-classaop:configproxy-target-class="true"> 
  3. <aop:pointcutidaop:pointcutid="serviceJdbcMethod" 
  4.  
  5. expression="within(user.nestcall.BaseService+)"/> 
  6. <aop:advisorpointcut-refaop:advisorpointcut-ref="serviceJdbcMethod" 
  7. advice-ref="jdbcAdvice"order="0"/> 
  8. aop:config> 
  9. <tx:adviceidtx:adviceid="jdbcAdvice"transaction-manager="jdbcManager"> 
  10. <tx:attributes> 
  11. <tx:methodnametx:methodname="*"/> 
  12. tx:attributes> 
  13. tx:advice> 

將日志級別設置為DEBUG,啟動Spring容器并執行UserService#logon()的方法,仔細觀察如下的輸出日志:

清單12執行日志

  1. 16:25:04,765DEBUG(AbstractPlatformTransactionManager.java:365)-  
  2. Creatingnewtransactionwithname[user.nestcall.UserService.logon]:  
  3. PROPAGATION_REQUIRED,ISOLATION_DEFAULT①為UserService#logon方法啟動一個事務  
  4. 16:25:04,765DEBUG(DataSourceTransactionManager.java:205)-  
  5. AcquiredConnection[org.apache.commons.dbcp.PoolableConnection@32bd65]  
  6. forJDBCtransaction  
  7. logonmethod...  
  8. updateLastLogonTime...②直接執行updateLastLogonTime方法  
  9. 16:25:04,781DEBUG(JdbcTemplate.java:785)-ExecutingpreparedSQLupdate  
  10. 16:25:04,781DEBUG(JdbcTemplate.java:569)-ExecutingpreparedSQLstatement  
  11. [UPDATEt_useruSETu.last_logon_time=?WHEREuser_name=?]  
  12. 16:25:04,828DEBUG(JdbcTemplate.java:794)-SQLupdateaffected0rows  
  13. 16:25:04,828DEBUG(AbstractPlatformTransactionManager.java:470)-Participating  
  14. inexistingtransaction③ScoreService#addScore方法加入到UserService#logon的事務中  
  15. addScore...  
  16. 16:25:04,828DEBUG(JdbcTemplate.java:785)-ExecutingpreparedSQLupdate  
  17. 16:25:04,828DEBUG(JdbcTemplate.java:569)-ExecutingpreparedSQLstatement  
  18. [UPDATEt_useruSETu.score=u.score+?WHEREuser_name=?]  
  19. 16:25:04,828DEBUG(JdbcTemplate.java:794)-SQLupdateaffected0rows  
  20. 16:25:04,828DEBUG(AbstractPlatformTransactionManager.java:752)-  
  21. Initiatingtransactioncommit  
  22. 16:25:04,828DEBUG(DataSourceTransactionManager.java:265)-CommittingJDBCtransaction  
  23. onConnection[org.apache.commons.dbcp.PoolableConnection@32bd65]  
  24. 16:25:04,828DEBUG(DataSourceTransactionManager.java:323)-ReleasingJDBCConnection  
  25. [org.apache.commons.dbcp.PoolableConnection@32bd65]aftertransaction  
  26. 16:25:04,828DEBUG(DataSourceUtils.java:312)-ReturningJDBCConnectiontoDataSource  

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

多線程的困惑

由于Spring事務管理器是通過線程相關的ThreadLocal來保存數據訪問基礎設施,再結合IOC和AOP實現高級聲明式事務的功能,所以Spring的事務天然地和線程有著千絲萬縷的聯系。

我們知道Web容器本身就是多線程的,Web容器為一個Http請求創建一個獨立的線程,所以由此請求所牽涉到的Spring容器中的Bean也是運行于多線程的環境下。在絕大多數情況下,Spring的Bean都是單實例的(singleton),單實例Bean的最大的好處是線程無關性,不存在多線程并發訪問的問題,也即是線程安全的。一個類能夠以單實例的方式運行的前提是“無狀態”:即一個類不能擁有狀態化的成員變量。我們知道,在傳統的編程中,DAO必須執有一個Connection,而Connection即是狀態化的對象。所以傳統的DAO不能做成單實例的,每次要用時都必須new一個新的實例。傳統的Service由于將有狀態的DAO作為成員變量,所以傳統的Service本身也是有狀態的。

但是在Spring中,DAO和Service都以單實例的方式存在。Spring是通過ThreadLocal將有狀態的變量(如Connection等)本地線程化,達到另一個層面上的“線程無關”,從而實現線程安全。Spring不遺余力地將狀態化的對象無狀態化,就是要達到單實例化Bean的目的。由于Spring已經通過ThreadLocal的設施將Bean無狀態化,所以Spring中單實例Bean對線程安全問題擁有了一種天生的免疫能力。不但單實例的Service可以成功運行于多線程環境中,Service本身還可以自由地啟動獨立線程以執行其它的Service。下面,通過一個實例對此進行描述:

清單13UserService.java在事務方法中啟動獨立線程運行另一個事務方法

  1. @Service("userService")  
  2. publicclassUserServiceextendsBaseService{  
  3. @Autowired  
  4. privateJdbcTemplatejdbcTemplate;  
  5.  
  6. @Autowired  
  7. privateScoreServicescoreService;  
  8. //①在logon方法體中啟動一個獨立的線程,在該獨立的線程中執行ScoreService#addScore()方法  
  9. publicvoidlogon(StringuserName){  
  10. System.out.println("logonmethod...");  
  11. updateLastLogonTime(userName);  
  12. ThreadmyThread=newMyThread(this.scoreService,userName,20);  
  13. myThread.start();  
  14. }  
  15.  
  16. publicvoidupdateLastLogonTime(StringuserName){  
  17. System.out.println("updateLastLogonTime...");  
  18. Stringsql="UPDATEt_useruSETu.last_logon_time=?WHEREuser_name=?";  
  19. jdbcTemplate.update(sql,System.currentTimeMillis(),userName);  
  20. }  
  21. //②封裝ScoreService#addScore()的線程  
  22. privateclassMyThreadextendsThread{  
  23. privateScoreServicescoreService;  
  24. privateStringuserName;  
  25. privateinttoAdd;  
  26. privateMyThread(ScoreServicescoreService,StringuserName,inttoAdd){  
  27. this.scoreService=scoreService;  
  28. this.userName=userName;  
  29. this.toAdd=toAdd;  
  30. }  
  31. publicvoidrun(){  
  32. scoreService.addScore(userName,toAdd);  
  33. }  
  34. }  

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

清單14執行日志

  1. [main](AbstractPlatformTransactionManager.java:365)-Creatingnewtransactionwithname  
  2. [user.multithread.UserService.logon]:PROPAGATION_REQUIRED,ISOLATION_DEFAULT①  
  3.  
  4. [main](DataSourceTransactionManager.java:205)-AcquiredConnection  
  5. [org.apache.commons.dbcp.PoolableConnection@1353249]forJDBCtransaction  
  6.  
  7. logonmethod...  
  8.  
  9. updateLastLogonTime...  
  10.  
  11. [main](JdbcTemplate.java:785)-ExecutingpreparedSQLupdate  
  12. [main](JdbcTemplate.java:569)-ExecutingpreparedSQLstatement  
  13. [UPDATEt_useruSETu.last_logon_time=?WHEREuser_name=?]  
  14. [main](JdbcTemplate.java:794)-SQLupdateaffected0rows  
  15. [main](AbstractPlatformTransactionManager.java:752)-Initiatingtransactioncommit  
  16.  
  17. [Thread-2](AbstractPlatformTransactionManager.java:365)-  
  18. Creatingnewtransactionwithname[user.multithread.ScoreService.addScore]:  
  19. PROPAGATION_REQUIRED,ISOLATION_DEFAULT②  
  20. [main](DataSourceTransactionManager.java:265)-CommittingJDBCtransaction  
  21. onConnection[org.apache.commons.dbcp.PoolableConnection@1353249]③  
  22.  
  23. [main](DataSourceTransactionManager.java:323)-ReleasingJDBCConnection  
  24. [org.apache.commons.dbcp.PoolableConnection@1353249]aftertransaction  
  25. [main](DataSourceUtils.java:312)-ReturningJDBCConnectiontoDataSource  
  26.  
  27. [Thread-2](DataSourceTransactionManager.java:205)-AcquiredConnection  
  28. [org.apache.commons.dbcp.PoolableConnection@10dc656]forJDBCtransaction  
  29.  
  30. addScore...  
  31.  
  32. [main](JdbcTemplate.java:416)-ExecutingSQLstatement  
  33. [DELETEFROMt_userWHEREuser_name='tom']  
  34. [main](DataSourceUtils.java:112)-FetchingJDBCConnectionfromDataSource  
  35. [Thread-2](JdbcTemplate.java:785)-ExecutingpreparedSQLupdate  
  36. [Thread-2](JdbcTemplate.java:569)-ExecutingpreparedSQLstatement  
  37. [UPDATEt_useruSETu.score=u.score+?WHEREuser_name=?]  
  38. [main](DataSourceUtils.java:312)-ReturningJDBCConnectiontoDataSource  
  39. [Thread-2](JdbcTemplate.java:794)-SQLupdateaffected0rows  
  40. [Thread-2](AbstractPlatformTransactionManager.java:752)-Initiatingtransactioncommit  
  41. [Thread-2](DataSourceTransactionManager.java:265)-CommittingJDBCtransaction  
  42. onConnection[org.apache.commons.dbcp.PoolableConnection@10dc656]④  
  43. [Thread-2](DataSourceTransactionManager.java:323)-ReleasingJDBCConnection  
  44. [org.apache.commons.dbcp.PoolableConnection@10dc656]aftertransaction 

在①處,在主線程(main)執行的UserService#logon()方法的事務啟動,在③處,其對應的事務提交,而在子線程(Thread-2)執行的ScoreService#addScore()方法的事務在②處啟動,在④處對應的事務提交。

所以,我們可以得出這樣的結論:在相同線程中進行相互嵌套調用的事務方法工作于相同的事務中。如果這些相互嵌套調用的方法工作在不同的線程中,不同線程下的事務方法工作在獨立的事務中。

小結

Spring聲明式事務是Spring最核心,最常用的功能。由于Spring通過IOC和AOP的功能非常透明地實現了聲明式事務的功能,一般的開發者基本上無須了解Spring聲明式事務的內部細節,僅需要懂得如何配置就可以了。

但是在實際應用開發過程中,Spring的這種透明的高階封裝在帶來便利的同時,也給我們帶來了迷惑。就像通過流言傳播的消息,最終聽眾已經不清楚事情的真相了,而這對于應用開發來說是很危險的。本系列文章通過剖析實際應用中給開發者造成迷惑的各種難點,通過分析Spring事務管理的內部運作機制將真相還原出來。在本文中,我們通過剖析了解到以下的真相:

◆在沒有事務管理的情況下,DAO照樣可以順利進行數據操作;

◆將應用分成Web,Service及DAO層只是一種參考的開發模式,并非是事務管理工作的前提條件;

◆Spring通過事務傳播機制可以很好地應對事務方法嵌套調用的情況,開發者無須為了事務管理而刻意改變服務方法的設計;

◆由于單實例的對象不存在線程安全問題,所以進行事務管理增強的Bean可以很好地工作在多線程環境下。

在下一篇文章中,筆者將繼續分析Spring事務管理的以下難點:

◆混合使用多種數據訪問技術(如SpringJDBC+Hibernate)的事務管理問題;

◆在通過Bean的方法通過SpringAOP增強存在哪些特殊的情況。

【編輯推薦】

  1. 簡單介紹Spring事務管理
  2. Spring的Hibernate事務管理機制
  3. Spring聲明式事務管理源碼解讀之事務提交
  4. 實例詳解Spring JDBC事務管理
  5. Hibernate事務管理機制剖析
責任編輯:王曉東 來源: IBM
相關推薦

2010-03-29 13:34:15

ibmdwSpring

2014-08-25 09:12:47

Spring事務管理

2009-09-23 17:48:00

Hibernate事務

2009-06-17 14:43:47

Spring框架Spring事務管理

2023-10-08 08:28:10

Spring事務管理

2009-06-17 14:57:11

Spring事務管理

2009-06-30 16:57:42

Spring事務管理

2009-06-08 17:56:00

SpringJDBC事務

2023-03-27 10:40:09

2009-09-25 12:59:53

Hibernate事務

2009-02-11 13:08:29

事務提交事務管理Spring

2009-02-11 11:14:31

事務管理事務開始Spring

2025-02-08 10:56:18

2025-02-18 13:00:00

SpringBoot事務管理代碼

2009-06-03 10:20:11

Hibernate事務管理配置

2009-09-29 09:44:52

Hibernate事務

2025-02-21 08:00:00

事務管理SpringBootJava

2023-05-06 07:29:49

Spring事務傳播

2022-08-04 08:46:16

單體架構微服務事務管理

2009-07-17 14:03:34

ibatis DAO事務管理
點贊
收藏

51CTO技術棧公眾號

日本道免费精品一区二区三区| 成人免费看黄yyy456| 最好看的2019年中文视频| 亚洲人视频在线| 韩国日本一区| 国产日产欧美一区二区视频| 欧美日本视频在线| 亚洲AV无码成人精品一区| 肥臀熟女一区二区三区| 日韩电影一区二区三区| 欧美成人免费一级人片100| 亚洲成人av免费在线观看| 日本黄色一区| 亚洲1区2区3区4区| 制服丝袜综合日韩欧美| 三级黄视频在线观看| 韩国理伦片一区二区三区在线播放| 久久久视频精品| 一本在线免费视频| 免费萌白酱国产一区二区三区| 欧美日韩1区2区| 国产91在线视频观看| www在线免费观看视频| 久久伊99综合婷婷久久伊| 亚洲aⅴ男人的天堂在线观看| 狠狠人妻久久久久久| 国产精品av一区二区| 中文字幕在线日韩| 欧美色图亚洲激情| 国产成人夜色高潮福利影视| 欧美日韩国产高清一区二区| 国产99久久九九精品无码| 日韩激情av| |精品福利一区二区三区| 欧美日韩一区二区三区在线视频 | 成人直播视频| 亚洲午夜在线电影| 欧美一级免费在线观看| jizz亚洲| 国产精品素人视频| 日本在线观看一区二区三区| 天天操天天操天天操| 国产大片一区二区| 亚洲在线一区二区| 夜夜躁很很躁日日躁麻豆| 欧美aⅴ一区二区三区视频| 奇米影视亚洲狠狠色| 亚洲影院在线播放| 久久不射网站| 国产mv久久久| 无码人妻丰满熟妇精品| 久久影院亚洲| 国产精品电影一区| 91精品国产乱码久久久| 老色鬼精品视频在线观看播放| 国产精品福利片| 自拍偷拍福利视频| 看片网站欧美日韩| 92裸体在线视频网站| 99国产精品99| 成人综合在线视频| 国产日韩欧美精品| 熟妇高潮一区二区三区| 久久先锋资源网| 欧美日韩在线播放一区二区| 九九在线视频| 中文幕一区二区三区久久蜜桃| 一本色道久久99精品综合| 看黄网站在线| 亚洲国产一区二区三区| 久草热视频在线观看| 成人做爰视频www网站小优视频| 欧洲一区二区三区在线| 亚洲 国产 图片| 亚洲亚洲一区二区三区| 日韩av综合中文字幕| 极品人妻videosss人妻| 亚洲精品国产成人影院| 高清欧美性猛交xxxx黑人猛交| 国产区在线观看视频| 久久在线精品| 91亚洲永久免费精品| 免费观看的毛片| 国产亚洲欧美日韩日本| 亚洲精品日韩精品| 成人性生交大片免费看网站| 日韩欧美在线免费观看| 亚洲天堂2018av| 国产区精品视频在线观看豆花| 亚洲女人被黑人巨大进入| www.4hu95.com四虎| 欧美午夜在线| 国产精品免费电影| 欧洲av在线播放| 国产日韩欧美综合一区| 性生活免费观看视频| 在线观看网站免费入口在线观看国内 | 亚洲成人亚洲激情| 亚洲自拍偷拍图| 欧美高清一区| 国产精品狼人色视频一区| 亚洲黄色一级大片| 国产精品入口麻豆原神| 欧美一级欧美一级| 久久69成人| 日韩精品在线视频| 久草视频免费在线播放| 日本午夜精品一区二区三区电影 | 欧洲美女7788成人免费视频| 国产超碰人人模人人爽人人添| 2021国产精品久久精品| 欧美交换配乱吟粗大25p| 日韩精品一区二区三区av| 精品国产伦一区二区三区观看体验| 国产传媒在线看| 国产美女精品| 成人午夜电影免费在线观看| 欧美人xxx| 欧美色精品天天在线观看视频| 欧产日产国产精品98| 亚洲精品成人无限看| 国产精品视频一| 日韩一级中文字幕| 一区二区视频免费在线观看| 天天干天天草天天| 丝袜久久网站| 91国自产精品中文字幕亚洲| 黄色www视频| 伊人开心综合网| 国产又粗又猛大又黄又爽| 人人狠狠综合久久亚洲婷婷| 日韩av片免费在线观看| 四虎成人免费在线| 亚洲福利电影网| 国产一精品一aⅴ一免费| 91久久国产| 国产主播喷水一区二区| 91社区在线观看播放| 欧美综合一区二区三区| 在线免费观看麻豆| 日韩精品电影一区亚洲| 欧美一区二区三区成人久久片| 三妻四妾的电影电视剧在线观看| 亚洲国产天堂网精品网站| 日本天堂在线视频| 99免费精品在线观看| 日本www在线视频| 久久午夜影院| 欧美亚洲成人精品| 天堂av在线免费观看| 疯狂做受xxxx高潮欧美日本| 久久午夜夜伦鲁鲁片| 国产精品亚洲欧美| 欧美日韩视频在线一区二区观看视频| 亚洲欧美se| 亚洲偷熟乱区亚洲香蕉av| 少妇无套内谢久久久久| 国产精品三级视频| 天天干天天色天天干| 亚洲香蕉av| 成人在线观看网址| 阿v视频在线| 亚洲视频一区二区| 中文字幕制服诱惑| 日韩码欧中文字| 丰满少妇中文字幕| 国产日韩视频| 亚洲精品免费在线看| 秋霞影院一区| 2020久久国产精品| www.国产精品.com| 欧美一级专区免费大片| 久久久午夜影院| 国产农村妇女毛片精品久久麻豆| 日韩av.com| 国产日本精品| 在线一区高清| 国产一区在线电影| 国产精品美乳一区二区免费| 最新av在线播放| 亚洲老司机av| 国产精品热久久| 香蕉影视欧美成人| 懂色av蜜桃av| 国产成人精品一区二| 日韩免费毛片视频| 伊人成综合网| 欧美12av| 综合激情久久| 国产精品久在线观看| 岛国av在线播放| 自拍偷拍亚洲一区| 日批视频免费播放| 这里是久久伊人| 成人毛片在线播放| 亚洲视频在线观看三级| 无码人妻aⅴ一区二区三区| 韩国v欧美v亚洲v日本v| 六月激情综合网| 欧美日韩亚洲三区| 亚洲激情啪啪| 欧美网色网址| 91探花福利精品国产自产在线| 美女高潮在线观看| 欧美成人一二三| 国产二区在线播放| 亚洲国产成人久久| 亚洲天堂aaa| 日本道免费精品一区二区三区| 久久免费小视频| 自拍偷拍亚洲激情| 91av在线免费| 成人激情av网| 四虎国产精品免费| 精品一区二区国语对白| av片在线免费| 自拍偷拍欧美专区| 亚洲欧洲一区二区福利| 女人丝袜激情亚洲| 国产一区再线| 中文字幕一区二区三区四区久久| 国产欧美精品va在线观看| 深夜成人影院| 欧美一区二区.| 国产亚洲成av人片在线观看| 色综合91久久精品中文字幕 | 亚洲精品乱码久久久久久久久| 亚洲理论片在线观看| 99久久精品国产一区二区三区| 波多野结衣三级视频| 国产一区二区三区精品欧美日韩一区二区三区 | 在线免费观看成人| 久久精品国产99久久| 日韩午夜视频在线观看| 国产欧美日韩视频在线| 欧美日韩一区二区视频在线| 天堂资源在线亚洲| 久久日韩精品| 少妇精品久久久一区二区| 久久综合毛片| 久久99国内| 日韩免费电影一区二区三区| 国模精品一区| 亚洲一区二区高清视频| 欧美黄色大片在线观看| 在线观看欧美激情| 91精品国产麻豆国产在线观看 | 国产亚洲精品熟女国产成人| 久久五月婷婷丁香社区| 无码 人妻 在线 视频| 国产日产亚洲精品系列| 青青青视频在线免费观看| 中文字幕欧美一| 欧美老熟妇一区二区三区| 樱花影视一区二区| 精品欧美一区二区三区免费观看 | 久久99国产精品视频| 品久久久久久久久久96高清| 精品国产一区二区三区小蝌蚪| 亚洲制服中文| 欧美日韩免费| 午夜肉伦伦影院| 美腿丝袜亚洲色图| 欧美国产日韩一区二区| 亚洲午夜无码久久久久| 欧美日韩一区二区不卡| 国产精品美女一区| 亚洲第五色综合网| 久久精品国产亚洲a∨麻豆| 国产一区av在线| 免费黄网站在线| 久久99精品国产99久久6尤物| 成人在线高清免费| 国产精品成久久久久三级| 秋霞国产精品| julia一区二区中文久久94| 美女一区2区| 亚洲精品日韩精品| 在线成人亚洲| 美女网站视频黄色| 成人一区二区视频| 天堂久久精品忘忧草| 亚洲欧美福利一区二区| 日韩视频免费观看高清| 欧美色爱综合网| 黄色av小说在线观看| 最新69国产成人精品视频免费| 色呦呦在线观看视频| 国产精品91久久久久久| 欧美日韩国产一区二区在线观看| 久久人人九九| 欧美日韩成人| 国产理论在线播放| gogo大胆日本视频一区| 亚洲综合图片一区| 狠狠躁夜夜躁人人爽超碰91| 一区二区精品视频在线观看| 日韩成人av在线| 岛国成人毛片| 国产精品久久久久久久久久ktv| 日韩中文字幕| 亚洲欧美成人一区| 国产午夜精品一区二区三区欧美 | 亚洲人体在线| 欧美日本亚洲| 亚洲区第一页| 潘金莲一级淫片aaaaaaa| 国产色婷婷亚洲99精品小说| 日本少妇bbwbbw精品| 91精品国产综合久久福利| 国产免费a∨片在线观看不卡| 午夜精品久久久久久久白皮肤| 日韩一区二区三免费高清在线观看| 免费久久一级欧美特大黄| 亚洲茄子视频| 日韩大尺度视频| 亚洲视频在线一区二区| 亚洲无码精品国产| 亚洲天堂2020| 在线免费三级电影网站| 韩国成人动漫在线观看| 欧美日韩国产精品一区二区亚洲| 国产视频1区2区3区| 国产日韩精品一区二区三区| 天天综合天天干| 亚洲国产精品va在线观看黑人| 午夜dj在线观看高清视频完整版 | 国产精品久久久久久久久久久免费看| 好看的av在线| 亚洲黄页网在线观看| 久草在线资源站资源站| 91黄色国产视频| 欧美精品97| 性xxxxxxxxx| 亚洲免费三区一区二区| 国产精品亚洲lv粉色| 色黄久久久久久| 国产精品久久乐| 亚洲精品一品区二品区三品区| 日日欢夜夜爽一区| 手机免费看av| 欧美性xxxxx极品少妇| h视频在线播放| 国产精品天天狠天天看| 国内精品久久久久久久久电影网| 粉嫩虎白女毛片人体| 国产欧美中文在线| 一级黄色大片免费观看| 色播久久人人爽人人爽人人片视av| 欧美日韩免费电影| 二级片在线观看| 国产成人亚洲综合a∨猫咪| 免费视频网站www| 亚洲加勒比久久88色综合| 天堂中文av在线资源库| 日韩久久久久久久| 麻豆精品在线观看| 91香蕉一区二区三区在线观看| 欧美一级生活片| √8天堂资源地址中文在线| 精品国产乱码久久久久久郑州公司 | 久久97精品久久久久久久不卡| 91成人短视频| 久久精品国产精品亚洲色婷婷| 久久精品人人做人人爽97| 亚洲最大成人av| 九九热精品在线| 日本一区福利在线| 欧美一级裸体视频| 亚洲欧美在线aaa| 六月丁香色婷婷| 国产精品草莓在线免费观看| 91精品一区国产高清在线gif| 国产免费a级片| 色婷婷综合五月| a级在线观看| 久久伦理网站| 国产自产v一区二区三区c| 久久人人爽人人爽人人| 国产亚洲精品久久久优势| 久久久国产精品入口麻豆| 欧美 日本 亚洲| 国产精品久久三| 手机看片一区二区| 91精品视频在线免费观看| 在线视频免费在线观看一区二区| 一二三四国产精品| 亚洲国产精品成人精品| 日本a人精品| 欧美国产亚洲一区| 亚洲欧洲三级电影| 五十路在线观看| 亚洲综合视频1区| 日韩激情视频网站| 国产主播在线播放| 最近2019中文字幕mv免费看 | 日韩毛片在线视频| 日韩亚洲第一页| 蜜桃成人av| 日批免费观看视频|