SpringBoot实现mysql多数据源配置(Springboot+Mybatis)

最近在学习SpringBoot的一些知识,主要是参考了纯洁的微笑的一些博客作为练手,想着他已经把入门的教程写的很详细了,如果再简单的把他的教程拷贝到自己的简书,这是是赤裸裸的剽窃,很不地道,作为一个技术人员,应该有一定的技术底线。所以,就把纯洁微笑的博客链接放在参考链接中的第一条,( ̄▽ ̄)"

目的

多数据源是指在同一个项目中写入/读取相同类型数据源的不同库的情况,比如在项目中需要访问mysql两个不同的数据库,常见于主从模式、或者数据库的分库的场景。
多数据源不包含不同类型数据源的情况,例如:一个项目中可能引入mysql数据库,同时也引入redis缓存数据库,这的确是两个数据源,但是类型不同,可以直接使用SpringBoot的相关配置即可完成。

开始动手干活吧

话不多说,直接开整

开发环境

  • IDE:idea
  • 项目构建:maven
  • 测试工具:postman

创建SpringBoot的项目

在这里说一下纯洁的微笑的项目结构,首先,创建了一个spring-boot-example的maven工程,该工程的打包类型为pom,每一个功能作为一个子module 项目,使用IDEA有一个比较高尴尬的事情,使用Spring 脚手架(spring initializr) 创建的module 在父项目的pom.xml文件中没有增加<module>标签。也有可能是因为我用的姿势不正确~
使用spring initalizr,在创建的时候,可以勾选所需要的依赖,避免手工填写相关的依赖,所选依赖如下图所示:

使用spring initalizr创建module

在spring-boot-example项目里创建一个新的module,名称为mybatis-mutidbsource。在父项目的pom.xml文件,手工增加 <module>mybatis-mutidbsource</module> ,如果不加的话,应该也没问题,这个没尝试过。迫于强迫症的习惯,还是手工加上了。

引入相关的依赖

<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  <java.version>1.8</java.version>
  <mybatis.version>2.1.2</mybatis.version>
</properties>
<dependencies>
        <!-- web -->
        <!-- web 模块 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.version}</version>
        </dependency>
        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </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>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

配置application.properties

在配置数据库信息时,需要注意一点,单数据源的数据库连接使用的参数为:spring.datasource.url ,具体示例如下:

spring.datasource.url = jdbc:mysql://localhost:3306/spring_boot_test?useSSL=true&serverTimezone=UTC

在使用多数据源的场景下,要将url修改为jdbc-url,对于多数据源参数的前缀信息也没有强制要求,但为了更方便的配置参数,建议相同数据源使用相同的前缀信息,修改后的配置参数如下:

spring.datasource.test1.jdbc-url = jdbc:mysql://localhost:3306/spring_boot_test1?useSSL=true&serverTimezone=UTC

其他的配置参数没有特别说明,完整的配置信息如下:

# 数据库相关
## 配置多数据源 test1
spring.datasource.test1.username=root
spring.datasource.test1.password=1qaz@WSX
spring.datasource.test1.driver-class-name=com.mysql.cj.jdbc.Driver
#此处url 修改为jdbc-url
spring.datasource.test1.jdbc-url = jdbc:mysql://localhost:3306/spring_boot_test1?useSSL=true&serverTimezone=UTC

## 配置多数据源 test2
spring.datasource.test2.username=root
spring.datasource.test2.password=1qaz@WSX
spring.datasource.test2.driver-class-name=com.mysql.cj.jdbc.Driver
#此处url 修改为jdbc-url
spring.datasource.test2.jdbc-url = jdbc:mysql://localhost:3306/spring_boot_test2?useSSL=true&serverTimezone=UTC


# mybatis
mybatis.type-aliases-package=com.fulin.example.bootexample.mybatismutidbsource.model

最最最最重要的数据源配置

第一个数据源

@Configuration
@MapperScan(basePackages = "com.fulin.example.bootexample.mybatismutidbsource.mapper.test1",sqlSessionTemplateRef = "test1SqlSessionTemplate")
public class DataSource1Config {

    @Bean(name = "test1DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.test1")
    @Primary
    public DataSource testDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "test1SqlSessionFactory")
    @Primary
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mybatis/mapper/test1/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "test1SqlSessionTemplate")
    @Primary
    public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory){
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean(name = "test1TransactionManager")
    @Primary
    public DataSourceTransactionManager testTransactionManager(@Qualifier("test1DataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }


}

第二个数据源

@Configuration
@MapperScan(basePackages = "com.fulin.example.bootexample.mybatismutidbsource.mapper.test2",sqlSessionTemplateRef = "test2SqlSessionTemplate")
public class DataSource2Config {

    @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 {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mybatis/mapper/test2/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "test2TransactionManager")
    public DataSourceTransactionManager testTransactionManager(@Qualifier("test1DataSource") DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }

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

比对两个数据源配置文件

使用文本比较软件,比对两个数据源配置文件,比对结果如下:


数据源配置文件差异比对

两个配置文件的主要差异有两点:

  1. 第一个数据源需要增加@Primary标签,用于区分是否为主库
  2. 两个数据源的bean 类型完全相同,应该使用名称进行区分

验证功能

为了能够验证配置的数据源,使用用户信息查询的简单demo验证配置是否正确。可以使用SpringBoot的Test类进行测试,也可以使用Restful接口的方式进行验证,在这里使用了后面的Restful接口的方式进行验证

创建model类

为了能够更加清晰的演示,针对不同的数据源,创建不同的model类,只是类名不同,具体字段等内容完全一致。使用了lombok的@Data标签可以省略getter和setter方法的编码。为了减少篇幅大小,在这里只显示了第一个数据源的model类。

@Data
public class User1Entity implements Serializable {

    private static final long serialVersionUID = 1L;
    public long id;
    public String userName;
    public String passWord;
    public UserSexEnum userSex;
    public String nickName;


    public User1Entity() {
        super();
    }

    public User1Entity(String userName, String passWord, UserSexEnum userSex) {
        super();
        this.passWord = passWord;
        this.userName = userName;
        this.userSex = userSex;
    }

    @Override
    public String toString() {
        return "userName: " + this.userName + ", pasword :" + this.passWord + ", sex :" + userSex.name();
    }
}

创建Mapper类

创建Mapper类,主要写了一些增删改查的方法,便于后面的测试。

@Mapper
@Repository
public interface User1Mapper {

    List<User1Entity> getAll();
    User1Entity getOne(Long id);
    void inserUser(User1Entity userEntity);

    void updateUser(User1Entity userEntity);

    void deleteById(Long id);

    User1Entity getOneByUserName(String userName);

    void deleteAll();
}

创建mapper.xml

创建mybatis的mapper.xml文件

<?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.fulin.example.bootexample.mybatismutidbsource.mapper.test1.User1Mapper" >
    <resultMap id="BaseResultMap" type="com.fulin.example.bootexample.mybatismutidbsource.model.test1.User1Entity">
        <id column="id" property="id" jdbcType="BIGINT" />
        <result column="userName" property="userName" jdbcType="VARCHAR" />
        <result column="passWord" property="passWord" jdbcType="VARCHAR" />
        <result column="user_sex" property="userSex" javaType="com.fulin.example.bootexample.mybatismutidbsource.enums.UserSexEnum" />
        <result column="nick_name" property="nickName" jdbcType="VARCHAR" />
    </resultMap>

    <sql id="Base_Column_List">
        id , userName,passWord,user_sex,nick_name
    </sql>
    <select id="getAll" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List"/>
        from users
    </select>

    <select id="getOne" parameterType="java.lang.Long" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from users
        where id = #{id}
    </select>
    <select id="getOneByUserName" parameterType="java.lang.String" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from users
        where userName = #{userName}
    </select>

    <insert id="inserUser" parameterType="com.fulin.example.bootexample.mybatismutidbsource.model.test1.User1Entity">
        insert into users (userName,passWord,nick_name,user_sex)
        values(#{userName},#{passWord},#{nickName},#{userSex})
    </insert>
    <select id="updateUser" parameterType="com.fulin.example.bootexample.mybatismutidbsource.model.test1.User1Entity">
        update users
        set
        <if test="userName != null"> userName = #{userName} , </if>
        <if test="passWord != null"> passWord = #{passWord} , </if>
        nick_name=#{nickName}
    </select>

    <delete id="deleteById" parameterType="java.lang.Long">
        delete from users
        where id = #{id}
    </delete>

    <delete id="deleteAll" >
        delete from users
    </delete>
</mapper>

controller

为了简单的测试相关的接口,没有编写service 层,直接在controller层调用dao层相关的接口。

@RestController
public class UserController {

    @Autowired
    private User1Mapper user1Mapper;

    @Autowired
    private User2Mapper user2Mapper;

    @GetMapping("/user1/getAllUser")
    public List<User1Entity> getAllUser1(){
        return user1Mapper.getAll();
    }

    @GetMapping("/user2/getAllUser")
    public List<User2Entity> getAllUser2(){
        return user2Mapper.getAll();
    }

    @GetMapping("/user1/{id}")
    public User1Entity getOneUser1(@PathVariable(value = "id") Long id){
        return user1Mapper.getOne(id);
    }

    @GetMapping("/user2/{id}")
    public User2Entity getOneUser2(@PathVariable(value = "id") Long id){
        return user2Mapper.getOne(id);
    }

    @PostMapping("/user1/inserUser1")
    public String insertUser1(@RequestParam(value = "userName") String userName,
                              @RequestParam(value = "passWord") String passWord,
                              @RequestParam(value = "userSex") String userSex){

        UserSexEnum  userSexEnum = UserSexEnum.valueOf(userSex);
        User1Entity user1Entity = new User1Entity( userName,  passWord,  userSexEnum);
        user1Mapper.inserUser(user1Entity);
        return "insert user ["+user1Entity+"] OK !";
    }

    @PostMapping("/user2/inserUser2")
    public String insertUser2(@RequestParam(value = "userName") String userName,
                              @RequestParam(value = "passWord") String passWord,
                              @RequestParam(value = "userSex") String userSex){

        UserSexEnum  userSexEnum = UserSexEnum.valueOf(userSex);
        User2Entity user2Entity = new User2Entity( userName,  passWord,  userSexEnum);
        user2Mapper.inserUser(user2Entity);
        return "insert user ["+user2Entity+"] OK !";
    }

}

项目结构

最后的项目结构:


项目结构

最后的测试

使用postman进行接口测试,在这里只展示insertUser的测试界面

postman调用一个数据源的insertUser接口
第一个数据库查询结果
postman调用一个数据源的insertUser接口
第二个数据库查询结果

问题列表

  1. 程序正常启动,调用系统接口时,提示“java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName”,具体错误信息如下:


    jdbcUrl is required with driverClassName 错误截图

    解决方法:在配置多数据源时,数据库连接串的关键字应该改为jdbc-url

  2. 在controller层引入mapper时,会提示"Could not autowire. No beans of 'User1Mapper' type found. "错误信息

    Could not autowire. No beans of 'User1Mapper' type found. 错误截图

    解决方法:在User1Mapper增加@Repository注解,这是一个相对比较简单的方法,其他修改方法可以参考文献3

参考链接

  1. Spring Boot(七):Mybatis 多数据源最简解决方案
  2. 纯洁的微笑源码 spring-boot-example
  3. Intellij IDEA中Mybatis Mapper自动注入警告的6种解决方案
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,247评论 6 543
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,520评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,362评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,805评论 1 317
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,541评论 6 412
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,896评论 1 328
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,887评论 3 447
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,062评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,608评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,356评论 3 358
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,555评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,077评论 5 364
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,769评论 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,175评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,489评论 1 295
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,289评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,516评论 2 379