⭐️作者简介:一个力求全干的Java后端开发者。
💗个人主页:CVcode码农的博客
👉当前专栏:SpringBoot整合篇
✔️本文内容:多数据源的配置,项目中如果需要使用多个数据源,可以直接cv
其他内容查看👇
SpringBoot整合jdbc和开启事务
本人能力有限,如有遗漏或错误,敬请指正,谢谢
目录
一、学习前提和项目结构
前提:需要掌握mybatis、mybatis-plus的使用和配置
数据库结构:在项目中同时使用这两个数据库
|
|
|
二、mybatis方式配置多数据源
思路:根据自定义的配置数据源信息初始化数据源【不使用SpringBoot的配置自动注入数据源,因为这样只能配置一个数据源】
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.22</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
项目结构:
spring.datasource.db1.username=账号
spring.datasource.db1.password=密码
spring.datasource.db1.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.db1.jdbc-url=jdbc:mysql://localhost:3306/db1
spring.datasource.db1.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.db2.username=账号
spring.datasource.db2.password=密码
spring.datasource.db2.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.db2.jdbc-url=jdbc:mysql://localhost:3306/db2
spring.datasource.db2.type=com.alibaba.druid.pool.DruidDataSource
2.1配置第一个数据源
/**
* @author zhenghs
* @version 1.0
* @description:
* db1的数据源配置,@MapperScan先通过basePackages确认需要在哪个位置生成的mapper,
* 再通过sqlSessionFactoryRef来确定动态代理生成的mapper是用的哪个数据源(sqlSessionFactory的作用可以去看mybatis部分)
* @date 2022/12/29 11:56
*/
@Configuration
@MapperScan(basePackages = "com.hs.dao.db1",sqlSessionFactoryRef = "db1SqlSessionFactory")
public class DataSourceOneConfig {
@Value("${spring.datasource.db1.jdbc-url}")
private String url;
@Value("${spring.datasource.db1.driverClassName}")
private String driverClassName;
@Value("${spring.datasource.db1.username}")
private String username;
@Value("${spring.datasource.db1.password}")
private String password;
//注入db1的数据源
@Bean("db1dataSource")
public DataSource dataSource1(){
DruidDataSource druidDataSource=new DruidDataSource();
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
druidDataSource.setUrl(url);
druidDataSource.setDriverClassName(driverClassName);
return druidDataSource;
}
//注入由db1数据源的创建的sqlSessionFactory,不懂的可以看mybatis如何和spring整合
@Bean("db1SqlSessionFactory")
public SqlSessionFactory db1SqlSessionFactory(DataSource db1dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(db1dataSource);
sqlSessionFactoryBean.setTypeAliasesPackage("com.hs.entity");
org.apache.ibatis.session.Configuration configuration=new org.apache.ibatis.session.Configuration();
configuration.setLogImpl(StdOutImpl.class);
sqlSessionFactoryBean.setConfiguration(configuration);
//扫描mapper的位置
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:mapper/db1/*.xml"));
return sqlSessionFactoryBean.getObject();
}
//db1数据源的事务管理器
@Bean("db1DataSourceTransactionManager")
public DataSourceTransactionManager db1DataSourceTransactionManager(DataSource db1dataSource){
DataSourceTransactionManager dataSourceTransactionManager=new DataSourceTransactionManager(db1dataSource);
return dataSourceTransactionManager;
}
//这个作用和jdbcTemplate差不多,【但一般不用,可配可不配!】
@Bean
public SqlSessionTemplate db1SqlSessionTemplate(SqlSessionFactory db1SqlSessionFactory){
SqlSessionTemplate sqlSessionTemplate=new SqlSessionTemplate(db1SqlSessionFactory);
return sqlSessionTemplate;
}
}
2.2配置第二个数据源
/**
* @author zhenghs
* @version 1.0
* @description:
* db2的数据源配置,@MapperScan先通过basePackages确认需要在哪个位置生成的mapper,
* 再通过sqlSessionFactoryRef来确定动态代理生成的mapper是用的哪个数据源(sqlSessionFactory的作用可以去看mybatis部分)
* @date 2022/12/29 11:56
*/
@Configuration
@MapperScan(basePackages = "com.hs.dao.db2",sqlSessionFactoryRef = "db2SqlSessionFactory") //动态代理生成
public class DataSourceTwoConfig {
@Value("${spring.datasource.db2.jdbc-url}")
private String url;
@Value("${spring.datasource.db2.driverClassName}")
private String driverClassName;
@Value("${spring.datasource.db2.username}")
private String username;
@Value("${spring.datasource.db2.password}")
private String password;
//注入db2的数据源
@Bean("db2dataSource")
public DataSource dataSource2(){
DruidDataSource druidDataSource=new DruidDataSource();
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
druidDataSource.setUrl(url);
druidDataSource.setDriverClassName(driverClassName);
return druidDataSource;
}
//注入由db2数据源的创建的sqlSessionFactory,不懂的可以看mybatis如何和spring整合
@Bean("db2SqlSessionFactory")
public SqlSessionFactory db2SqlSessionFactory(DataSource db2dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(db2dataSource);
sqlSessionFactoryBean.setTypeAliasesPackage("com.hs.entity");
org.apache.ibatis.session.Configuration configuration=new org.apache.ibatis.session.Configuration();
configuration.setLogImpl(StdOutImpl.class);
sqlSessionFactoryBean.setConfiguration(configuration);
//扫描mapper的位置
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:mapper/db2/*.xml"));
return sqlSessionFactoryBean.getObject();
}
//db2数据源的事务管理器
@Bean("db2DataSourceTransactionManager")
public DataSourceTransactionManager db2DataSourceTransactionManager(DataSource db2dataSource){
DataSourceTransactionManager dataSourceTransactionManager=new DataSourceTransactionManager(db2dataSource);
return dataSourceTransactionManager;
}
//这个作用和jdbcTemplate差不多,【但一般不用,可配可不配!】
@Bean
public SqlSessionTemplate db1SqlSessionTemplate(SqlSessionFactory db1SqlSessionFactory){
SqlSessionTemplate sqlSessionTemplate=new SqlSessionTemplate(db1SqlSessionFactory);
return sqlSessionTemplate;
}
}
2.3测试查询
(实体类代码省略,代码已通过测试,可自行测试)
//这个由db1数据源动态代理生成bean
public interface UserMapper {
User getUserById(int id);
}
//UserMapper.xml
<mapper namespace="com.hs.dao.db1.UserMapper">
<select id="getUserById" resultType="com.hs.entity.User">
select * from user where id=#{id}
</select>
</mapper>
//这个由db2数据源动态代理生成bean
public interface RoleMapper {
Role getRoleById(int id);
}
//RoleMapper.xml
<mapper namespace="com.hs.dao.db2.RoleMapper">
<select id="getRoleById" resultType="com.hs.entity.Role">
select * from role where id=#{id}
</select>
</mapper>
@RestController
public class UserController {
@Autowired
private UserMapper userMapper;
@RequestMapping("/user/{id}")
public User getUserById(@PathVariable int id){
User user = userMapper.getUserById(id);
System.out.println(user);
return user;
}
}
@RestController
public class RoleController {
@Autowired
private RoleMapper roleMapper;
@RequestMapping("/role/{id}")
public Role getRoleById(@PathVariable int id){
Role role = roleMapper.getRoleById(id);
System.out.println(role);
return role;
}
}
三、 mybatis-plus方式配置多数据源
思路:原理使用的是aop,具体实现原理可看我另一篇文章,其他配置可看mybatis-plus官方文档mybatis-plus
ps:以下代码忽略mybatis-plus的mapper
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.22</version>
</dependency>
<!--多数据源-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
项目结构:
#设置默认的数据源,默认值即为master
spring.datasource.dynamic.primary=master
#严格匹配数据源,默认false。 true:未匹配到指定数据源时抛异常 false:未匹配到则使用默认数据源
spring.datasource.dynamic.strict=false
spring.datasource.dynamic.datasource.master.url=jdbc:mysql://localhost:3306/db1
spring.datasource.dynamic.datasource.master.username=root
spring.datasource.dynamic.datasource.master.password=123456
spring.datasource.dynamic.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
#设置第二个数据源
spring.datasource.dynamic.datasource.slave.url=jdbc:mysql://localhost:3306/db2
spring.datasource.dynamic.datasource.slave.username=root
spring.datasource.dynamic.datasource.slave.password=123456
spring.datasource.dynamic.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver
mybatis-plus.type-aliases-package=com.hs.entity
3.1快速开始
使用@DS(“库名”) 在方法或者类上切换数据源,如果在方法上没有添加@DS,那么默认在方法中使用的是默认数据源,也就是上面配置spring.datasource.dynamic.primary=master
的master数据源
举例1:根据id查询user表数据,user表为db1数据库的一张表,上述配置中的master数据源
@RestController
public class TestController {
@Autowired
private UserMapper userMapper;
//由于没有加@DS,那么使用默认数据源,也可以指定@DS("master")
@RequestMapping("/user/{id}")
public User getUserById(@PathVariable int id){
User user = userMapper.selectById(id);
System.out.println(user);
return user;
}
}
举例2:根据id查询role表数据,role表为db2数据库的一张表,上述配置中的slave数据源
@RestController
public class TestController {
@Autowired
private RoleMapper roleMapper;
//指定slave数据源
@DS("slave")
@RequestMapping("/role/{id}")
public Role getRoleById(@PathVariable int id){
Role role = roleMapper.selectById(id);
System.out.println(role);
return role;
}
}
举例3:手动切换数据源,在一个方法中,查询两个数据库。根据id查询user表和role表数据
@RestController
public class TestController {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleMapper roleMapper;
@RequestMapping("/test/{id}")
public String getDataById(@PathVariable int id){
//不指定现在默认使用的是master数据源
User user = userMapper.selectById(id);
//该方法作用是切换成slave数据源,只能有一个数据源,如果再添加,会替换之前的数据源
DynamicDataSourceContextHolder.push("slave");
Role role = roleMapper.selectById(id);
System.out.println(user);
System.out.println(role);
//查看当前数据源
System.out.println(DynamicDataSourceContextHolder.peek());
//移除数据源,恢复master数据源
DynamicDataSourceContextHolder.poll();
return "success";
}
}
3.2 DynamicDataSourceContextHolder解读
看源码得知,内部是由一个ThreadLocal来维护数据源的,也就是说手动切换数据源只在一个线程内有效,如果开一个新线程,那么数据源又变回默认数据源
验证如下:
@RestController
public class TestController {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleMapper roleMapper;
@RequestMapping("/test")
public String test(){
//使用默认数据源查询
System.out.println(userMapper.selectList(null));
//在当前线程切换到slave数据源
DynamicDataSourceContextHolder.push("slave");
//新启动一个线程,又恢复成默认数据源,role表在slave数据源中,所以此时查询会报错
new Thread(() ->{
System.out.println(roleMapper.selectList(null));
}).start();
return "success";
}
}
四、总述
如果你的项目中使用的是mybatis,那么可以用上面这两种方式来操作多数据源
如果需要自己实现一个,那么需要使用AOP来实现,后续会出一篇文章《AOP动态切换数据源》