关闭

[置顶] Spring配置多数据源和JOTM分布式事务解决方案

标签: spring数据库事务多数据源
9586人阅读 评论(26) 收藏 举报
分类:

欢迎入QQ群交流:377597358

因为会有在一个项目中需要操作多个数据库的情况,所以本人在网上查看了多篇别人写的博客,并结合自己的认识改写了一些代码最终得到了下面的多数据源和分布式事务的整体解决方案。篇幅有点长,还请耐心看完。本人知识和技术有限,如有觉得不妥或者错误的地方,欢迎吐槽,砖还请轻拍~

这里建议你创建一个新的web项目测试之后加入到现有项目中哦~
因为这样走的更加的放心和清楚明白~
放心~不会坑你滴~~~~

先上个图吧~
这里写图片描述

第一个姿势:配置数据源

首先我们的第一步是配置两个数据源当然你也可以配置三个四个,只是个例子而已:

    <!-- 数据源A -->
    <bean id="A_DataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbca.driver}" />
        <property name="url" value="${jdbca.url}" />
        <property name="username" value="${jdbca.username}" />
        <property name="password" value="${jdbca.password}" />
    </bean>
    <!-- 数据源B -->
    <bean id="B_DataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbcb.driver}" />
        <property name="url" value="${jdbcb.url}" />
        <property name="username" value="${jdbcb.username}" />
        <property name="password" value="${jdbcb.password}" />
    </bean>

如果你在网上搜了别人的博客可能你会觉得我这里是不是少配置了一个类似下面继承了AbstractRoutingDataSource抽象类的数据源:

<!--
<bean id="dataSource" class="com.sh.ddyc.activity.spring.dataSource.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry key="A_DataSource" value-ref="A_DataSource"/>
                <entry key="B_DataSource" value-ref="B_DataSource"/>
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="A_DataSource"/>
    </bean>
    -->

我在写这篇博客之前也有用过类似网上的方式配置上面的类来动态切换数据源,但是后面在事务处理的时候出现了一点问题,所以我这里暂时不打算讲这个~
保持姿势继续走~

第二个姿势:配置MyBatis和JdbcTemplate

接下来配置MyBatis和JdbcTemplate,当然你只使用一个也可以,只是我这里是两个用来区分:

<!-- jdbcTemplate配置 -->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="B_DataSource"/>
    </bean>

<!-- MyBatis配置 -->
    <bean id="sqlSessionFactory" name="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="A_DataSource"/>
        <property name="configurationProperties">
            <props>
                <prop key="mapUnderscoreToCamelCase">true</prop>
                <prop key="logPrefix">mapper.</prop>
            </props>
        </property>
    </bean>

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.sh.ddyc.activity.data.mapper"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

第三个姿势:给dao层或者mapper层AOP增强用来动态确认数据源

先是创建一个Annotation用来数据源的标记:

package com.sh.ddyc.activity.annotation;

import java.lang.annotation.*;

/**
 * Created by Alvin on 2016/5/29.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String value();
}

然后写一个AOP增强类com.sh.ddyc.activity.spring.dataSource.DataSourceAdvice:

package com.sh.ddyc.activity.spring.dataSource;

import com.sh.ddyc.activity.annotation.DataSource;
import com.sh.ddyc.activity.constant.Constant;
import com.sh.ddyc.activity.spring.SpringContext;
import org.apache.ibatis.mapping.Environment;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.jdbc.core.JdbcTemplate;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * 确定数据源切入
 * Created by Alvin on 2016/5/29.
 */
public class DataSourceAdvice implements MethodBeforeAdvice{

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {

        javax.sql.DataSource dataSource;
        if(method.isAnnotationPresent(DataSource.class)){ //如果用了@DataSource注解标注
            DataSource dataSourceAnn = method.getAnnotation(DataSource.class);
            String key = dataSourceAnn.value();
            dataSource = (javax.sql.DataSource)SpringContext.getBean(key);//获取数据源
        }else{
            //如果没有标注则默认使用A_DataSource的数据源
            dataSource = (javax.sql.DataSource)SpringContext.getBean(Constant.DataSourceKey.A_DATASOURCE); //获取数据源
        }
        //---------------------修改jdbcTemplate和mybatis的数据源-----------------------
        //修改jdbcTemplate的数据源
        JdbcTemplate jdbcTemplate = (JdbcTemplate)SpringContext.getBean(JdbcTemplate.class);
        jdbcTemplate.setDataSource(dataSource);
        //修改MyBatis的数据源
        SqlSessionFactoryBean sqlSessionFactoryBean = (SqlSessionFactoryBean)SpringContext.getBean(SqlSessionFactoryBean.class);
        Environment environment = sqlSessionFactoryBean.getObject().getConfiguration().getEnvironment();
        Field dataSourceField = environment.getClass().getDeclaredField("dataSource");
        dataSourceField.setAccessible(true);//跳过检查
        dataSourceField.set(environment,dataSource);//修改mybatis的数据源
    }
}

上面增强类里面有段代码:Constant.DataSourceKey.A_DATASOURCE 这代码是常量,存的是数据源的id。

还需要配置AOP:

<aop:config>
        <aop:pointcut id="dataSourceMethods" expression="execution(* com.sh.ddyc.activity.data.mapper.*.*(..)) || execution(* com.sh.ddyc.activity.dao.*.*(..))" />

        <aop:advisor advice-ref="dataSourceAdvice" pointcut-ref="dataSourceMethods" />
    </aop:config>
    <bean id="dataSourceAdvice" class="com.sh.ddyc.activity.spring.dataSource.DataSourceAdvice"/>

如上配置,因为我有一个dao包和mapper包,dao是用jdbcTemplate来操作的,mapper是用mybatis。所以上面要对这两个包里面的类的方法进行切入增强。
然后是在你的dao接口的方法或者mapper接口里面的方法上面用之前创建的Annotation进行标注:

    //这里证明是需要用A_DATASOURCE的数据源来操作,当然你这里如果是B_DATASOURCE那么就是用B_DATASOURCE数据源
    @DataSource(Constant.DataSourceKey.A_DATASOURCE)
    int insert(Elect20160526 record);

为什么要用AOP增强呢?因为在调用mybatis的mapper类的方法或者dao的方法对数据库进行操作之前,你需要确认你用哪一个数据源操作,所以需要用AOP对mapper类进行代理前置增强,也就是在执行真正方法之前先执行上面的增强类的before方法来确定用哪一个数据源。

增强类里面的方法的逻辑是(ps:这里的解决方案在网上应该是没有的,可是我花了多少脑力才弄出来的):在对数据库进行操作之前,先根据反射获取被增强方法上面是否用了@DataSource(Constant.DataSourceKey.A_DATASOURCE)注解,如果有标注则根据它的value值获取Spring容器里面的name为value值的DataSource。然后把获取的DataSource重新设置给JdbcTemplate和mybatis,这样就实现了动态切换了。这种方式不需要像其它博客那样实现AbstractRoutingDataSource类然后在创建conn的时候根据key获取指定的dataSource。不是说实现AbstractRoutingDataSource类的方式不好,而是我还没找到能解决这种方式在分布式事务环境中出现的问题,除非你不要事务的管理裸奔,当然我也不是说这种方式没有解决方案,只是我暂时还没找到而已!还有,你可能会认为在一个service里面可能会调用多个dao层或mapper层的方法对数据库操作,是不是会创建多次conn从而降低效率或者说浪费连接池的连接呢?其实不然,Spring里面获取数据库连接是通过DataSourceUtils来获取的,DataSourceUtils获取数据库连接的时候会判断当前事务中是否包含了指定数据源的连接,如果包含则直接返回,不包含则创建并注册到当前事务中供后面需要这个数据源连接的时候直接取用。如下代码就是DataSourceUtils获取数据连接的源代码:
这里写图片描述

到这里你应该可以在没有事务的情况下测试一下是否能通过~

第四个姿势:加入JOTM分布式事务管理

copy百度百科的介绍如下:
什么是JOTM?
它提供了 JAVA 应用程序的事务支持,而且与 JTA( JAVA 事务 API)兼容。您可以在JOTM home page了解到更多的详细信息。
在 TOMCAT(或其它 Servlet 容器)整合了 JOTM 后,JSP 和 servlet 的开发者们就可以获得事务的优势轻而易举的创建更多
健壮的 web 应用程序。
为什么使用JOTM?
JOTM 提供了以下性能,有助于增强 WEB 应用程序。
1.完全分布式事务支持.如果数据层、业务层、表示层运行在不同的 JVM 上,则有可能有一个全程的事务跨度这些JVM,事务的内容在 RMI/JRMP 和 RMI/IIOP 上传播。
2.整合 JDBC。使用的 XAPool例子就是一个 XA-兼容的 JDBC 连接池,可以与数据库相互操作。XAPool 类似于 Jakarta DBCP,只是增加了 XA-兼容的特征,如果要结合 JDBC 使用 JTA 事务就必须遵从这个特征。
3.整合 JMS。JOTM 可以结合 JORAM,由ObjectWeb 协会开发的“JMS 提供者”提供了事务的 JMS 消息。你可以得到出现在 servlet中同一事务的 JMS 消息发送件和更新的数据库。
4.WEB 服务事务。JOTM 提供了BTP(Business Transaction Protocol)、JOTM-BTP接口,它们用于在 WEB 服务中增加事务行为。
所有这些功能的样例和文档都可以在 JOTM 的档案和网站上找到。

OK,象征性的copy了一段网上的介绍……
简单来说JOTM就是一个分布式事物的解决方案,网上介绍说还有atomikos也可以,但我没用过~

好了,保持姿势继续走~

现在需要导入JOTM所需的jar包,maven如下:

        <!-- 分布式事务 -->
        <dependency>
            <groupId>jotm</groupId>
            <artifactId>jotm</artifactId>
            <version>2.0.10</version>
        </dependency>
        <dependency>
            <groupId>org.ow2.jotm</groupId>
            <artifactId>jotm-core</artifactId>
            <version>2.3.1-M1</version>
        </dependency>
        <dependency>
            <groupId>org.ow2.jotm</groupId>
            <artifactId>jotm-datasource</artifactId>
            <version>2.3.1-M1</version>
        </dependency>
        <dependency>
            <groupId>org.ow2.jotm</groupId>
            <artifactId>jotm-standalone</artifactId>
            <version>2.3.1-M1</version>
        </dependency>
        <dependency>
            <groupId>javax.resource</groupId>
            <artifactId>javax.resource-api</artifactId>
            <version>1.7</version>
        </dependency>
        <dependency>
            <groupId>com.experlog</groupId>
            <artifactId>xapool</artifactId>
            <version>1.6-beta</version>
        </dependency>

如果不是maven我相信你也会看了上面的配置也知道需要导入什么jar包吧…
jotm-2.0.10.jar
jotm-core-2.3.1-M1.jar
jotm-datasource-2.3.1-M1.jar
jotm-standalone-2.3.1-M1.jar
javax.resource-api-1.7.jar
xapool-1.6-beta.jar
然后是配置JOTM如下:

    <!-- ①这里是第一个要注意的地方!!!!------------------------------------- ↓-->
    <bean id="jotm" class="com.sh.ddyc.activity.spring.dataSource.SysJotmFactoryBean">
        <property name="defaultTimeout" value="500000"/>
    </bean>
    <!-- ①这里是第一个要注意的地方!!!!------------------------------------- ↑-->

    <!-- ②这里是第二个要注意的地方!!!!------------------------------------- ↓-->
    <!-- 数据源A -->
    <bean id="A_DataSource" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource" destroy-method="shutdown">
        <property name="dataSource">
            <bean class="org.enhydra.jdbc.standard.StandardXADataSource" destroy-method="shutdown">
                <property name="transactionManager" ref="jotm"/>
                <property name="driverName" value="${jdbca.driver}"/>
                <property name="url" value="${jdbca.url}"/>
            </bean>
        </property>
        <property name="user" value="${jdbca.username}"/>
        <property name="password" value="${jdbca.password}"/>
    </bean>
    <!-- 数据源B -->
    <bean id="B_DataSource" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource" destroy-method="shutdown">
        <property name="dataSource">
            <bean class="org.enhydra.jdbc.standard.StandardXADataSource" destroy-method="shutdown">
                <property name="transactionManager" ref="jotm"/>
                <property name="driverName" value="${jdbcb.driver}"/>
                <property name="url" value="${jdbcb.url}"/>
            </bean>
        </property>
        <property name="user" value="${jdbcb.username}"/>
        <property name="password" value="${jdbcb.password}"/>
    </bean>
    <!-- ②这里是第二个要注意的地方!!!!------------------------------------- ↑-->

    <!-- JTA事务管理器 -->
    <bean id="jtaTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="userTransaction" ref="jotm" />
    </bean>

    <!-- 事务注入 和 mapper、dao层的方法增强 -->
    <aop:config>
        <aop:pointcut id="productServiceMethods" expression="execution(* com.sh.ddyc.activity.service.*.*(..))" />
        <aop:pointcut id="dataSourceMethods" expression="execution(* com.sh.ddyc.activity.data.mapper.*.*(..)) || execution(* com.sh.ddyc.activity.dao.*.*(..))" />

        <aop:advisor advice-ref="txAdvice" pointcut-ref="productServiceMethods" />
        <aop:advisor advice-ref="dataSourceAdvice" pointcut-ref="dataSourceMethods" />
    </aop:config>
    <bean id="dataSourceAdvice" class="com.sh.ddyc.activity.spring.dataSource.DataSourceAdvice"/>

    <tx:advice id="txAdvice" transaction-manager="jtaTransactionManager">
        <tx:attributes>
            <tx:method name="get*" propagation="REQUIRED"/>
            <tx:method name="insert*" propagation="REQUIRED"  />
            <tx:method name="getRechargeParam" propagation="REQUIRED"  />
            <tx:method name="add*" propagation="REQUIRED"  />
            <tx:method name="find*" propagation="REQUIRED" read-only="true" />
            <tx:method name="page*" propagation="REQUIRED" read-only="true" />
            <tx:method name="*" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>

上面用注释标注了需要注意的地方。
①处:在Spring2.5的版本Spring是有org.springframework.transaction.jta.JotmFactoryBean这个类来整合JOTM的,但是Spring3.0以后就没有了,所以需要下载Spring2.5的源码包去copy这个类到你的项目中,然后像上面①处配置就好了。
②处:把原来的配置的数据源替换成②处的配置。
剩余的就是配置事务的东西了,应该看得懂,不多说了…

至此,可以测测你们的项目了….

如果你们测试成功,麻烦在评论里面告知一下...如果没有成功,也麻烦在评论告知一下...因为这样我才能知道这个博客是否对你们有帮助...

第五个姿势:欢迎吐槽

麻烦你有什么疑问或者建议能在下面评论一下,我天天都会在csdn可以回复~
希望共同进步~

转载请标明出处:http://blog.csdn.net/u013632755/article/details/51557956

1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:121738次
    • 积分:1434
    • 等级:
    • 排名:千里之外
    • 原创:46篇
    • 转载:21篇
    • 译文:0篇
    • 评论:46条
    最新评论