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

jopen 8年前

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>

最后贴上工程代码的结构图:
工程结构图

声明:本文是我在项目实际业务开发之前搭建的框架,其中如出现一些敏感字,声明不涉及版权问题。
贴出的配置,仅供大家学习。

来自: http://blog.csdn.net/catoop/article/details/50337613