spring cloud脚手架项目(四)mybaits+druid多数据+atomikos分布式事务

前言

本篇是我的spring cloud脚手架项目的第四篇。上篇讲的是feign接口。我们的spring boot项目已经可以做一个最基本的接口返回和微服务提供了。本篇讲的是所有项目常用的数据库相关配置的接入
参考博客:
https://blog.csdn.net/u012702547/article/details/103029910
https://blog.csdn.net/m0_37809146/article/details/86673372
https://blog.csdn.net/ypp91zr/article/details/83988919

代码

maven

            <!--mybatis starter-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.2</version>
            </dependency>
            <!--引入mysql驱动-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>6.0.6</version>
            </dependency>
            <!--druid 依赖-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.1.10</version>
            </dependency>
            <!--atomikos依赖 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jta-atomikos</artifactId>
                <version>2.0.3.RELEASE</version>
            </dependency>
            <!-- pagehelper分页插件依赖 -->
            <dependency>
                <groupId>com.github.pagehelper</groupId>
                <artifactId>pagehelper-spring-boot-starter</artifactId>
                <version>1.2.5</version>
            </dependency>

DataSourceConfig
因为引入JIT分布式事务了。所以在多数据源上使用XA两段式提交了。所以drudi的DataSource也要切换为对应的DruidXADataSource

import com.alibaba.druid.pool.xa.DruidXADataSource;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties("spring.datasource.druid.db1")
    public DataSource druidDataSourceOne() {
        return new DruidXADataSource();
    }
    @Bean
    @ConfigurationProperties("spring.datasource.druid.db2")
    public DataSource druidDataSourceTwo() {
        return new DruidXADataSource();
    }

    @Bean
    public DataSource dataSourceOne(DataSource druidDataSourceOne) {
        AtomikosDataSourceBean sourceBean = new AtomikosDataSourceBean();
        sourceBean.setXaDataSource((DruidXADataSource) druidDataSourceOne);
        // 必须为数据源指定唯一标识
        sourceBean.setUniqueResourceName("db1");
        return sourceBean;
    }

    @Bean
    public DataSource dataSourceTwo(DataSource druidDataSourceTwo) {
        AtomikosDataSourceBean sourceBean = new AtomikosDataSourceBean();
        sourceBean.setXaDataSource((DruidXADataSource) druidDataSourceTwo);
        sourceBean.setUniqueResourceName("db2");
        return sourceBean;
    }
}

@MapperScan(basePackages="")中就是对应数据源应mapper文件应该存放的位置
MyBatisConfigOne

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.annotation.Resource;
import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = "com.chen.dao.mapper1",sqlSessionFactoryRef = "sqlSessionFactory1",sqlSessionTemplateRef = "sqlSessionTemplate1")
public class MyBatisConfigOne {
    @Resource(name = "dataSourceOne")
    DataSource dsOne;

    @Bean
    SqlSessionFactory sqlSessionFactory1() {
        SqlSessionFactory sessionFactory = null;
        try {
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setDataSource(dsOne);
            sessionFactory = bean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sessionFactory;
    }
    @Bean
    SqlSessionTemplate sqlSessionTemplate1() {
        return new SqlSessionTemplate(sqlSessionFactory1());
    }
    //去除单个事务管理
//    @Bean
//    public DataSourceTransactionManager transactionManager1() {
//        return new DataSourceTransactionManager(dsOne);
//    }
}

MyBatisConfigTwo

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.annotation.Resource;
import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = "com.chen.dao.mapper2",sqlSessionFactoryRef = "sqlSessionFactory2",sqlSessionTemplateRef = "sqlSessionTemplate2")
public class MyBatisConfigTwo {
    @Resource(name = "dataSourceTwo")
    DataSource dsTwo;

    @Bean
    SqlSessionFactory sqlSessionFactory2() {
        SqlSessionFactory sessionFactory = null;
        try {
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setDataSource(dsTwo);
            sessionFactory = bean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sessionFactory;
    }
    @Bean
    SqlSessionTemplate sqlSessionTemplate2() {
        return new SqlSessionTemplate(sqlSessionFactory2());
    }
    //去除单个事务管理
//    @Bean
//    public DataSourceTransactionManager transactionManager2() {
//        return new DataSourceTransactionManager(dsTwo);
//    }
}

dao模块添加maven特殊配置,让mapper.xml文件也会加入到jar包中去

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

yml中db相关配置

spring:
  application:
    name: SERVICE-A
  #数据库配置
  datasource:
    druid:
      db1:
        url: jdbc:mysql://127.0.0.1:3306/chen_data?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
        username: root
        password: root
        driver-class-name: com.mysql.cj.jdbc.Driver

        # 初始化时建立物理连接的个数。初始化发生在显示调用 init 方法,或者第一次 getConnection 时
        initialSize: 5
        # 最小连接池数量
        minIdle: 5
        # 最大连接池数量
        maxActive: 10
        # 获取连接时最大等待时间,单位毫秒。配置了 maxWait 之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置 useUnfairLock 属性为 true 使用非公平锁。
        maxWait: 60000
        # Destroy 线程会检测连接的间隔时间,如果连接空闲时间大于等于 minEvictableIdleTimeMillis 则关闭物理连接。
        timeBetweenEvictionRunsMillis: 60000
        # 连接保持空闲而不被驱逐的最小时间
        minEvictableIdleTimeMillis: 300000
        # 用来检测连接是否有效的 sql 因数据库方言而异, 例如 oracle 应该写成 SELECT 1 FROM DUAL
        validationQuery: SELECT 1
        # 建议配置为 true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于 timeBetweenEvictionRunsMillis,执行 validationQuery 检测连接是否有效。
        testWhileIdle: true
        # 申请连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。
        testOnBorrow: false
        # 归还连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。
        testOnReturn: false
        # 是否自动回收超时连接
        removeAbandoned: true
        # 超时时间 (以秒数为单位)
        remove-abandoned-timeout: 1800

      db2:
        url: jdbc:mysql://127.0.0.1:3306/chen_data2?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
        username: root
        password: root
        driver-class-name: com.mysql.cj.jdbc.Driver

        # 初始化时建立物理连接的个数。初始化发生在显示调用 init 方法,或者第一次 getConnection 时
        initialSize: 6
        # 最小连接池数量
        minIdle: 6
        # 最大连接池数量
        maxActive: 10
        # 获取连接时最大等待时间,单位毫秒。配置了 maxWait 之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置 useUnfairLock 属性为 true 使用非公平锁。
        maxWait: 60000
        # Destroy 线程会检测连接的间隔时间,如果连接空闲时间大于等于 minEvictableIdleTimeMillis 则关闭物理连接。
        timeBetweenEvictionRunsMillis: 60000
        # 连接保持空闲而不被驱逐的最小时间
        minEvictableIdleTimeMillis: 300000
        # 用来检测连接是否有效的 sql 因数据库方言而异, 例如 oracle 应该写成 SELECT 1 FROM DUAL
        validationQuery: SELECT 1
        # 建议配置为 true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于 timeBetweenEvictionRunsMillis,执行 validationQuery 检测连接是否有效。
        testWhileIdle: true
        # 申请连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。
        testOnBorrow: false
        # 归还连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。
        testOnReturn: false
        # 是否自动回收超时连接
        removeAbandoned: true
        # 超时时间 (以秒数为单位)
        remove-abandoned-timeout: 1800

      # WebStatFilter 用于采集 web-jdbc 关联监控的数据。
      web-stat-filter:
        # 是否开启 WebStatFilter 默认是 true
        enabled: true
        # 需要拦截的 url
        url-pattern: /*
        # 排除静态资源的请求
        exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"

      # Druid 内置提供了一个 StatViewServlet 用于展示 Druid 的统计信息。
      stat-view-servlet:
        #是否启用 StatViewServlet 默认值 true
        enabled: true
        # 需要拦截的 url
        url-pattern: /druid/*
        # 允许清空统计数据
        reset-enable: true
        login-username: druid
        login-password: druid

网上还有一些是关于AOP切面自动切换数据源的。我个人认为没有啥必要,在MyBatisConfig中配置多个数据源,然后分别放到不同的mapper中做区分就可以了。这样也比较简单易懂。
然后就是写一个controller,一个manager,里面做一个查询,开启pagehelper,写一个多数据源插入,抛出问题,查看回滚
manager代码

	#pagehelper的测试方法,注意不要在多个mapper中同时分页,会有问题
    public String getOneByPage() {
        ChenTestExample example = new ChenTestExample();
        ChenTestExample.Criteria criteria = example.createCriteria();
        #开启分页
        PageHelper.startPage(1, 5);
        List<ChenTest> list = chenTestMapper.selectByExample(example);
        PageInfo<ChenTest> pageInfo = new PageInfo<>(list);
        #结束分页,获得分页结果
        log.info("list1:{}", JSON.toJSONString(list));
        log.info("page1:{}",JSON.toJSONString(pageInfo));
        return "byPage1";
    }
	#添加注解即可开启XA分布式事务,最后我写了一个已知错误可以查看是否回滚数据
    @Transactional(propagation = Propagation.REQUIRED)
    public String insertOne() {
        log.info("manager#insertOne");
        ChenTest chenTest = new ChenTest();
        chenTest.setGmtCreate(LocalDateTime.now());
        chenTest.setGmtModified(LocalDateTime.now());
        chenTest.setName("chentest1");
        chenTest.setNum(5);
        int result = chenTestMapper.insertSelective(chenTest);
        ChenTest2 chenTest2 = new ChenTest2();
        chenTest2.setGmtCreate(LocalDateTime.now());
        chenTest2.setGmtModified(LocalDateTime.now());
        chenTest2.setName("chentest2");
        chenTest2.setNum(6);
        int result2 = chenTest2Mapper.insertSelective(chenTest2);
		#故意写一个异常,查看是否2个数据源都回滚了
        int error = 0;
        int b = 1/error;
        return "1";
    }

然后启动项目在127.0.0.1/druid还可以看到druid相关监控信息
调用方法插入多个数据源时报错会回滚多个数据源,pagehelper也正常分页了。这里一下,pagehepler只要在spring boot中添加启动maven即可自动启动,如果有其他配置需要配置的话可以在yml中再写

结尾

这样我们的项目的数据库相关的链接就做好了。本来还想再写一个sharding相关的,种种原因(其实就是mysql不够多)也就放弃了。
github地址:https://github.com/alex9567/SpringCloudScaffold

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章