最近在來到一個新公司,使用新的spring3,hibernate4框架,在使用註解事務總是不起作用。
首先看配置檔案,然後再講解。
首先是springmvc-servlet.xml,這個配置檔案是servlet的載入檔案,
引用這個檔案的位置是在web.xml裡
<!-- 定義 springmvcServlet --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <!-- 預設/WEB-INF/[servlet名字]-servlet.xml載入上下文, 如果配置了contextConfigLocation引數, 將使用classpath:/springmvc-servlet.xml載入上下文 --> <param-name>contextConfigLocation</param-name> <param-value>classpath:/springmvc-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <!-- 攔截匹配的請求,這裡所有請求採用名字為mvc-dispatcher的DispatcherServlet處理 --> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
<!-- 啟動自動掃描該包下所有的Bean(例如@Controller) 這塊很重要,會影響到事務--> <context:component-scan base-package="com.bpz.demo" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"></context:include-filter> </context:component-scan> <!-- 定義檢視解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix"> <value>/jsp/</value> </property> <property name="suffix"> <value>.jsp</value> </property> </bean>
然後是applicationcontext.xml
<!-- 啟動自動掃描該包下所有的Bean 注意這塊,也非常重要 --> <context:component-scan base-package="com.bpz.demo" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"></context:include-filter> <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"></context:include-filter> </context:component-scan> <!-- Hibernate4 --> <!-- 載入資原始檔 其中包含變數資訊,必須在Spring配置檔案的最前面載入,即第一個載入--> <context:property-placeholder location="classpath:mysql.properties" /> <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="packagesToScan"> <list> <!-- 可以加多個包 --> <value>com.bpz.demo.entity</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">${hibernate.dialect}</prop> <prop key="hibernate.show_sql">${hibernate.show_sql}</prop> </props> </property> </bean> <!-- 資料庫對映 --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.user}" /> <property name="password" value="${jdbc.pass}" /> </bean> <!-- 基於註釋的事務,當註釋中發現@Transactional時,使用id為“transactionManager”的事務管理器 --> <!-- 如果沒有設定transaction-manager的值,則spring以預設預設的事務管理器來處理事務,預設事務管理器為第一個載入的事務管理器 --> <tx:annotation-driven transaction-manager="transactionManager"/> <!-- 配置Hibernate事務管理器 --> <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
引用位置是在web.xml裡的
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:/applicationcontext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
以上是一個正確的配置方式,事務起作用完全沒有問題。現在說說為什麼很多時候配置的事務未起作用呢。
1.如果事務未起作用還未報錯或異常,那應該是使用了sessionFactory.openSession(),在hibernate4裡,如果使用spring來管理事務,需要使用
Session session = sessionFactory.getCurrentSession();來獲取session。
2.如果使用了Session session = sessionFactory.getCurrentSession();來獲取session有可能會報一個異常:
No Session found for current thread
為什麼會出現這種情況呢,這和上面的配置就有關係了。一般情況下很多人為了省事,在servlet配置檔案裡直接
<context:component-scan base-package="com.bpz.demo"> </context:component-scan>
這樣配置,對全部程式碼掃描,但是在servlet裡並沒有配置事務,所以事務完全不起作用.
不起作用的原因,在另一篇轉載的文章裡可以看到。
web.xml 中的listener、 filter、servlet 載入順序及其詳解
servlet是最後載入的,在最後使用
context:component-scan
掃描的時候,也會發現在有@Service,@Reposity等註解並進行例項化,由於在servlet配置檔案裡未配置事務,所以造成了事務不起作用,
看下面的原始碼:
public Session currentSession() throws HibernateException { Object value = TransactionSynchronizationManager.getResource(this.sessionFactory); if(value instanceof Session) { return (Session)value; } else if(value instanceof SessionHolder) { SessionHolder session2 = (SessionHolder)value; Session session1 = session2.getSession(); if(TransactionSynchronizationManager.isSynchronizationActive() && !session2.isSynchronizedWithTransaction()) { TransactionSynchronizationManager.registerSynchronization(new SpringSessionSynchronization(session2, this.sessionFactory)); session2.setSynchronizedWithTransaction(true); FlushMode flushMode = session1.getFlushMode(); if(flushMode.equals(FlushMode.MANUAL) && !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { session1.setFlushMode(FlushMode.AUTO); session2.setPreviousFlushMode(flushMode); } } return session1; } else if(this.jtaSessionContext != null) { Session session = this.jtaSessionContext.currentSession(); if(TransactionSynchronizationManager.isSynchronizationActive()) { TransactionSynchronizationManager.registerSynchronization(new SpringFlushSynchronization(session)); } return session; } else { throw new HibernateException("No Session found for current thread"); } }
1:@Transactional宣告的方法執行時,Spring的TransactionManager會自動Open Session,自動開啟事務,並且將此Sesion繫結到SpringSessionContext(實際上是TransactionSynchronizationManager的ThreadLocal的Map)中..而@Transactional的宣告只有
tx:annotation-driven配置才會處理這個宣告
2:SessionFactory.getCurrentSession()方法執行時,呼叫SpringSessionContext.currentSession()從TransactionSynchronizationManager的上下文中查詢 當前的Session
3:找到後返回當前的Session,找不到,則返回HibernateException("No Session found for current thread")
對上面原始碼的理解。
so:處理事務不起作用的辦法就是servlet裡做好自己的事,掃描@controller就可以了,這不但能加快啟動速度,而且最主要的就是不會 畫蛇添足。