前言
近期的工作中协助同事解决一个SpringBoot+MyBatis-Plus+MySQL多数据源的问题,借此记录一下。
业务背景:从一个数据库中获取某个字段为空的的所有记录,将这些记录集作为一个接口的请求参数调用接口将接口返回值再插入到对应的数据库中
从业务上来说,这个场景只需要单纯的使用多数据源即可。并不牵涉到主从数据库的概念。
本片文章通过一个简单的案例来描述一下多数据源的配置及使用。
实现的功能:查询数据库1中的一条数据,将其插入到数据库2中
准备SQL
-- 创建数据库db1,db2
CREATE DATABASE `db1` DEFAULT CHARACTER SET utf8;
CREATE DATABASE `db2` DEFAULT CHARACTER SET utf8;
-- 在db1库和db2库中分别创建user表
use db1;
CREATE TABLE `user` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL,
`pwd` varchar(50) DEFAULT NULL,
PRIMARY KEY (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
use db2;
CREATE TABLE `user` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL,
`pwd` varchar(50) DEFAULT NULL,
PRIMARY KEY (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
项目结构
实现步骤
1.引入项目所需依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.4</version>
</dependency>
<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>
2.yml配置
单纯的多数据源配置如下所示:
server:
port: 8085
spring:
datasource:
db1:
druid:
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3306/db1?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=true
username: root
password: root
filters: wall,mergeStat
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: select 1 from dual
test-while-idle: true
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
db2:
druid:
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3306/db2?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=true
username: root
password: root
filters: wall,mergeStat
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: select 1 from dual
test-while-idle: true
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
# xml文件映射路径
mybatis:
db1:
mapper-locations: classpath:mapper/db1/*.xml
db2:
mapper-locations: classpath:mapper/db2/*.xml
3.编写配置类,加载数据源及相关配置。
加载db1数据库
package com.scholartang.config;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
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.beans.factory.annotation.Value;
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;
/**
* @Author ScholarTang
* @Date 2021/1/25 7:38 下午
* @Desc 数据库1配置类
*/
@Configuration
@MapperScan(basePackages = "com.scholartang.mapper.db1", sqlSessionTemplateRef = "db1SqlSessionTemplate")
public class DataSourceDb1Config {
/**
* 获取映射文件所在的路径
*/
@Value("${mybatis.db1.mapper-locations}")
private String db1tMapperPath;
/**
* 数据源加载
* @return
*/
@Bean(name = "db1DataSource")
@ConfigurationProperties(prefix = "spring.datasource.db1.druid")
public DataSource test1DataSource() {
return DataSourceBuilder.create().build();
}
/**
* 注入SqlSessionFactory,指定数据源和映射文件路径
* @param dataSource
* @return
* @throws Exception
*/
@Bean(name = "db1SqlSessionFactory")
public SqlSessionFactory testSqlSessionFactory(@Qualifier("db1DataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
bean.setDataSource(dataSource);
Resource[] resources = new PathMatchingResourcePatternResolver().getResources(db1tMapperPath);
bean.setMapperLocations(resources);
return bean.getObject();
}
/**
* 注入DataSourceTransactionManager事物管理器
* @param dataSource
* @return
*/
@Bean(name = "db1TransactionManager")
public DataSourceTransactionManager testTransactionManager(@Qualifier("db1DataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
/**
*
* @param sqlSessionFactory
* @return
* @throws Exception
*/
@Bean(name = "db1SqlSessionTemplate")
public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("db1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
加载db2数据库
package com.scholartang.config;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
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.beans.factory.annotation.Value;
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;
/**
* @Author ScholarTang
* @Date 2021/1/25 7:38 下午
* @Desc 数据库1配置类
*/
@Configuration
@MapperScan(basePackages = "com.scholartang.mapper.db2", sqlSessionTemplateRef = "db2SqlSessionTemplate")
public class DataSourceDb2Config {
/**
* 获取映射文件所在的路径
*/
@Value("${mybatis.db2.mapper-locations}")
private String db2tMapperPath;
/**
* 数据源加载
* @return
*/
@Bean(name = "db2DataSource")
@ConfigurationProperties(prefix = "spring.datasource.db2.druid")
public DataSource test1DataSource() {
return DataSourceBuilder.create().build();
}
/**
* 注入SqlSessionFactory,指定数据源和映射文件路径
* @param dataSource
* @return
* @throws Exception
*/
@Bean(name = "db2SqlSessionFactory")
public SqlSessionFactory testSqlSessionFactory(@Qualifier("db2DataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
bean.setDataSource(dataSource);
Resource[] resources = new PathMatchingResourcePatternResolver().getResources(db2tMapperPath);
bean.setMapperLocations(resources);
return bean.getObject();
}
/**
* 注入DataSourceTransactionManager事物管理器
* @param dataSource
* @return
*/
@Bean(name = "db2TransactionManager")
public DataSourceTransactionManager testTransactionManager(@Qualifier("db2DataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
/**
*
* @param sqlSessionFactory
* @return
* @throws Exception
*/
@Bean(name = "db2SqlSessionTemplate")
public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("db2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
4.编写数据表对应的实体类(我这里db1、db2用的是完全相同的两张表所以共用同一个JavaBean)
package com.scholartang.model.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* @Author ScholarTang
* @Date 2021/1/26 5:21 下午
* @Desc 用户表对应的实体类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@TableName(value = "user")
public class User {
@TableId(value = "id",type = IdType.AUTO)
private Integer uid;
@TableField(value = "username")
private String username;
@TableField(value = "pwd")
private String pwd;
}
5.编写Mapper接口提供数据层支持(上边加载数据源的时候配置类不同数据源对应的mapper所在位置,所有用的时候mapper会被指向性的到某个库)
db1库对应的mapper层
package com.scholartang.mapper.db1;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.scholartang.model.po.User;
import org.apache.ibatis.annotations.Param;
/**
* @Author ScholarTang
* @Date 2021/1/26 5:23 下午
* @Desc 数据库db1-提供用户数据层支持
*/
public interface UserToDb1Mapper extends BaseMapper<User> {
User selectUserById(@Param("id") int id);
}
db2库对应的mapper层
package com.scholartang.mapper.db2;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.scholartang.model.po.User;
/**
* @Author ScholarTang
* @Date 2021/1/26 5:23 下午
* @Desc 数据库db2-提供用户数据层支持
*/
public interface UserToDb2Mapper extends BaseMapper<User> {
}
5.编写mapper对应的xml
UserToDb1Mapper.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.scholartang.mapper.db1.UserToDb1Mapper">
<!-- 可根据自己的需求,是否要使用 -->
<resultMap type="com.scholartang.model.po.User" id="user">
<result property="uid" column="uid"/>
<result property="username" column="username"/>
<result property="pwd" column="pwd"/>
</resultMap>
<select id="mySelectById" resultType="com.scholartang.model.po.User">
select * from `user` where id = 1;
</select>
<select id="selectUserById" resultMap="user">
select * from user where uid = #{id}
</select>
</mapper>
UserToDb2Mapper.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.scholartang.mapper.db2.UserToDb2Mapper">
<!-- 可根据自己的需求,是否要使用 -->
<resultMap type="com.scholartang.model.po.User" id="user">
<result property="uid" column="uid"/>
<result property="username" column="username"/>
<result property="pwd" column="pwd"/>
</resultMap>
</mapper>
6.编写服务层
本文作为简单实现,只提供了一个供
处理层
调研的service类,在该类中通过注入mapper层的接口调用接口中的方法来实现交互。
package com.scholartang.service;
import com.scholartang.mapper.db1.UserToDb1Mapper;
import com.scholartang.mapper.db2.UserToDb2Mapper;
import com.scholartang.model.po.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @Author ScholarTang
* @Date 2021/1/26 5:37 下午
* @Desc 提供测试服务
*/
@Service
public class MyTestService {
@Autowired
private UserToDb1Mapper userToDb1Mapper;
@Autowired
private UserToDb2Mapper userToDb2Mapper;
public void syncDb1ToDb2() {
User user = userToDb1Mapper.selectUserById(1);
saveData(user);
}
@Transactional(value = "db2TransactionManager")
public void saveData(User user) {
user.setUid(null);
userToDb2Mapper.insert(user);
}
}
7.编写测试类,测试
package com.scholartang;
import com.scholartang.service.MyTestService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class MultipleDataSourcesDemoApplicationTests {
@Autowired
private MyTestService service;
@Test
void contextLoads() {
service.syncDb1ToDb2();
}
}
8.结果
测试满足预期,从db1库中的user表查询出一条数据,并将该数据插入到了db2库中的user表中。
9.补充
开始的时候我在加载数据源是,数据源的
url
并没有指明是否进行SSL连接出现了事物问题。但是我加上后事物失效问题得到了解决。对此我还不大能想明白这个问题,望浏览的大佬多多指点。