1. 程式人生 > >SpringMVC+MyBatis+JMS+JTA(分布式事務)

SpringMVC+MyBatis+JMS+JTA(分布式事務)

pack china ras broker classpath package listener internal eba

SpringMVC+MyBatis 相信已經是如今企業開發中經常使用技術了。


由於一些需求,我們須要集成JMS(我使用的是ActiveMQ)。大家應該都知道。MQ也能夠覺得是一個數據源。數據也是數據源。

這樣的情況下,假設我們在一個方法內操作JMS和數據庫。我們就須要保證這種方法運行須要滿足原子性。
這也就意味這一個問題,我們要多個數據源在同一個事務中。

這裏不枚舉市面上的全部解決方式,事實上atomikos JTA 是一個比較不錯分布式事務管理器。
當然假設沒有使用到JMS,在須要多數據源(也就是須要連接多個數據庫)的情況相同適用。

以下將項目的主要配置貼出來共享:
1、applicationContext.xml

<?

xml version="1.0" encoding="GBK"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:amq="http://activemq.apache.org/schema/core" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core-5.13.0.xsd"

>
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/> <!-- 配置掃描路徑 --> <context:component-scan base-package="com.hvgroup.zhuhai10086.jms"> <!-- 僅僅掃描Service。也能夠加入Repostory,可是要把Controller排除在外,Controller由spring-mvc.xml去載入 -->
<!-- <context:include-filter type="annotation" expression="org.springframework.stereotype.Service" /> --> <!-- <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository" /> --> <!-- <context:include-filter type="annotation" expression="org.springframework.stereotype.Component" /> --> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- 讀取環境變量(自己定義擴展) --> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:config/config.properties</value> </list> </property> </bean> <import resource="classpath:spring/applicationContext-Service.xml" /> </beans>

2、applicationContext-Service.xml

<?

xml version="1.0" encoding="GBK"?

> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd"> <!-- JDBC連接數據庫數據源 --> <!-- <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.username}" /> --> <!-- <property name="password" value="${jdbc.password}" /> --> <!-- </bean> --> <!-- DBCP連接池 --> <!-- <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" --> <!-- destroy-method="close"> --> <!-- <property name="driverClassName" value="${jdbc.driverClassName}" /> --> <!-- <property name="url" value="${jdbc.url}" /> --> <!-- <property name="username" value="${jdbc.username}" /> --> <!-- <property name="password" value="${jdbc.password}" /> --> <!-- <property name="initialSize" value="20" /> --> <!-- <property name="maxActive" value="80" /> --> <!-- <property name="maxIdle" value="100" /> --> <!-- <property name="minIdle" value="20" /> --> <!-- <property name="validationQuery" value="SELECT COUNT(*) FROM DUAL"></property> --> <!-- <property name="testOnBorrow" value="true"></property> --> <!-- <property name="testOnReturn" value="true"></property> --> <!-- <property name="testWhileIdle" value="true"></property> --> <!-- </bean> --> <bean id="dataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close"> <property name="uniqueResourceName" value="ds1" /> <!-- <property name="xaDataSourceClassName" value="${jdbc.driverClassName}" /> --> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="xaProperties"> <props> <prop key="URL">${jdbc.url}</prop> <prop key="user">${jdbc.username}</prop> <prop key="password">${jdbc.password}</prop> </props> </property> <!-- #連接池中保留的最小連接數 --> <property name="minPoolSize" value="5" /> <!-- #連接池中保留的最大連接數 --> <property name="maxPoolSize" value="20" /> <!-- #最大空暇時間,60秒內未使用則連接被丟棄。

若為0則永不丟棄。

Default: --> <property name="maxIdleTime" value="60" /> <property name="testQuery"> <value>select 1</value> </property> </bean> <!-- JNDI連接池 --> <!-- <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> --> <!-- <property name="jndiName"> --> <!-- <value>java:comp/env/jdbc/huiyzl</value> --> <!-- </property> --> <!-- </bean> --> <!-- 配置SqlSessionFactoryBean --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- mapper和resultmap配置路徑 --> <property name="mapperLocations"> <list> <!-- 註意classpath後面的星號不可省略,否則不能載入jar包中的xml文件 --> <value>classpath*:com/hvgroup/zhuhai10086/jms/**/sql/mysql/*Mapper.xml </value> </list> </property> <property name="plugins"> <array> <!-- 關於分頁插件的使用說明:http://git.oschina.net/free/Mybatis_PageHelper/blob/master/wikis/HowToUse.markdown --> <bean class="com.github.pagehelper.PageHelper"> <property name="properties"> <value> offsetAsPageNum=true rowBoundsWithCount=true reasonable=true </value> </property> </bean> </array> </property> </bean> <!-- 事務管理 --> <!-- <bean id="transactionManager" --> <!-- class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> --> <!-- <property name="dataSource" ref="dataSource" /> --> <!-- </bean> --> <!-- MapperScanner配置,Spring 自己主動去搜索mapper裏的對象,並註入。指定markerInterface表示僅僅有繼承SqlMapper接口的接口,才會被掃描映射 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.hvgroup.zhuhai10086.jms.**.mapper" /> <property name="markerInterface" value="com.hvgroup.zhuhai10086.jms.mapper.SqlMapper" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> </bean> <bean id="userTransactionService" class="com.atomikos.icatch.config.UserTransactionServiceImp" init-method="init" destroy-method="shutdownForce"> <constructor-arg> <props> <prop key="com.atomikos.icatch.service">com.atomikos.icatch.standalone.UserTransactionServiceFactory</prop> </props> </constructor-arg> </bean> <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" depends-on="userTransactionService" init-method="init" destroy-method="close" > <property name="forceShutdown" value="false"/> </bean> <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp" > <property name="transactionTimeout" value="300"/> </bean> <!-- 分布式事務 --> <bean id="jtaTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="transactionManager" ref="atomikosTransactionManager"/> <property name="userTransaction" ref="atomikosUserTransaction"/> </bean> <!-- <tx:jta-transaction-manager /> --> <!-- 可通過註解控制事務。也能夠配置攔截器方式配置事務 --> <tx:annotation-driven transaction-manager="jtaTransactionManager" /> </beans>

3、ActiveMQ-XA.xml

<?

xml version="1.0" encoding="GBK"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:amq="http://activemq.apache.org/schema/core" xmlns:jms="http://www.springframework.org/schema/jms" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-4.1.xsd http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core-5.13.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd"> <!-- 重發策略:最多重發次數=maximumRedeliveries --> <amq:redeliveryPolicy id="redeliveryPolicy" maximumRedeliveries="6" /> <!-- 抓取策略 --> <amq:prefetchPolicy id="prefetchPolicy" queuePrefetch="5" topicPrefetch="5" /> <!-- activeMQ連接信息,XA事務 --> <amq:xaConnectionFactory id="jmsXaConnectionFactory" brokerURL="${activemq.brokerURL}" userName="${activemq.username}" password="${activemq.password}" redeliveryPolicy="#redeliveryPolicy" alwaysSessionAsync="false" alwaysSyncSend="true" prefetchPolicy="#prefetchPolicy"/> <bean id="amqConnectionFactory" class="com.atomikos.jms.AtomikosConnectionFactoryBean" init-method="init" destroy-method="close"> <property name="uniqueResourceName" value="XAactiveMQ"/> <property name="xaConnectionFactory" ref="jmsXaConnectionFactory"/> <property name="poolSize" value="100"/> </bean> <!-- ====Producer side start==== --> <!-- 定義JmsTemplate的Queue類型 --> <bean id="jmsQueueTemplate" class="org.springframework.jms.core.JmsTemplate"> <constructor-arg ref="amqConnectionFactory" /> <!-- 非pub/sub模型(公布/訂閱)。即隊列模式 --> <property name="pubSubDomain" value="false" /> </bean> <!-- 定義JmsTemplate的Topic類型 --> <bean id="jmsTopicTemplate" class="org.springframework.jms.core.JmsTemplate"> <constructor-arg ref="amqConnectionFactory" /> <!-- pub/sub模型(公布/訂閱) --> <property name="pubSubDomain" value="true" /> </bean> <!-- ====Producer side end==== --> <!-- ====Consumer side start==== --> <!--這個是sessionAwareQueue目的地(響應消息)--> <amq:queue id="queueReceiver2ResponseDestination" physicalName="test.queue2.response" /> <!-- 定義Queue監聽器(無事務) --> <jms:listener-container destination-type="queue" container-type="default" connection-factory="amqConnectionFactory" error-handler="jmsErrorHandler" acknowledge="auto"> <jms:listener destination="xinge.queue.push" ref="xgMessageReceiver" concurrency="5-100" /> <jms:listener destination="xinge.queue.push.device.multiple" ref="xgMessageReceiverMultiple" concurrency="5-100" /> </jms:listener-container> <!-- 定義Queue監聽器(有事務) --> <jms:listener-container destination-type="queue" container-type="default" connection-factory="amqConnectionFactory" transaction-manager="jtaTransactionManager" error-handler="jmsErrorHandler" acknowledge="transacted"> <jms:listener destination="xinge.queue.push.invokelog" ref="xgMessageReceiverInvokeLog" concurrency="5-100" /> <!-- <jms:listener destination="test.queue" ref="queueReceiver" concurrency="10-100" /> --> <!-- <jms:listener destination="test.queue2" ref="queueReceiver2" concurrency="2-10"/> --> <!-- <jms:listener destination="test.queue2.response" ref="queueReceiver2Response" concurrency="2-10" /> --> </jms:listener-container> <!-- 定義Topic監聽器 --> <!-- <jms:listener-container --> <!-- destination-type="topic" --> <!-- container-type="default" --> <!-- connection-factory="amqConnectionFactory" --> <!-- transaction-manager="jtaTransactionManager" --> <!-- error-handler="jmsErrorHandler" --> <!-- acknowledge="transacted" > --> <!-- <jms:listener destination="test.topic" ref="topicReceiver" /> --> <!-- <jms:listener destination="test.topic" ref="topicReceiver2" /> --> <!-- </jms:listener-container> --> <!-- ====Consumer side end==== --> <bean id="xingeApp" class="com.tencent.xinge.XingeApp"> <constructor-arg index="0" value="2100170086"/><!-- accessId --> <constructor-arg index="1" value="2d257b10220bc3849743ffe0e9bd233a"/><!-- secretKey --> </bean> </beans>

4、spring-mvc.xml

<?

xml version="1.0" encoding="GBK"?

> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"> <!-- 啟用MVC註解 --> <mvc:annotation-driven /> <!-- 靜態資源文件。不會被Spring MVC攔截 --> <mvc:resources location="/resources/" mapping="/resources/**"/> <!-- 指定Sping組件掃描的基本包路徑 --> <context:component-scan base-package="com.hvgroup.zhuhai10086.jms" > <!-- 這裏僅僅掃描Controller,不可反復載入Service --> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- JSP視圖解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> <property name="order" value="1" /> </bean> </beans>

5、config.properties

jdbc.url=jdbc:mysql://localhost:3306/xgmessage?useUnicode=true&amp;characterEncoding=utf-8&amp;relaxAutoCommit=true
jdbc.username=root
jdbc.password=123456

activemq.brokerURL=tcp://localhost:61616
activemq.username=admin
activemq.password=admin

6、web.xml

<?xml version="1.0" encoding="UTF-8"?

> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:web="http://java.sun.com/xml/ns/javaee" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <display-name>zhuhai10086-jms</display-name> <context-param> <param-name>webAppRootKey</param-name> <param-value>zhuhai10086-jms</param-value> </context-param> <!-- Log4j配置 --> <context-param> <param-name>log4jConfigLocation</param-name> <param-value>classpath:log4j/log4j.xml</param-value> </context-param> <!-- 載入log4j配置文件 --> <listener> <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> </listener> <filter> <filter-name>characterEncoding</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring/applicationContext.xml,classpath*:activemq/ActiveMQ-XA.xml </param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>SpringMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>SpringMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>

7、把log4j.xml 也貼出來吧,後續有的同學能用上

<?

xml version="1.0" encoding="UTF-8"?> <!DOCTYPE log4j:configuration PUBLIC "-//LOGGER" "log4j.dtd"> <log4j:configuration debug="true" xmlns:log4j="http://jakarta.apache.org/log4j/"> <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender"> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="[%d][%p][%13F:%L] %m%n" /> </layout> </appender> <appender name="DEBUG" class="org.apache.log4j.DailyRollingFileAppender"> <param name="File" value="${log4j.logfile.path}" /> <param name="Encoding" value="UTF-8" /> <param name="DatePattern" value="‘.‘yyyy-MM-dd" /> <param name="ImmediateFlush" value="true" /> <param name="Append" value="true" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="[%d][%p] %m%n" /> </layout> </appender> <logger name="java.sql.Connection"> <level value="DEBUG" /> <appender-ref ref="DEBUG" /> <appender-ref ref="CONSOLE" /> </logger> <logger name="java.sql.PreparedStatement"> <level value="DEBUG" /> <appender-ref ref="DEBUG" /> <appender-ref ref="CONSOLE" /> </logger> <logger name="java.sql.Statement"> <level value="DEBUG" /> <appender-ref ref="DEBUG" /> <appender-ref ref="CONSOLE" /> </logger> <logger name="java.sql.ResultSet"> <level value="DEBUG" /> <appender-ref ref="DEBUG" /> <appender-ref ref="CONSOLE" /> </logger> <logger name="org.mybatis"> <level value="DEBUG" /> <appender-ref ref="DEBUG" /> <appender-ref ref="CONSOLE" /> </logger> <logger name="org.springframework"> <level value="INFO" /> <appender-ref ref="DEBUG" /> <appender-ref ref="CONSOLE" /> </logger> <logger name="org.apache.ibatis"> <level value="INFO" /> <appender-ref ref="DEBUG" /> <appender-ref ref="CONSOLE" /> </logger> <logger name="org.apache.xbean.spring"> <level value="INFO" /> <appender-ref ref="DEBUG" /> <appender-ref ref="CONSOLE" /> </logger> <logger name="com.atomikos"> <level value="ERROR" /> <appender-ref ref="DEBUG" /> <appender-ref ref="CONSOLE" /> </logger> <logger name="org.apache.activemq"> <level value="INFO" /> <appender-ref ref="DEBUG" /> <appender-ref ref="CONSOLE" /> </logger> <logger name="com.hvgroup"> <level value="DEBUG"/> <appender-ref ref="DEBUG" /> <appender-ref ref="CONSOLE" /> </logger> <!-- Root Logger --> <root> <level value="DEBUG"/> </root> </log4j:configuration>

對須要使用數據庫數據源的方法使用 @Transactional 註解就可以,在配置上,JMS的事務我們已經在配置文件裏指定了。例如以下代碼中指定的 transaction-manager=”jtaTransactionManager”:

    <!-- 定義Queue監聽器(有事務) -->
    <jms:listener-container
        destination-type="queue" 
        container-type="default" 
        connection-factory="amqConnectionFactory" 
        transaction-manager="jtaTransactionManager"
        error-handler="jmsErrorHandler"
        acknowledge="transacted">
        <jms:listener destination="xinge.queue.push.invokelog" ref="xgMessageReceiverInvokeLog"  concurrency="5-100" />
    </jms:listener-container>

最後貼上project代碼的結構圖:
技術分享

聲明:本文是我在項目實際業務開發之前搭建的框架,當中如出現一些敏感字。聲明不涉及版權問題。
貼出的配置。僅供大家學習。

SpringMVC+MyBatis+JMS+JTA(分布式事務)