spring、mybatis实现数据库多数据源和多数据源事务

多数据源

就是在一个程序服务里面,需发连接多个数据库。

动态数据源

在多数据源的基础上,要实现数据源的动态切换。

这两种复杂系统的数据库联接有相关性,也有不同应用场景的区别。如要连接两个不同的业务库,一般会使用多数据源,如要动态切换不同数据源的相同业务库,会选择使用动态数据源。

多数据源事务

要支持多数据源的事务,只能用JTA事务管理(没用过 -_-||),而且应用服务器还不能是Tomcat(一直在用tomcat,不想换-_-!!),头疼了。幸亏后面还有说,有第三方的实现支持JTA事务管理,一是JOTM,一是Atomikos。只要用了其中一个,还能继续用Tomcat。因为名字短,先考虑用JOTM。到官网一看,最后更新日期是2010年。。呃。。转向Atomikos

本地事务

本地事务就是用关系数据库来控制事务,关系数据库通常都具有ACID特性,传统的单体应用通常会将数据全部存储在一个数据库中,会借助关系数据库来完成事务控制。普通本地事务和上面的多数据原事务,都是本地事务的范畴。

分布式事务

在分布式系统中一次操作由多个系统协同完成,这种一次事务操作涉及多个系统通过网络协同完成的过程称为分布式事务。

Seata 是 阿里巴巴2019年开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。当然分布式事务,不是本文讨论的重点内容。

数据库锁和分布式锁

数据库的读和写如果分离,在多线程操作操作时会有线程安全问题。如果只加线程锁,在分布式程序中也是不安全的。这个时候可以使用数据库的悲观锁和乐观锁,也可以使用基于redis、zk等的分布式锁解决。

 

spring 多数据源配置

spring 多数据源配置一般有两种方案(基于多数据源和动态数据源原理):

1、在spring项目启动的时候直接配置两个不同的数据源,不同的sessionFactory。在dao 层根据不同业务自行选择使用哪个数据源的session来操作。

2、配置多个不同的数据源,使用一个sessionFactory,在业务逻辑使用的时候自动切换到不同的数据源,有一个种是在拦截器里面根据不同的业务现切换到不同的datasource;有的会在业务层根据业务来自动切换。但这种方案在多线程并发的时候会出现一些问题,需要使用threadlocal等技术来实现多线程竞争切换数据源的问题。

本问主要介绍的是多数据源场景和分布式事务(Atomikos jta)的配置实现。

 

数据源和事务配置

--applicationContext-db.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd" default-lazy-init="true">
    <description>数据库配置</description>

    <bean id="b2b2cDataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
        <property name="uniqueResourceName" value="b2b2cAtomDS"/>
        <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"/>
        <property name="poolSize" value="5"/>
        <property name="minPoolSize" value="5"/>
        <property name="maxPoolSize" value="30"/>
        <property name="xaProperties">
            <props>
                <prop key="user">root</prop>
                <prop key="password">edians317</prop>
                <prop key="url">jdbc:mysql://192.168.130.22:34211/b2b2c?useUnicode=true&amp;characterEncoding=utf8&amp;pinGlobalTxToPhysicalConnection=true</prop>
            </props>
        </property>
        <property name="maintenanceInterval" value="300" />
        <property name="testQuery" value="select 1"/>
    </bean>


    <bean id="esDataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
        <property name="uniqueResourceName" value="esAtomDS"/>
        <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"/>
        <property name="poolSize" value="5"/>
        <property name="minPoolSize" value="5"/>
        <property name="maxPoolSize" value="30"/>
        <property name="xaProperties">
            <props>
                <prop key="user">root</prop>
                <prop key="password">edians317</prop>
                <prop key="url">jdbc:mysql://192.168.130.22:34211/es?useUnicode=true&amp;characterEncoding=utf8&amp;pinGlobalTxToPhysicalConnection=true</prop>
            </props>
        </property>
        <property name="maintenanceInterval" value="300" />
        <property name="testQuery" value="select 1"/>
    </bean>

    <bean id="welDataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
        <property name="uniqueResourceName" value="welAtomDS"/>
        <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"/>
        <property name="poolSize" value="5"/>
        <property name="minPoolSize" value="5"/>
        <property name="maxPoolSize" value="30"/>
        <property name="xaProperties">
            <props>
                <prop key="user">root</prop>
                <prop key="password">edians317</prop>
                <prop key="url">jdbc:mysql://192.168.130.22:34211/welfare?useUnicode=true&amp;characterEncoding=utf8&amp;pinGlobalTxToPhysicalConnection=true</prop>
            </props>
        </property>
        <property name="maintenanceInterval" value="300" />
        <property name="testQuery" value="select 1"/>
    </bean>

    <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"
          init-method="init" destroy-method="close">
        <property name="forceShutdown" value="true"/>
    </bean>

    <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
        <property name="transactionTimeout" value="300"/>
    </bean>

    <bean id ="jtaTxManager" class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManager" ref="atomikosTransactionManager"/>
        <property name="userTransaction" ref="atomikosUserTransaction"/>
        <!-- 必须设置,否则程序出现异常 JtaTransactionManager does not support custom isolation levels by default -->
        <property name="allowCustomIsolationLevels" value="true"/>
    </bean>
    <!-- 可以同时配置annotation和aop集成,可以参考下面的测试方法 -->
    <tx:annotation-driven transaction-manager="jtaTxManager"></tx:annotation-driven>

    <!-- 配置事务的传播特性 -->
    <tx:advice id="txAdvice" transaction-manager="jtaTxManager">
        <tx:attributes>
            <tx:method name="save*" propagation="REQUIRED" />
            <tx:method name="add*" propagation="REQUIRED" />
            <tx:method name="update*" propagation="REQUIRED" />
            <tx:method name="delete*" propagation="REQUIRED" />
            <tx:method name="*" read-only="true" />
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <aop:pointcut id="servicePointCut" expression="execution(* com.leimingtech.service.es..service.*.*(..))
        || execution(* com.leimingtech.service.module..service.*.*(..))
        || execution(* com.leimingtech.extend.module..service.*.*(..))
        || execution(* com.leimingtech.gencode.service.*.*(..))
        || execution(* com.leimingtech.service.cp..service.*.*(..))
        || execution(* com.leimingtech.service.*.*(..))"/>
        <aop:advisor pointcut-ref="servicePointCut" advice-ref="txAdvice"/>
    </aop:config>
</beans>

mybatis映射文件配置

--applicationContext-sqlmapping.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd"
       default-lazy-init="true">
    <description>Sql Mapping配置</description>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    <bean id="b2b2cSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="b2b2cDataSource" />
        <property name="configLocation" value="classpath:conf/sqlmap-config.xml" />
        <property name="mapperLocations">
            <list>
                <value>classpath*:com/leimingtech/service/module/**/dao/mapper/*Mapper.xml</value>
                <value>classpath*:com/leimingtech/extend/module/**/dao/mapper/*Mapper.xml</value>
                <value>classpath*:mapper/**/*Mapper.xml</value>
            </list>
        </property>
    </bean>


    <bean id="esSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="esDataSource" />
        <property name="configLocation" value="classpath:conf/sqlmap-config.xml" />
        <property name="mapperLocations">
            <list>
                <value>classpath*:com/leimingtech/service/es/**/dao/mapper/*Mapper.xml</value>
            </list>
        </property>
    </bean>

    <!-- JDBC template -->
    <bean id="jdbcTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="b2b2cSessionFactory"/>
    </bean>


    <!-- b2b2c db -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.sikong.**.dao.mapper;
        com.leimingtech.service.module.**.dao.mapper;
        com.leimingtech.extend.module.**.dao.mapper;
        com.leimingtech.gencode.dao.mapper" />
        <property name="annotationClass" value="com.leimingtech.core.orm.mybatis.SqlMapper"/>
        <property name="sqlSessionFactoryBeanName" value="b2b2cSessionFactory"/>
    </bean>
    <!-- es db -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.leimingtech.service.es.**.dao.mapper;" />
        <property name="annotationClass" value="com.leimingtech.core.orm.mybatis.SqlMapper"/>
        <property name="sqlSessionFactoryBeanName" value="esSessionFactory"/>
    </bean>

</beans>

测试服务类

/**
 * description: admin多数据源本地事务demo
 * date: 2020/7/2 14:16
 * author: herongbing2
 * version: 1.0
 */
public interface TransactionService {
    /**
     * description: 单数源注解事务 
     * version: 1.0 <br>
     * date: 2020/7/2 14:26 
     * author: herongbing2
     * No such property: code for class: Script1
     * @return void
     */ 
    void singleDatasource();
    /**
     * description: 多数据源注解事务 
     * version: 1.0 
     * date: 2020/7/3 10:31 
     * author: herongbing2 
     * @param 
     * @return void
     */ 
    void multDatasource();
    /**
     * description: 单数据源aop事务 
     * version: 1.0 
     * date: 2020/7/3 10:31 
     * author: herongbing2 
     * @param 
     * @return void
     */ 
    void saveSingleDataSourceAop();
    /**
     * description: 多数据aop事务
     * version: 1.0 
     * date: 2020/7/3 10:32 
     * author: herongbing2 
     * @param 
     * @return void
     */ 
    void saveMultDatasourceAop();

    /**
    * description: 测试idea注释模板
    * version: 1.0
    * date: 2020/7/2 16:55
    * author: herongbing2
    * @param key
    * @param value
    * @return java.lang.String
    */
    String testTemplate(String key,Object value);
}

junit测试

/**
 * description:  对admin平台的事务(多数据源)实现进行测试
 * version: 1.0
 * date: 2020/7/3 10:26
 * author: herongbing2
 */
@RunWith(SpringJUnit4ClassRunner.class) //使用SpringJUnit4测试类
@ContextConfiguration(locations = { "classpath:context/applicationContext.xml" }) //加载配置文件、
@WebAppConfiguration
public class TransactionTestCase {
    @Autowired
    private MemberService memberService;
    @Autowired
    private TransactionService transactionService;
    @Autowired
    private AdminService adminService;
    @Before
    public void before(){
        System.out.println("before");
    }

    @Test
    @Transactional(value = "jtaTxManager",rollbackFor = Exception.class)
    @Rollback(false) //true时为回滚,spring -test的rollback
    public void testRollBack(){
        /*修改admin表*/
        Admin ma = new Admin();
        ma.setAdminName("testRollBack");
        adminService.save(ma);

    }

    @Test
    public void testSingleDatasource(){
        transactionService.singleDatasource();
        System.out.println("testSingleDatasource exec over!");
    }

    @Test
    public void testMultDatasource(){
        transactionService.multDatasource();
        System.out.println("testMultDatasource exec over!");
    }

    @Test
    public void testSaveSingleDataSourceAop(){
        transactionService.saveSingleDataSourceAop();
        System.out.println("testSaveSingleDataSourceAop exec over!");
    }

    @Test
    public void testSaveMultDatasourceAop() {
        transactionService.saveMultDatasourceAop();
        System.out.println("testSaveMultDatasourceAop exec over!");
    }


    @Ignore
    public void ignore(){
        fail("not yet implemented");
    }

}

注意:

atomikos分布式事务的几个坑

https://www.cnblogs.com/rain-in-sun/p/4322174.html

 

以上是一些学习整理和配置笔记,希望对大家有帮助。有问题欢迎一起探讨……——……。下面是本人公众号,可加微信探讨。

 

聚焦程序猿的世界,探讨技艺,分享资源,侃谈生活……

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值