SpringBoot整合Mybatis-plus(二) 多數據源Druid監控,Atomikos處理事務,跨庫連表查詢

SpringBoot整合Mybatis-plus(二) 多數據源Druid監控,Atomikos處理事務,跨庫連表查詢

在上一章中文,我使用springboot工程整合了mp,以及mp基礎使用,代碼生成器使用,Druid 數據監控等,但僅限於單數據源 請戳SpringBoot整合Mybatis-plus(一)基本使用與自定義模板代碼生成器

因爲很多時候,在開發中,並不會只有一個數據庫,在保存一條數據的時候,可能需要向幾個數據庫保存,那麼當發生異常時候,就會面臨事務問題了,需要將保存的數據進行回滾,所以,本文開始講解,整合 Mp 多數據源下的druid監控以及事務處理

在這裏插入圖片描述

一.添加新的依賴

        <!--分佈式事務-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jta-atomikos</artifactId>
        </dependency>
        <!--由於我mysql重裝了,使用8版本驅動有點問題,所以我回退到了5版本-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

二.多數據源情況下的YML配置

spring:
  datasource:
    type: com.alibaba.druid.pool.xa.DruidXADataSource
    druid:
      #第一個數據源
      one:
        name: oneDataSource
        url: jdbc:mysql://127.0.0.1:3306/mybatis-plus?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
        driver-class-name: com.mysql.jdbc.Driver
        username: root
        password: root
        # 下面爲連接池的補充設置,應用到上面所有數據源中
        # 初始化大小,最小,最大
        initialSize: 5
        minIdle: 5
        maxActive: 20
        # 配置獲取連接等待超時的時間
        maxWait: 60000
        # 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒
        timeBetweenEvictionRunsMillis: 60000
        # 配置一個連接在池中最小生存的時間,單位是毫秒
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1
        validationQueryTimeout: 10000
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        # 打開PSCache,並且指定每個連接上PSCache的大小
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        filters: stat,wall
        # 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
        # 合併多個DruidDataSource的監控數據
        useGlobalDataSourceStat: true

      #第二個數據源
      two:
        name: twoDataSource
        url: jdbc:mysql://127.0.0.1:3306/mybatis-plus2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
        driver-class-name: com.mysql.jdbc.Driver
        username: root
        password: root
        # 下面爲連接池的補充設置,應用到上面所有數據源中
        # 初始化大小,最小,最大
        initialSize: 5
        minIdle: 5
        maxActive: 20
        # 配置獲取連接等待超時的時間
        maxWait: 60000
        # 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒
        timeBetweenEvictionRunsMillis: 60000
        # 配置一個連接在池中最小生存的時間,單位是毫秒
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1
        validationQueryTimeout: 10000
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        # 打開PSCache,並且指定每個連接上PSCache的大小
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        filters: stat,wall
        # 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
        # 合併多個DruidDataSource的監控數據
        useGlobalDataSourceStat: true
      #第三個數據源
      three:
        name: threeDataSource
        url: jdbc:mysql://127.0.0.1:3306/mybatis-plus3?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
        driver-class-name: com.mysql.jdbc.Driver
        username: root
        password: root
        # 下面爲連接池的補充設置,應用到上面所有數據源中
        # 初始化大小,最小,最大
        initialSize: 5
        minIdle: 5
        maxActive: 20
        # 配置獲取連接等待超時的時間
        maxWait: 60000
        # 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒
        timeBetweenEvictionRunsMillis: 60000
        # 配置一個連接在池中最小生存的時間,單位是毫秒
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1
        validationQueryTimeout: 10000
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        # 打開PSCache,並且指定每個連接上PSCache的大小
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        filters: stat,wall
        # 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
        # 合併多個DruidDataSource的監控數據
        useGlobalDataSourceStat: true
  jta:
    atomikos:
      properties:
        log-base-dir: tx-logs
    transaction-manager-id: txManager    #默認取計算機的IP地址 需保證生產環境值唯一

三.配置Bean形式讀取數據庫信息

由於配置了one two three 三個數據源 已經是改變了springBoot 對Mysql 連接讀取的配置路徑,所以呢,我們需要自定義數據源的位置,在啓動項目時才能成功讀取數據庫相關信息

無論使用Mybatis 還是mp 在訪問數據庫時都需要 數據源 , SqlSessionFactory,SqlSessionTemplate

首先需要從配置文件中,獲取到數據源信息,以及Druid需要監控的信息

讀取我們配置文件中數據庫路徑 生成對象並配置成Bean 交由spring管理

附上我完整代碼

DruidConfig
package com.leilei.config;

import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;

import java.util.Properties;

/**
 * 多數據源和Druid配置
 *
 * @author leilei
 */
@Configuration
public class DruidConfig {

    /**
     * 數據源1配置   使用AtomikosDataSourceBean 支持多數據源事務
     *
     * @param env
     * @return Primary 指定主庫  (必須指定一個主庫 否則會報錯)
     */
    @Bean(name = "MybatisPlusOneDataSource")
    @Primary
    @Autowired
    public AtomikosDataSourceBean oneDataSource(Environment env) {
        AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
        Properties prop = build(env, "spring.datasource.druid.one.");
        ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
        ds.setUniqueResourceName("oneDataSource");
        ds.setPoolSize(5);
        ds.setXaProperties(prop);
        return ds;
    }

    /**
     * 數據源2配置  使用AtomikosDataSourceBean 支持多數據源事務
     *
     * @param env
     * @return
     */
    @Autowired
    @Bean(name = "MybatisPlusTwoDataSource")
    public AtomikosDataSourceBean twoDataSource(Environment env) {
        AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
        Properties prop = build(env, "spring.datasource.druid.two.");
        ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
        ds.setUniqueResourceName("twoDataSource");
        ds.setPoolSize(5);
        ds.setXaProperties(prop);
        return ds;
    }

    @Autowired
    @Bean(name = "MybatisPlusThreeDataSource")
    public AtomikosDataSourceBean threeDataSource(Environment env) {
        AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
        Properties prop = build(env, "spring.datasource.druid.three.");
        ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
        ds.setUniqueResourceName("threeDataSource");
        ds.setPoolSize(5);
        ds.setXaProperties(prop);
        return ds;
    }

//    /**
//     * 注入事物管理器
//     * @return
//     */
//    @Bean(name = "leijta")
//    public JtaTransactionManager regTransactionManager () {
//        UserTransactionManager userTransactionManager = new UserTransactionManager();
//        UserTransaction userTransaction = new UserTransactionImp();
//        return new JtaTransactionManager(userTransaction, userTransactionManager);
//    }

    /**
     * 從配置文件中加載數據源信息
     *
     * @param env
     * @param prefix
     * @return
     */
    private Properties build(Environment env, String prefix) {
        Properties prop = new Properties();
        prop.put("url", env.getProperty(prefix + "url"));
        prop.put("username", env.getProperty(prefix + "username"));
        prop.put("password", env.getProperty(prefix + "password"));
        prop.put("driverClassName", env.getProperty(prefix + "driverClassName", ""));
        prop.put("initialSize", env.getProperty(prefix + "initialSize", Integer.class));
        prop.put("maxActive", env.getProperty(prefix + "maxActive", Integer.class));
        prop.put("minIdle", env.getProperty(prefix + "minIdle", Integer.class));
        prop.put("maxWait", env.getProperty(prefix + "maxWait", Integer.class));
        prop.put("poolPreparedStatements", env.getProperty(prefix + "poolPreparedStatements", Boolean.class));
        prop.put("maxPoolPreparedStatementPerConnectionSize",
                env.getProperty(prefix + "maxPoolPreparedStatementPerConnectionSize", Integer.class));
        prop.put("maxPoolPreparedStatementPerConnectionSize",
                env.getProperty(prefix + "maxPoolPreparedStatementPerConnectionSize", Integer.class));
        prop.put("validationQuery", env.getProperty(prefix + "validationQuery"));
        prop.put("validationQueryTimeout", env.getProperty(prefix + "validationQueryTimeout", Integer.class));
        prop.put("testOnBorrow", env.getProperty(prefix + "testOnBorrow", Boolean.class));
        prop.put("testOnReturn", env.getProperty(prefix + "testOnReturn", Boolean.class));
        prop.put("testWhileIdle", env.getProperty(prefix + "testWhileIdle", Boolean.class));
        prop.put("timeBetweenEvictionRunsMillis",
                env.getProperty(prefix + "timeBetweenEvictionRunsMillis", Integer.class));
        prop.put("minEvictableIdleTimeMillis", env.getProperty(prefix + "minEvictableIdleTimeMillis", Integer.class));
        prop.put("filters", env.getProperty(prefix + "filters"));
        return prop;
    }

    /**
     * druid訪問配置
     *
     * @return
     */
    @Bean
    public ServletRegistrationBean druidServlet() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        //控制檯管理用戶,加入下面2行 進入druid後臺就需要登錄
        servletRegistrationBean.addInitParameter("loginUsername", "leilei");
        servletRegistrationBean.addInitParameter("loginPassword", "123456");
        return servletRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        filterRegistrationBean.addInitParameter("profileEnable", "true");
        return filterRegistrationBean;
    }

    @Bean
    public StatFilter statFilter() {
        StatFilter statFilter = new StatFilter();
        //slowSqlMillis用來配置SQL慢的標準,執行時間超過slowSqlMillis的就是慢。
        statFilter.setLogSlowSql(true);
        //SQL合併配置
        statFilter.setMergeSql(true);
        //slowSqlMillis的缺省值爲3000,也就是3秒。
        statFilter.setSlowSqlMillis(1000);
        return statFilter;
    }

    @Bean
    public WallFilter wallFilter() {
        WallFilter wallFilter = new WallFilter();
        //允許執行多條SQL
        WallConfig config = new WallConfig();
        config.setMultiStatementAllow(true);
        wallFilter.setConfig(config);
        return wallFilter;
    }
}

在DruidConfig中看到了 每個數據源的Bean 都是 AtomikosDataSourceBean 類型 ,只要數據源是此類型,在多個數據源下,打上Spring自帶的事務註解@Transactional 即可

AtomikosDataSourceBean類型數據源
/**
 * 數據源1配置   使用AtomikosDataSourceBean 支持多數據源事務
 *
 * @param env
 * @return Primary 指定主庫  (必須指定一個主庫 否則會報錯)
 */
@Bean(name = "MybatisPlusOneDataSource")
@Primary
@Autowired
public AtomikosDataSourceBean oneDataSource(Environment env) {
    AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
    Properties prop = build(env, "spring.datasource.druid.one.");
    ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
    ds.setUniqueResourceName("oneDataSource");
    ds.setPoolSize(5);
    ds.setXaProperties(prop);
    return ds;
}
配置每個數據源的sqlsessionfactory sqlSessionTemplate

這裏貼上我的第一個數據源的sqlsessionfactory sqlSessionTemplate,無論還有多少數據源,按照我這個配置即可,改改包路徑,@Qualifier選擇對應數據源 ,改改mpper.xml路徑即可

需注意的是,必須有一個數據源的所有相關(datasource,sqlsessionfactory sqlSessionTemplate)要使用**@Primary**註解指定一個主庫,否則會啓動報錯。

package com.leilei.config;

import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;

/**
 * @author leilei
 */
@Configuration
@MapperScan(basePackages = "com.leilei.mapper.one", sqlSessionFactoryRef = "oneSqlSessionFactory")
public class OneDataSourceConfig {

    @Primary
    @Bean(name = "oneSqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("MybatisPlusOneDataSource") DataSource dataSource) throws Exception {
        //配置myabtisSqlSession
        MybatisSqlSessionFactoryBean sessionFactoryBean = new MybatisSqlSessionFactoryBean();
        // 指明mapper.xml位置(配置文件中指明的xml位置會失效用此方式代替,具體原因未知)
        sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:com/leilei/mapper/one/*/*Mapper.xml"));
        // 指明實體掃描(多個package用逗號或者分號分隔)
        sessionFactoryBean.setTypeAliasesPackage("com.leilei.entity.one");

        MybatisConfiguration mybatisConfiguration = new MybatisConfiguration();
        // mybatisConfiguration.setJdbcTypeForNull(JdbcType.NULL);
        //駝峯
        mybatisConfiguration.setMapUnderscoreToCamelCase(true);
        //是否開啓緩存
        mybatisConfiguration.setCacheEnabled(false);
        //多數據源下分頁模式
        mybatisConfiguration.addInterceptor(new PaginationInterceptor());
        // 配置打印sql語句
        mybatisConfiguration.setLogImpl(StdOutImpl.class);
        sessionFactoryBean.setConfiguration(mybatisConfiguration);
        //數據源注入
        sessionFactoryBean.setDataSource(dataSource);
        return sessionFactoryBean.getObject();
    }

    @Primary
    @Bean(name = "oneSqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("oneSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

其他數據源的sqlsessionfactory sqlSessionTemplate 照着我這個配置即可 ,去掉 @Primary註解 注意Bean 名稱設爲唯一。

其實到這裏 多數據源下的事務管理以及Druid監控已經是整合完畢了

四.本文需注意的點

1.導入atomikos 依賴
2.mysql 驅動爲5 版本
3.包路徑劃分

在使用代碼生成器時,將每個數據源的mapper, entity ,mapper.xml包路徑劃分開來

在這裏插入圖片描述

4.多數據源也需要@Transactional 註解

五.多數據源事務測試

事務測試

一個方法中,調用多個數據源插入數據,並中途製造異常,報錯後,查看數據回滾·

在這裏插入圖片描述

body 爲我封裝的一個對象,因爲本文設計三個對象,我準備向三個數據源一起插入數據測試事務
在這裏插入圖片描述

操作數據庫

    @Override
    @Transactional //數據源配置了AtomikosDataSourceBean 再打上此註解即可做到多數據源事務控制
    public Map<String, Object> insertAto(BodyVo bodyVo) {
        userMapper.insert(bodyVo.getUser());
        userRoleMapper.insert(bodyVo.getUserRole());
        int a = 1 / 0; //製造異常
        roleMapper.insert(bodyVo.getRole());
        HashMap<String, Object> map = new HashMap<>();
        map.put("user", bodyVo.getUser());
        map.put("user_role", bodyVo.getUserRole());
        map.put("role", bodyVo.getRole());
        return map;
    }

未開始測試前,數據庫數據

在這裏插入圖片描述

運行測試結果,可以看到是報了異常

在這裏插入圖片描述

那麼,此時再查看數據數據,刷新後,發現數據並未改變

在這裏插入圖片描述

那麼說明,多數據源下的事務問題已經解決了!!!!!

多數據源下Mp 分頁以及跨表查詢

本文是將一個多對多關係的三張表放在了三個庫中

依User 爲第一查詢視角
在這裏插入圖片描述

在這裏插入圖片描述

本文中usermapper.MoreDatasourceFindAll 是自定義的方法,爲了有分頁效果必須構造一個Page對象,將Page對象與查詢條件一起作爲參數傳到UserMapper.interface中,再有mapper.xml操作數據庫

    IPage<User> MoreDatasourceFindAll(Page<User> page, @Param("id") Long id);
  <!-- 通用查詢映射結果 -->
    <resultMap id="BaseResultMap" type="com.leilei.entity.one.User">
        <id column="id" property="id"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <collection property="roleList" javaType="list" ofType="com.leilei.entity.three.Role">
            <id column="roleid" property="id"/>
            <result column="rolename" property="roleName"/>
        </collection>
    </resultMap>
    <select id="MoreDatasourceFindAll" resultMap="BaseResultMap">
        SELECT mpu.*,mp3r.id roleid,mp3r.role_name rolename
        FROM `mybatis-plus`.`user` mpu ,`
        mybatis-plus2`.user_role mp2ur,
        `mybatis-plus3`.role mp3r
        <where>
            and mp2ur.user_id=mpu.id AND mp2ur.role_id=mp3r.id
            <if test="id!=null and id !='' ">
                AND mpu.id=#{id}
            </if>
        </where>
    </select>

在這裏插入圖片描述
查看Druid監控
發現三個數據源均在此排列
在這裏插入圖片描述
Url監控
在這裏插入圖片描述
結語
到這裏,本次項目整合就結束了,附上我的項目源碼
SpringBoot整合Mybatis-plus(二) 多數據源Druid監控,Atomikos處理事務,跨庫連表查詢

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