SpringMVC+MyBatis 相信已经是现在企业开发中常用技术了。
因为一些需求,我们需要集成JMS(我使用的是ActiveMQ),大家应该都知道,MQ也可以认为是一个数据源,数据也是数据源。这种情况下,如果我们在一个方法内操作JMS和数据库,我们就需要保证这个方法执行需要满足原子性。
这也就意味这一个问题,我们要多个数据源在同一个事务中。这里不枚举市面上的所有解决方案,其实atomikos JTA 是一个比较不错分布式事务管理器。
当然如果没有使用到JMS,在需要多数据源(也就是需要连接多个数据库)的情况同样适用。
下面将项目的主要配置贴出来共享:
1、applicationContext.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
<?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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
<?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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
<?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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
<?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
1 2 3 4 5 6 7 |
jdbc.url=jdbc:mysql://localhost:3306/xgmessage?useUnicode=true&characterEncoding=utf-8&relaxAutoCommit=true jdbc.username=root jdbc.password=123456 activemq.brokerURL=tcp://localhost:61616 activemq.username=admin activemq.password=admin |
6、web.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
<?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 也贴出来吧,兴许有的同学能用上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
<?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”:
1 2 3 4 5 6 7 8 9 10 |
<!-- 定义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> |
最后贴上工程代码的结构图:
声明:本文是我在项目实际业务开发之前搭建的框架,其中如出现一些敏感字,声明不涉及版权问题。
贴出的配置,仅供大家学习。