【springboot專題】二十 springboot中如何配置多數據源

程序猿學社的GitHub,歡迎Star
https://github.com/ITfqyd/cxyxs
本文已記錄到github,形成對應專題。

前言

隨着業務的不斷增加,我們的系統會越來越龐大,因此,一個項目中使用多個數據源,是我們可能會遇到的問題。本來就來,看看springboot多數據源是怎麼搭建的。

多數據源,如何劃分?

分爲兩種。

  • 分包,分包主要是根據業務劃分。
  • 註解方式,實際上就是通過aop進行攔截,不同的註解裏面的值,指向不同的數據源。

環境

爲了減少寫sql,更好的提高開發效率,引入mybatis-plus

名稱 作用
springboot2.0 簡化配置,快速構建一個項目
mybatis-plus 簡化sql的一個插件,可以實現通過操作實體類,操作數據庫的表。
mysql 數據庫(兩個庫pro和pro1)

單個數據源

爲什麼要說一說搭建單個數據源,社長,多數據源勒?來自某某社友的吐槽,你個糟老頭子,壞的很。
先搭建單個數據源也是爲了簡化難度。先保證單個數據源沒有問題後,我們再來挑戰多個數據源,不要,還沒頭學會走路,就想着跑,還是得一步一個腳印。才能纔不會摔倒。也不會遇到各種各樣的報錯。

  • 小技巧:可以把一個複雜的問題,拆分成多個小的問題,一個個的解決。

sql腳本

創建一個數據庫pro

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for emp
-- ----------------------------
DROP TABLE IF EXISTS `emp`;
CREATE TABLE `emp`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `age` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of emp
-- ----------------------------
INSERT INTO `emp` VALUES (1, '后羿', 21);
INSERT INTO `emp` VALUES (2, '韓信', 40);
INSERT INTO `emp` VALUES (3, '蘭陵王', 11);

SET FOREIGN_KEY_CHECKS = 1;

搭建項目

創建一個springboot項目

可以發現有一個test1包和test2包,我們先用test1包實現調用數據庫成功。

pom.xml依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.cloudtech</groupId>
    <artifactId>moredatasource</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>moredatasource</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 增加thymeleaf座標  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!--簡化實體類-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- 熱部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

        <!--swagger2-->
        <dependency>
            <groupId>com.spring4all</groupId>
            <artifactId>spring-boot-starter-swagger</artifactId>
            <version>1.5.1.RELEASE</version>
        </dependency>

        <!--MP插件,簡化sql操作-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.0</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.18</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.6</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
  • springboot是2.0以上,mp的版本是3.0以上,如果不一樣,可能會有驚喜。

application.yml

server:
    port: 8888

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://localhost:3306/pro?useUnicode=true&characterEncoding=utf8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8
    username: root
    password: root

mybatis-plus:
  ##mapper.xml文件存放的路徑
  mapper-locations: classpath*:/com/cxyxs/moredatasource/test1/mapper/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  • 這個也沒有什麼好說的,log-impl是打印sql日誌。
@Data
public class Emp {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String name;
    private Integer age;
}
  • 可以發現我們的代碼沒有set get,這是因爲我們引入lombok插件
  • @TableId(type = IdType.AUTO) 數據庫ID自增,國產的源碼看起來就是舒服,中文註釋,對我這種英文不好的,很好使。

dao

@Mapper
@Repository
public interface EmpMapper1 extends BaseMapper<Emp> {

}

在這裏插入圖片描述

  • @Repository 爲了解決報紅錯的,但是,這個紅色的波浪線,不會影響代碼的運行,但是,對於我這種有強迫症的人,看起來十分的不舒服。

controller

package com.cxyxs.moredatasource.controller;

import com.cxyxs.moredatasource.entity.Emp;
import com.cxyxs.moredatasource.test1.dao.EmpMapper1;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;


/**
 * Description:
 * Author: 程序猿學社
 * Date:  2020/3/7 12:15
 * Modified By:
 */
@RestController
public class TestController {
    @Autowired
    private EmpMapper1 empMapper;

    @GetMapping("/getKing")
    public List getKing(){
        List<Emp> emps = empMapper.selectList(null);
        return emps;
    };
}
  • 操作數據庫用到mybatis-plus的一些知識,可以看看springboot集成mybatis-plus
  • @RestController註解,實際上就是兩個註解的效果。我們看一看的源碼就知道是怎麼一回事。
  • @GetMapping 使用的RESTful風格,等價於@RequestMapping(method = RequestMethod.GET)

springboot入口

package com.cxyxs.moredatasource;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages = {"com.cxyxs"})
@MapperScan("com.cxyxs.moredatasource.test1.dao")
public class MoredatasourceApplication {

    public static void main(String[] args) {
        SpringApplication.run(MoredatasourceApplication.class, args);
    }

}
  • @SpringBootApplication實際上就是三個註解的組合
  • @MapperScan 掃描的Mapper類所在包的路徑,也就是我們所謂的dao包。
  • ComponentScan 自動掃描當前包及子包下所有類,得理解這句話

訪問網站

輸入http://localhost:8080/getKing

  • 有不少社友在問,社長,社長,返回的結果集,你界面好直觀,數據結構很清楚。而我的都堆積在一起,閱讀起來很累。那是因爲我使用的谷歌瀏覽器,所以,我直接百度上搜索"谷歌json插件"。

到這裏單個數據源,我們已經搞定,可以給自己獎勵一個雞腿!
下面,我們繼續多數據源的搭建。話不多說,走起,盤他

多個數據源(分包)

mybatis-plus(簡稱MP),以後的文章中,寫MP,就是表示mybatis-plus

利用單個數據源的sql,再重新搭建一個數據庫pro1

  • 上面是兩個數據庫表的對應數據,爲了方便後面區分,age是有明顯的區別的。第一個表是123,第二個庫的表是456.

表關係

pro test1
pro1 test2

項目改造

在單個數據源上改造

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.cloudtech</groupId>
    <artifactId>moredatasource</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>moredatasource</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 增加thymeleaf座標  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!--簡化實體類-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- 熱部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

        <!--swagger2-->
        <dependency>
            <groupId>com.spring4all</groupId>
            <artifactId>spring-boot-starter-swagger</artifactId>
            <version>1.5.1.RELEASE</version>
        </dependency>

        <!--MP插件,簡化sql操作-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.0</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.18</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.6</version>
        </dependency>

        <!--swagger2-->
        <dependency>
            <groupId>com.spring4all</groupId>
            <artifactId>spring-boot-starter-swagger</artifactId>
            <version>1.5.1.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <!--解決編譯後,xml文件沒有過去的問題-->
        <resources>
                <resource>
                    <directory>src/main/java</directory>
                    <includes>
                        <include>**/*.xml</include>
                    </includes>
                </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
  • 相對於單個數據源,多引入了一個swagger,主要是實現api接口文檔可視化。

applicaion.yml

server:
    port: 8888
spring:
  datasource:
    test1:
      # 使用druid數據源
      type: com.alibaba.druid.pool.DruidDataSource
      jdbc-url: jdbc:mysql://localhost:3306/pro?useUnicode=true&characterEncoding=utf8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver

    test2:
      # 使用druid數據源
      type: com.alibaba.druid.pool.DruidDataSource
      jdbc-url: jdbc:mysql://localhost:3306/pro1?useUnicode=true&characterEncoding=utf8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
      
mybatis-plus:
  configuration:
    ##打印sql日誌,本地測試使用,生產環境不要使用,注意、注意、注意
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

####掃描swagger註解
swagger:
  base-package: com.cxyxs
  • jdbc-url和driver-class-name這兩個參數得注意,在多數據源配置中,有變動,我們熟悉的是url,所以不少人都會有困惑,同樣的代碼,爲什麼,社長你的啓動就不報錯,難道是社長人品太好的原因。
  • 前方高能,注意警惕,我們可以發現配置連接數據庫的那塊代碼,有了明顯的變動,增加了一個test1和test2。spring.datasource.test1和spring.datasource.test2後面會用到,這個是前綴,type,jdbc-url等等這些是後綴,是固定的。
  • swagger是需要配置掃描範圍的

config配置

數據源配置1

package com.cxyxs.moredatasource.config;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.plugin.Interceptor;
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.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

/**
 * Description:
 * Author: 程序猿學社
 * Date:  2020/3/7 18:14
 * Modified By:
 */
@Configuration
@MapperScan(basePackages= {"com.cxyxs.moredatasource.test1.dao"},sqlSessionFactoryRef="test1SqlSessionFactory")
public class DataSourceConfig1 {
    @Bean(name="test1DataSource")
    @ConfigurationProperties(prefix="spring.datasource.test1")
    public DataSource testDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name="test1SqlSessionFactory")
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource,@Qualifier("testPaginationInterceptor") PaginationInterceptor paginationInterceptor) throws Exception {
        MybatisSqlSessionFactoryBean bean=new MybatisSqlSessionFactoryBean ();
        bean.setDataSource(dataSource);
        Resource[] resources = new PathMatchingResourcePatternResolver()
                .getResources("classpath*:/com/cxyxs/moredatasource/test1/mapper/*.xml");
        bean.setMapperLocations(resources);
        Interceptor[] plugins = new Interceptor[]{paginationInterceptor};
        bean.setPlugins(plugins);
        return bean.getObject();
    }

    @Bean(name="test1TransactionManager")//配置事務
    public DataSourceTransactionManager testTransactionManager(@Qualifier("test1DataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean("testPaginationInterceptor")
    public PaginationInterceptor paginationInterceptor(){
        return new PaginationInterceptor();
    }


    @Bean(name="test1SqlSessionTemplate")
    public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
       return new SqlSessionTemplate(sqlSessionFactory);
    }
}
  • springboot2.0以上會有一個錯誤,需要你指定一個默認數據源,在springboot2.0以後就修復了這個問題,如果springboot版本在2.0一下的社友可以在每個@Bean註解下面增加一句代碼@Primary

  • paginationInterceptor註解是配置MP分頁插件,不瞭解的社友可以看看這篇文章
    springboot集成mybatis-plus

  • 配置好分頁插件的bean,是不是要跟SqlSessionFactory建立聯繫。在單個數據源中,我們是不需要建立聯繫的。

  • 下面代碼是配置mybatis配置文件在哪裏。還記得我們單數據源配置的時候,配置在哪裏嗎?配置在yml裏面。

  • 分享一下,個人搭建過程中,遇到的問題,最初我使用的是SqlSessionFactory這個對象,在測試過程中,發現,所有的mybatis調用數據庫的操作都沒有毛病,但是,我一調用MP提供的一些insert方法時,發現,提示找不到這個方法。就在想,是不是又要建立什麼聯繫,查了一些資料,才發現MP自己實現了一個工廠MybatisSqlSessionFactoryBean,換成這個對象,問題就解決了,從這,我得到一個啓示,學習一個東西,不應該簡單的會使用,而應該深入瞭解他的一個思想。進行一些簡單的源碼學習。

 //MP的工廠
 MybatisSqlSessionFactoryBean bean=new MybatisSqlSessionFactoryBean ();
//mybatis的工廠
//SqlSessionFactory bean=new MybatisSqlSessionFactoryBean ();

數據源配置2

直接把數據源配置1的類copy一下,全文把test1替換成test2。把類名的1改成2.放在注入到springboot中報錯。

  • boot2.0一下版本的社友,把數據源配置1代碼copy過來的時候,記得去掉@Primary註解,不然springboot會犯困惑,到底要加載那個數據源,會有疑惑,有疑惑,那隻能給你報錯。
package com.cxyxs.moredatasource.config;

import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
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.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

/**
 * Description:
 * Author: 程序猿學社
 * Date:  2020/3/7 18:14
 * Modified By:
 */
@Configuration
@MapperScan(basePackages= {"com.cxyxs.moredatasource.test2.dao"},sqlSessionFactoryRef="test2SqlSessionFactory")
public class DataSourceConfig2 {
    @Bean(name="test2DataSource")
    @ConfigurationProperties(prefix="spring.datasource.test2")
    public DataSource testDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name="test2SqlSessionFactory")
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("test2DataSource") DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean bean=new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dataSource);
       Resource[] resources = new PathMatchingResourcePatternResolver()
                .getResources("classpath*:/com/cxyxs/moredatasource/test2/mapper/*.xml");
        bean.setMapperLocations(resources);
        return bean.getObject();
    }

    @Bean(name="test2TransactionManager")//配置事務
    public DataSourceTransactionManager testTransactionManager(@Qualifier("test2DataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name="test2SqlSessionTemplate")
    public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
       return new SqlSessionTemplate(sqlSessionFactory);
    }
}

實體類

package com.cxyxs.moredatasource.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import org.omg.CORBA.IDLType;

/**
 * Description:
 * Author: 程序猿學社
 * Date:  2020/3/7 12:03
 * Modified By:
 */
@Data
public class Emp {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String name;
    private Integer age;
}

dao類和mapper

test1包

@Repository
public interface EmpMapper1 extends  BaseMapper<Emp>{
    @Select("select * from emp")
    public List<Emp> selectList();
    /**
     * 測試mapper
     * @return
     */
    public List<Emp> getAll();
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cxyxs.moredatasource.test1.dao.EmpMapper1">
  
  <!--  根據區域名稱獲取區域代碼-->
   <select id="getAll" resultType="com.cxyxs.moredatasource.entity.Emp">
		select * from emp
  </select>
</mapper>

test2包

package com.cxyxs.moredatasource.test2.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cxyxs.moredatasource.entity.Emp;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * Description:
 * Author: 程序猿學社
 * Date:  2020/3/7 12:01
 * Modified By:
 */
@Repository
public interface EmpMapper2  extends  BaseMapper<Emp>{
    @Select("select * from emp")
    public List<Emp> selectList();

    /**
     * 測試mapper
     * @return
     */
    public List<Emp> getAll();
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cxyxs.moredatasource.test2.dao.EmpMapper2">
  
  <!--  根據區域名稱獲取區域代碼-->
   <select id="getAll" resultType="com.cxyxs.moredatasource.entity.Emp">
		select * from emp
  </select>
</mapper>
  • 在這裏給大家推薦一款提高開發效率的工具,不是打廣告,MybatisX 是一款基於 IDEA 的快速開發插件,爲效率而生。他也是MP官方推薦的,簡單說一下他的作用。我們再dao類寫上如下代碼。使用MybatisX插件會幫我們生成對應的xml文件。
public List<Emp> getAll();

插件自動生成的

 <select id="getAll" resultType="com.cxyxs.moredatasource.entity.Emp"></select>

controll類

package com.cxyxs.moredatasource.controller;

import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.cxyxs.moredatasource.entity.Emp;
import com.cxyxs.moredatasource.test1.dao.EmpMapper1;
import com.cxyxs.moredatasource.test2.dao.EmpMapper2;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;


/**
 * Description:
 * Author: 程序猿學社
 * Date:  2020/3/7 12:15
 * Modified By:
 */
@RestController
@Api("測試多數據源接口")
public class TestController {
    @Autowired
    private EmpMapper1 empMapper1;
    @Autowired
    private EmpMapper2 empMapper2;

    @ApiOperation("測試mybatis@select註解,通過test1數據庫實現")
    @GetMapping("/getKing1")
    public List getKing1(){
        List<Emp> emps = empMapper1.selectList();
        return emps;
    };


    @ApiOperation("測試mybatis@select註解,通過test2數據庫實現")
    @GetMapping("/getKing2")
    public List getKing2(){
        List<Emp> emps = empMapper2.selectList();
        return emps;
    };

    @ApiOperation("測試mybatis的mapper.xml文件調用,通過test1數據庫實現")
    @GetMapping("/getKing3")
    public List getKing3(){
        List<Emp> emps = empMapper1.getAll();
        return emps;
    };

    @ApiOperation("測試mybatis的mapper.xml文件調用,通過test2數據庫實現")
    @GetMapping("/getKing4")
    public List getKing4(){
        List<Emp> emps = empMapper2.getAll();
        return emps;
    };

    @ApiOperation("通過mp調用test1數據庫實現查詢")
    @GetMapping("/getKing5")
    public List getKing5(){
        List<Emp> emps = empMapper1.selectList(null);
        return emps;
    };

    @ApiOperation("通過mp調用test2數據庫實現查詢")
    @GetMapping("/getKing6")
    public List getKing6(){
        List<Emp> emps = empMapper2.selectList(null);
        return emps;
    };
}
  • @api等等註解都是swgger提供的一些註解,也就是讓我們通過頁面,可以看到這些註釋,方便前端開發調用,不至於,動不動就問後臺,提供開發效率。這不是本文的重點,不瞭解swagger的社友,可以去學習一下。
  • 模擬了調用2個庫,通過三種方式實現。第一種,@select註解,第二種,通過mybatis的xml文件調用,第三種,通過MP提供的公有方法實現。也是考慮到,實際項目開發過程中,可能每一種都有可能用到。

啓動類

package com.cxyxs.moredatasource;

import com.spring4all.swagger.EnableSwagger2Doc;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages = {"com.cxyxs"})
@EnableSwagger2Doc
public class MoredatasourceApplication {

    public static void main(String[] args) {
        SpringApplication.run(MoredatasourceApplication.class, args);
    }

}
  • @EnableSwagger2Doc 啓動swagger

測試

輸入網址http://localhost:8080/swagger-ui.html#/

  • 選擇某個請求點擊try out
  • 往下來,可以查看Response Body,獲取對應後端返回的結果。

到這裏我們springboot+mybatis-plus+多數據源+swagger就實現了。

多數據源事務

爲什麼要使用事務?

  • 例如隔壁小王給社長轉賬的100元,他的業務可以分爲2步
  • 第一步:隔壁小王扣100元
  • 第二步:社長+100元。

如果第一步和第二步之間就報錯勒,可能會存在隔壁小王扣了100,但是社長,也沒有收到錢,隔壁小王賴上社長,這我跳到黃河都說不清。
爲了保證社長的一世清白,這是我們的主角事務開始隆重登場。

簡單的理解事務流程

  • START TRANSACTION開啓事務
  • 隔壁小王update
  • 社長 update
  • commit 提交事務或者rollback回滾事務。

給多數據源代碼添加事務

不指定某個事務

之前的內容,我們都是讀的操作,不涉及到事務,本文,我們來試試寫操作,並增加對應的事務來試試。
在TestController類中增加一個方法

 @ApiOperation("測試插入數據")
    @PostMapping("/saveEmp1")
    @Transactional
    public String saveEmp(Emp emp) {
        int insert = empMapper1.insert(emp);
        if(insert > 0){
            return "插入成功";
        }else{
            return "插入失敗";
        }
    };

  • id是數據庫自增主鍵,所以不需要設置值。
  • @Transactional我們都知道這個關鍵字是設置事務的。各位社友,覺得我直接啓動項目,訪問這個方法,可能會出現什麼樣的結果
    保存成功?
{
  "timestamp": "2020-03-08T11:53:42.234+0000",
  "status": 500,
  "error": "Internal Server Error",
  "message": "No qualifying bean of type 'org.springframework.transaction.TransactionManager' available: expected single matching bean but found 2: test1TransactionManager,test2TransactionManager",
  "trace": "org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.transaction.TransactionManager' available: expected single matching bean but found 2: test1TransactionManager,test2TransactionManager\r\n\tat org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1180)\r\n\tat org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:416)\r\n\tat org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349)\r\n\tat org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)\r\n\tat org.springframework.transaction.interceptor.TransactionAspectSupport.determineTransactionManager(TransactionAspectSupport.java:480)\r\n\tat org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:335)\r\n\tat org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:99)\r\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)\r\n\tat org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)\r\n\tat org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)\r\n\tat com.cxyxs.moredatasource.controller.TestController$$EnhancerBySpringCGLIB$$11aec56f.saveEmp(<generated>)\r\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\r\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.lang.reflect.Method.invoke(Method.java:498)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:660)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:741)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:367)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1639)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)\r\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.lang.Thread.run(Thread.java:745)\r\n",
  "path": "/saveEmp1"
}
  • org.springframework.transaction.TransactionManager’ available: expected single matching bean but found 2,這個事關鍵的信息,意思說找到兩個事務,就有點犯困惑,到底運行那個事務。
  • 還記得DataSourceConfig1類方法裏面,我們配置了test1TransactionManager,我們指定一下,使用那個事務。

指定某個事務

還是我們的TestController類中增加一個方法

 @ApiOperation("測試給test1插入數據,增加指定某個事務的代碼")
    @PostMapping("/saveEmp2")
    @Transactional(value = "test1TransactionManager")
    public String saveEmp2(Emp emp) {
        int insert = empMapper1.insert(emp);
        if(insert > 0){
            return "插入成功";
        }else{
            return "插入失敗";
        }
    };


指定事務,模擬報錯

 @ApiOperation("測試給test1插入數據,增加指定某個事務的代碼,並故意在代碼中報錯")
    @PostMapping("/saveEmp3")
    @Transactional(value = "test1TransactionManager")
    public String saveEmp3(Emp emp) {
        int insert = empMapper1.insert(emp);
        //故意報錯
        String str= null;
        System.out.println(str.toString());   //這裏會報錯
        if(insert > 0){
            return "插入成功";
        }else{
            return "插入失敗";
        }
    };
  • str爲null,再調用null.toString,肯定會報空指針,我們來斷點跟蹤一下。
  • insert的值爲1,是不是表示,這個記錄就寫入到數據庫了
    那我們來檢查一下數據庫表的身體,看看是否手腳乾淨。
  • 在方法上增加了事務,表示需要這個方法執行完,確保沒問題後,才能調用commit方法,同步到數據庫裏。
    繼續向下執行。

    -這裏直接報錯勒,到這裏整個方法已經跑完,各位社友覺得數據是否寫入成功
    再次檢查數據庫對應表的身體
  • 很乾淨,說明配置事務後,如果方法中間拋出異常,事務會回滾。

本文還未完,後續會持續更新
下次更新內容

如何解決多數據源事務問題,在實際項目開發過程中,會存在,在一個項目中,操作多個數據源的問題,在調用多個數據源過程中,我們如何保證事務的一致性,實際上,這個版本的代碼,你可以再調用test1的插入方法,再調用test2庫,執行插入方法。這兩者調用之間弄一個報錯,可以發現,test1庫新增了一條數據,test2插入失敗。

關注公衆號"程序猿學社",回覆關鍵字999,獲取源碼

程序猿學社的GitHub,歡迎Star
https://github.com/ITfqyd/cxyxs
本文已記錄到github,形成對應專題。

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