SpringBoot(十九) Mybatis 多数据源配置

多数据源设计一般应用到中大型的项目中,项目关联的业务性比较复杂,使用的数据库比较分散。我们使用多数据源的目的也有很多,比如:分布式数据库读写分离、集成不同数据库等。不管初衷是什么也都是为了提高项目的可维护性、稳定性、响应速度。

目标

基于SpringBoot项目整合Mybatis + Druid 完成多数据源根据包名自动切换。

数据初始化

创建两个数据库(test、books
test 数据库内包含了一张数据表t_user

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`t_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
`t_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '名称',
`t_age` int(10) NULL DEFAULT NULL COMMENT '年龄',
`t_address` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '家庭住址',
`t_pwd` varchar(100) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL COMMENT '用户登录密码',
PRIMARY KEY (`t_id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 10 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES (1, 'Ray', 6, 'zh', '123');
INSERT INTO `t_user` VALUES (2, 'q343509740', 18, 'zh', '456');
INSERT INTO `t_user` VALUES (3, 'Rain', 24, 'zh', '789');

SET FOREIGN_KEY_CHECKS = 1;

books 数据库内包含了一张数据库book

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for book
-- ----------------------------
DROP TABLE IF EXISTS `book`;
CREATE TABLE `book` (
`b_id` int(11) NOT NULL AUTO_INCREMENT,
`b_name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`b_id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of book
-- ----------------------------
INSERT INTO `book` VALUES (1, 'SpringBoot入门');
INSERT INTO `book` VALUES (2, 'SpringData实战');
INSERT INTO `book` VALUES (3, 'SpringData放弃');

SET FOREIGN_KEY_CHECKS = 1;

构建项目

pom.xml

添加Mybatis、WEB、Druid 等依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.ray</groupId>
<artifactId>springboot2-19-mybatis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>springboot2-19-mybatis</name>
<description>Demo project for Spring Boot</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Mybatis 依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--通用mapper-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.0.2</version>
</dependency>

<!-- MySQL 连接驱动依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--Spring Boot Druid 依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>
<!-- Lombok 依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

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

<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.6</version>
<dependencies>
<!--配置这个依赖主要是为了等下在配置mybatis-generator.xml的时候可以不用配置classPathEntry这样的一个属性,避免代码的耦合度太高-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.29</version>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>4.0.0</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>Generate MyBatis Artifacts</id>
<phase>package</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<!--允许移动生成的文件 -->
<verbose>true</verbose>
<!-- 是否覆盖 -->
<overwrite>true</overwrite>
<!-- 自动生成的配置 -->
<configurationFile>src/main/resources/mybatis-generator.xml</configurationFile>
</configuration>
</plugin>
</plugins>
</build>
</project>

这里使用Mybatis Generator 自动构建项目,需要Mapper 搭配使用。

appliction.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
spring:
# 数据库配置
datasource:
# 提取公共属性
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource

# user 数据库的配置
user:
url: jdbc:mysql://localhost:3306/test?characterEncoding=utf8
# book 数据库的配置
book:
url: jdbc:mysql://localhost:3306/books?characterEncoding=utf8

# Druid 常用配置
druid:
# 配置控制统计拦截的filters,去掉后监控界面sql将无法统计,wall用于防火墙
filter: stat,wall
# 最大活跃数
max-active: 20
# 初始化数量
initial-size: 1
# 最小连接池数量
min-idle: 1
# 最大连接等待超时时间
max-wait: 60000
# 打开PSCache,并且指定每个连接PSCache的大小,mysql可以设置为false
pool-prepared-statements: false
max-pool-prepared-statement-per-connection-size: 20
# 配置间隔多久才进行一次检测,检测需要关闭的空间连接,单位是毫秒
time-between-eviction-runs-millis: 60000
# 用来检测连接是否有效
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
async-init: true

# Mapper
mapper:
# 自定义Mapper接口
mappers: com.ray.springboot219mybatis.util.MyMapper
# 是否判断字符串类型 !=''
not-empty: false
# 取回主键的方式
identity: MYSQL

# Mybatis
mybatis:
config-location: classpath:mybatis-config.xml

mybatis-generator.xml

  1. 需要创建自定义接口MyMapper(注意路径)
  2. 修改指定数据库连接信息
  3. 修改生成对应表及类名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<!-- 具体配置内容 -->
<generatorConfiguration>
<!--加载配置文件,为下面读取数据库信息准备,通过${key}获取值,这里使用yml无法使用properties-->
<!--<properties resource="application.properties"/>-->

<!-- context 子元素有严格的配置顺序 -->
<context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">

<plugin type="tk.mybatis.mapper.generator.MapperPlugin">
<!--配置后生成的 Mapper 接口都会自动继承该接口-->
<property name="mappers" value="com.ray.springboot219mybatis.util.MyMapper" />
<!--caseSensitive默认false,当数据库表名区分大小写时,可以将该属性设置为true-->
<property name="caseSensitive" value="true"/>
</plugin>

<!-- 屏蔽生成自动注释 -->
<commentGenerator>
<property name="javaFileEncoding" value="UTF-8"/>
<property name="suppressDate" value="true"/>
<property name="suppressAllComments" value="true"/>
</commentGenerator>

<!--指定数据库连接信息-->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/test"
userId="root"
password="root">
</jdbcConnection>

<!--指定JDBC类型和Java类型如何转换-->
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>

<!--控制生成的实体类-->
<javaModelGenerator targetPackage="com.ray.springboot219mybatis.user.entity" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>

<!--生成映射文件存放位置-->
<sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>

<!--生成Mapper接口-->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.ray.springboot219mybatis.user.dao" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>

<!--生成对应表及类名
去掉Mybatis Generator生成的一堆 example
-->
<table tableName="t_user"
domainObjectName="User"
enableCountByExample="false"
enableUpdateByExample="false"
enableDeleteByExample="false"
enableSelectByExample="false"
selectByExampleQueryId="false">
<generatedKey column="id" sqlStatement="Mysql" identity="true"/>
</table>
</context>
</generatorConfiguration>

mybatis-config.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置全局属性 -->
<settings>
<!-- 使用jdbc的getGeneratedKeys获取数据库自增主键值 -->
<setting name="useGeneratedKeys" value="true" />

<!-- 使用列标签替换列别名 默认:true -->
<setting name="useColumnLabel" value="true" />

<!-- 开启驼峰命名转换:Table{create_time} -> Entity{createTime} -->
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
</configuration>

MyMapper.java

特别注意,该接口不能被扫描到,否则会出错

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.ray.springboot219mybatis.util;

import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.common.MySqlMapper;

/**
* @author Ray
* @date 2018/8/7 0007
* 自定义接口继承Mapper<T>
*/
public interface MyMapper<T> extends Mapper<T>, MySqlMapper<T> {
//FIXME 特别注意,该接口不能被扫描到,否则会出错
}

数据源配置类

我们自定义一个数据源配置类DataSourceConfigurer(与applition.java 同级),在类内对应声明两个数据源的Bean对象,以及使用application.yml配置文件内的前缀属性配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* @author Ray
* @date 2018/8/7 0007
* 数据源配置类,有多少个从库就要配置多少个
* 注意:DataSourceBuilder
*/
@Configuration
public class DataSourceConfigurer {

/**
* 用户数据源 - 从库
*/
@Bean(name = "userDataSource") //装配该方法返回值为userDataSource管理bean
@Qualifier(value = "userDataSource") //spring装配bean唯一标识,为了防止注入时冲突问题
@ConfigurationProperties(prefix = "spring.datasource.user") //application.yml配置文件中该数据源的配置前缀
public DataSource userDataSource(){
// return DataSourceBuilder.create().build(); // HikariCP -> 需修改jdbcUrl
return DruidDataSourceBuilder.create().build(); // Druid -> 默认 url
}

/**
* 书籍数据源 - 主库
*/
@Bean
@Primary //配置该数据源为主数据源
@Qualifier(value = "bookDataSource")
@ConfigurationProperties(prefix = "spring.datasource.book")
public DataSource bookDataSource(){
return DruidDataSourceBuilder.create().build();
}
}

  1. 两个数据库分别定义了两个数据源,userDataSource、bookDataSource。
  2. 使用@Qualifier 注解并且设置内容,是为了防止注入时冲突问题。
  3. xxxDataSource则是配置使用了spring.datasource.xxx前缀的配置。
  4. @Primary 配置了数据源为主数据源。

DataSourceConfigurer

我们自定义配置文件类(与applition.java 同级)
注意:主库 必须设置 @Primary
配置步骤如下:

  1. 配置@MapperScan
  2. 注入xx数据源
  3. 配置 SqlSessionFactory
  4. 配置 DataSourceTransactionManager
  5. 配置 SqlSessionTemplate

BookDataSourceConfigurer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* @author Ray
* @date 2018/8/7 0007
* 主库配置
*/
@Configuration
@MapperScan(
basePackages = "com.ray.springboot219mybatis.book.dao", // 1.扫描dao层(Mapper接口)
sqlSessionTemplateRef = "bookSqlSessionTemplate") // 2.给dao层注入指定的SqlSessionTemplate
public class BookDataSourceConfigurer {

/**
* 注入书籍数据源
*/
@Autowired
@Qualifier("bookDataSource")
private DataSource bookDataSource;

/**
* 配置SqlSessionFactory
* 1.创建SqlSessionFactoryBean
* 2.配置数据源
* 3.指定mapper文件的路径
*/
@Bean(name = "bookSqlSessionFactory")
@Primary // 定义主数据源
public SqlSessionFactory bookSqlSessionFactory() throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
// 设置数据源
bean.setDataSource(bookDataSource);
// 加载mapper.xml映射文件
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/book/*.xml"));
return bean.getObject();
}

/**
* 配置DataSourceTransactionManager
* MyBatis自动参与到spring事务管理中,无需额外配置
*/
@Bean(name = "bookTransactionManager")
@Primary // 定义主数据源
public DataSourceTransactionManager bookTransactionManager(){
return new DataSourceTransactionManager(bookDataSource);
}

/**
* 配置SqlSessionTemplate
*/
@Bean(name = "bookSqlSessionTemplate")
@Primary // 定义主数据源
public SqlSessionTemplate bookSqlSessionTemplate(@Qualifier("bookSqlSessionFactory") SqlSessionFactory sqlSessionFactory){
return new SqlSessionTemplate(sqlSessionFactory);
}
}

UserDataSourceConfigurer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
* @author Ray
* @date 2018/8/7 0007
* 从库配置
*/
@Configuration
@MapperScan(
basePackages = "com.ray.springboot219mybatis.user.dao",
sqlSessionTemplateRef = "userSqlSessionTemplate")
public class UserDataSourceConfigurer {

/**
* 注入用户数据源
*/
@Autowired
@Qualifier("userDataSource")
private DataSource userDataSource;

/**
* 配置SqlSessionFactory
* 1.创建SqlSessionFactoryBean
* 2.配置数据源
* 3.指定mapper文件的路径
*/
@Bean(name = "userSqlSessionFactory")
public SqlSessionFactory userSqlSessionFactory() throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(userDataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/user/*.xml"));
return bean.getObject();
}

/**
* 配置DataSourceTransactionManager
* MyBatis自动参与到spring事务管理中,无需额外配置
*/
@Bean(name = "userTransactionManager")
public DataSourceTransactionManager userTransactionManager(){
return new DataSourceTransactionManager(userDataSource);
}

/**
* 配置SqlSessionTemplate
*/
@Bean(name = "userSqlSessionTemplate")
public SqlSessionTemplate userSqlSessionTemplate(@Qualifier("userSqlSessionFactory") SqlSessionFactory sqlSessionFactory){
return new SqlSessionTemplate(sqlSessionFactory);
}
}

控制层

注入了xxxMapper,调用了mapper内部的selectAll()方法来读取全部数据列表,并通过@RestController注解作用返回Json字符串。

BookController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @author Ray
* @date 2018/8/7 0007
*/
@RestController
@RequestMapping(value = "/book")
public class BookController {

@Autowired
private BookMapper bookMapper;

/**
* 查询书籍列表
*/
@RequestMapping(value = "/list")
public List<Book> list(){
return bookMapper.selectAll();
}
}

UserController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @author Ray
* @date 2018/8/7 0007
*/
@RestController
@RequestMapping(value = "/user")
public class UserController {

@Autowired
private UserMapper userMapper;

/**
* 查询用户列表
*/
@RequestMapping("/list")
public List<User> list(){
return userMapper.selectAll();
}
}

运行测试

上面步骤我们编码已经完成,下面我们开启项目来测试数据是否对应不同的数据库获取。

访问用户列表:http://localhost:8080/user/list

访问书籍列表:http://localhost:8080/book/list

完整项目结构:

-------------- 本文结束  感谢您的阅读 --------------