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

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