Spring重複掃描導致事務失敗的解決方案及深入分析

問題及日誌

使用Spring和mybatis,然後配置事務,出現SqlSession was not registered for synchronization because synchronization is not active,事務沒有啓用成功。

[org.mybatis.spring.SqlSessionUtils] - Creating a new SqlSession
[org.mybatis.spring.SqlSessionUtils] - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1a714e6e] was not registered for synchronization because synchronization is not active
[org.springframework.jdbc.datasource.DataSourceUtils] - Fetching JDBC Connection from DataSource
[org.mybatis.spring.transaction.SpringManagedTransaction] - JDBC Connection [jdbc: mysql://localhost:3306/iot, UserName=root@localhost, MySQL Connector Java]* will not be managed by Spring*
[com.haoyifen.mySSMTemplate.dao.UserMapper.selectByPrimaryKey] - ==> Preparing: select ID, USER_ID, USER_NAME, SEX, ADDRESS, PHONE_NUMBER, MAIL_BOX from user where ID = ?
[com.haoyifen.mySSMTemplate.dao.UserMapper.selectByPrimaryKey] - ==> Parameters: 1(Integer)
[com.haoyifen.mySSMTemplate.dao.UserMapper.selectByPrimaryKey] - <== Total: 1
[org.mybatis.spring.SqlSessionUtils] - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1a714e6e]

IntelliJ IDEA的編輯器顯示我的攔截路徑是正確的。爲UserService類創建事務配置。
這裏寫圖片描述

問題分析及解決方案

後來深入分析才發現,原來是掃描配置錯誤。
我把Spring的ApplicationContext和DispatcherServlet(WebApplicationContext)配置使用兩個文件分離開的,分別爲spring.xml和spring-mvc.xml。在spring.xml中配置了事務。在兩個文件中都啓用了掃描設置,用於掃描@Service,@Component和@Controller

spring.xml
<!-- 自動掃描 -->
<context:component-scan base-package="com.haoyifen.mySSMTemplate"></context:component-scan>
spring-mvc.xml
<context:component-scan base-package="com.haoyifen.mySSMTemplate"></context:component-scan>

以上的配置使得spring-mvc重複掃描了userService類,而spring如果要使事務生效,就需要cglib爲userService生成代理子類,在spring.xml中已經生成了代理類,而在spring-mvc.xml中,又重新掃描了一遍,使得原先cglib生成的代理子類失效,從而事務攔截也失效。所以我們應該設置spring-mvc.xml,使其不掃描@Service和@Component,spring.xml不掃描@Controller。配置如下:

spring.xml
<!-- 自動掃描 ,忽略@Controller註解的類-->
<context:component-scan base-package="com.haoyifen.mySSMTemplate">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"></context:exclude-filter>
</context:component-scan>
spring-mvc.xml
<!--自動掃描該包,使spring-mvc只掃描controller包中的類(其中只有@Controller控制器),不會重複掃描到@Service或者@Component-->
<context:component-scan base-package="com.haoyifen.mySSMTemplate.controller">
</context:component-scan>

驗證

修改後,再查看日誌,就發現已經可以正確啓用事務了。
[org.springframework.jdbc.datasource.DataSourceTransactionManager] - Acquired Connection [jdbc:mysql://localhost:3306/iot, UserName=root@localhost, MySQL Connector Java] for JDBC transaction
[org.springframework.jdbc.datasource.DataSourceTransactionManager] - Switching JDBC Connection [jdbc:mysql://localhost:3306/iot, UserName=root@localhost, MySQL Connector Java]* to manual commit*
[org.mybatis.spring.SqlSessionUtils] - Creating a new SqlSession
[org.mybatis.spring.SqlSessionUtils] -* Registering transaction synchronization for SqlSession* [org.apache.ibatis.session.defaults.DefaultSqlSession@1031238e]
[org.mybatis.spring.transaction.SpringManagedTransaction] - JDBC Connection [jdbc:mysql://localhost:3306/iot, UserName=root@localhost, MySQL Connector Java]* will be managed by Spring*
….
[org.mybatis.spring.SqlSessionUtils] -* Transaction synchronization closing SqlSession* [org.apache.ibatis.session.defaults.DefaultSqlSession@1031238e]
[org.springframework.jdbc.datasource.DataSourceTransactionManager] - Initiating transaction commit
[org.springframework.jdbc.datasource.DataSourceTransactionManager] - Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/iot, UserName=root@localhost, MySQL Connector Java]
[org.springframework.jdbc.datasource.DataSourceTransactionManager] - Releasing JDBC Connection [jdbc:mysql://localhost:3306/iot, UserName=root@localhost, MySQL Connector Java] after transaction
[org.springframework.jdbc.datasource.DataSourceUtils] - Returning JDBC Connection to DataSource

深入日誌分析

我們可以查看日誌,來分析一下userService的創建過程。在修改xml文件之前:

Application初始化

創建Application時創建了一次userService,並使用cglib代理子類,生成了事務類:
[org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - Loading XML bean definitions from class path resource [spring.xml]
…..
開始創建userService類
[org.springframework.beans.factory.support.DefaultListableBeanFactory] -* Creating shared instance of singleton bean ‘userService’*
[org.springframework.beans.factory.support.DefaultListableBeanFactory] - Creating instance of bean ‘userService’
……
緩存userService用於其他bean的注入
[org.springframework.beans.factory.support.DefaultListableBeanFactory] - Eagerly caching bean ‘userService’ to allow for resolving potential circular references
……
注入userMapper
[org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor] - Autowiring by type from bean name ‘userService’ to bean named ‘userMapper’
……
生成cglib代理類
[org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator] - Creating implicit proxy for bean ‘userService’ with 0 common interceptors and 2 specific interceptors
[org.springframework.aop.framework.CglibAopProxy] - Creating CGLIB proxy: target source is SingletonTargetSource for target object [com.haoyifen.mySSMTemplate.service.UserService@2c06eb2b]
…..
完成創建,此時的userService已經是被cglib代理過的子類的實例
[org.springframework.beans.factory.support.DefaultListableBeanFactory] - Finished creating instance of bean ‘userService’

創建WebApplication子容器

接下來創建WebApplication子容器時,又創建了一次userService,並且沒有使用cglib進行代理,所以事務失效。
創建WebApplication
[org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - Loading XML bean definitions from class path resource [spring-mvc.xml]
開始創建userService類
[org.springframework.beans.factory.support.DefaultListableBeanFactory] -* Creating shared instance of singleton bean ‘userService’*
[org.springframework.beans.factory.support.DefaultListableBeanFactory] - Creating instance of bean ‘userService’
[org.springframework.beans.factory.annotation.InjectionMetadata] - Registered injected element on class [com.haoyifen.mySSMTemplate.service.UserService]: AutowiredFieldElement for private com.haoyifen.mySSMTemplate.dao.UserMapper com.haoyifen.mySSMTemplate.service.UserService.userMapper
緩存userService用於其他bean的注入
[org.springframework.beans.factory.support.DefaultListableBeanFactory] - Eagerly caching bean ‘userService’ to allow for resolving potential circular references
[org.springframework.beans.factory.annotation.InjectionMetadata] - Processing injected element of bean ‘userService’: AutowiredFieldElement for private com.haoyifen.mySSMTemplate.dao.UserMapper com.haoyifen.mySSMTemplate.service.UserService.userMapper
[org.springframework.beans.factory.support.DefaultListableBeanFactory] - Returning cached instance of singleton bean ‘userMapper’
注入userMapper
[org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor] - Autowiring by type from bean name ‘userService’ to bean named ‘userMapper’
完成創建,本來由cglib代理的子類實例被替換成原生的userService實例,自然也就沒有了事務功能
[org.springframework.beans.factory.support.DefaultListableBeanFactory] -* Finished creating instance of bean ‘userService’*

我們按照上面的方法將spring-mvc.xml文件更改後,在創建WebApplication時,就不會再重新創建userService了。

發佈了44 篇原創文章 · 獲贊 37 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章