最近项目用到了spring
多数据源配置,恰好看到这篇好文章,特地分享下
1 SpringBoot分库配置
主要介绍两种整合方式,分别是 springboot+mybatis
使用分包方式整合,和 springboot+druid+mybatisplus
使用注解方式整合
1.1 准备数据
在本地新建两个数据库,名称分别为db1
和db2
,新建一张user
表,表结构如下
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(25) NOT NULL COMMENT '姓名',
`age` int(2) DEFAULT NULL COMMENT '年龄',
`sex` tinyint(1) NOT NULL DEFAULT '0' COMMENT '性别:0-男,1-女',
`addr` varchar(100) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='测试用户表'
1.2 springboot+mybatis使用分包方式整合
1.2.1 pom.xml
<?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 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.1.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>multipledatasource</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>multipledatasource</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- spring 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mysql 依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.2.2 application.yml 配置文件
server:
port: 8080 # 启动端口
spring:
datasource:
db1: # 数据源1
jdbc-url: jdbc:mysql://localhost:3306/db1?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
db2: # 数据源2
jdbc-url: jdbc:mysql://localhost:3306/db2?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
注意事项:
各个版本的 springboot
配置 datasource
时参数有所变化,例如低版本配置数据库 url
时使用 url
属性,高版本使用 jdbc-url
属性,请注意区分
1.2.3 连接数据源配置文件
1.2.3.1 连接源配置一
@Configuration
@MapperScan(basePackages = "com.example.multipledatasource.mapper.db1", sqlSessionFactoryRef = "db1SqlSessionFactory")
public class DataSourceConfig1 {
@Primary // 表示这个数据源是默认数据源, 这个注解必须要加,因为不加的话spring将分不清楚那个为主数据源(默认数据源)
@Bean("db1DataSource")
@ConfigurationProperties(prefix = "spring.datasource.db1") //读取application.yml中的配置参数映射成为一个对象
public DataSource getDb1DataSource(){
return DataSourceBuilder.create().build();
}
@Primary
@Bean("db1SqlSessionFactory")
public SqlSessionFactory db1SqlSessionFactory(@Qualifier("db1DataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
// mapper的xml形式文件位置必须要配置,不然将报错:no statement (这种错误也可能是mapper的xml中,namespace与项目的路径不一致导致)
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/db1/*.xml"));
return bean.getObject();
}
@Primary
@Bean("db1SqlSessionTemplate")
public SqlSessionTemplate db1SqlSessionTemplate(@Qualifier("db1SqlSessionFactory") SqlSessionFactory sqlSessionFactory){
return new SqlSessionTemplate(sqlSessionFactory);
}
}
1.2.3.2 连接源配置二
@Configuration
@MapperScan(basePackages = "com.example.multipledatasource.mapper.db2", sqlSessionFactoryRef = "db2SqlSessionFactory")
public class DataSourceConfig2 {
@Bean("db2DataSource")
@ConfigurationProperties(prefix = "spring.datasource.db2")
public DataSource getDb1DataSource(){
return DataSourceBuilder.create().build();
}
@Bean("db2SqlSessionFactory")
public SqlSessionFactory db1SqlSessionFactory(@Qualifier("db2DataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/db2/*.xml"));
return bean.getObject();
}
@Bean("db2SqlSessionTemplate")
public SqlSessionTemplate db1SqlSessionTemplate(@Qualifier("db2SqlSessionFactory") SqlSessionFactory sqlSessionFactory){
return new SqlSessionTemplate(sqlSessionFactory);
}
}
1.2.4 项目结构
image.png注意事项:
在 service
层中根据不同的业务注入不同的 dao
层
如果是主从复制- -读写分离:比如 db1
中负责增删改,db2
中负责查询。但是需要注意的是负责增删改的数据库必须是主库(master)
1.3 springboot+druid+mybatisplus使用注解整合
1.3.1 pom.xml
<?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 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.1.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>mutipledatasource2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mutipledatasource2</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>2.5.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.20</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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>local1</id>
<properties>
<profileActive>local1</profileActive>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>local2</id>
<properties>
<profileActive>local2</profileActive>
</properties>
</profile>
</profiles>
</project>
1.3.2 application.yml 配置文件
server:
port: 8080
spring:
datasource:
dynamic:
primary: db1 # 配置默认数据库
datasource:
db1: # 数据源1配置
url: jdbc:mysql://localhost:3306/db1?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
db2: # 数据源2配置
url: jdbc:mysql://localhost:3306/db2?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
durid:
initial-size: 1
max-active: 20
min-idle: 1
max-wait: 60000
autoconfigure:
exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 去除druid配置
DruidDataSourceAutoConfigure
会注入一个DataSourceWrapper
,其会在原生的spring.datasource
下找 url, username, password
等。动态数据源 URL
等配置是在 dynamic
下,因此需要排除,否则会报错。排除方式有两种,一种是上述配置文件排除
,还有一种可以在项目启动类排除
@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
1.3.3 使用@DS区分数据源
给使用非默认数据源
添加注解@DS
@DS
可以注解在 方法
上和 类
上,同时存在方法注解优先于类上注解。
注解在 service
实现或 mapper
接口方法上,不要同时在 service
和 mapper
注解
mapper上使用
@DS("db2")
public interface UserMapper extends BaseMapper<User> {
}
service上使用
@Service
@DS("db2")
public class ModelServiceImpl extends ServiceImpl<ModelMapper, Model> implements IModelService {}
方法上使用
@Select("SELECT * FROM user")
@DS("db2")
List<User> selectAll();
转载于:https://www.cnblogs.com/aizen-sousuke/p/11756279.html
1.4 读写分离库
读写分离库使用AbstractRoutingDataSource
AbstractRoutingDataSource
类和aop
结合,还可以用来作为读写分离库
1.4.1 AbstractRoutingDataSource原理
AbstractRoutingDataSource
原理:
-
Map<Object, Object> targetDataSources
保存了所有可能的数据源,key
为数据库的key
,value
为DataSource
对象或字符串形式的连接信息 -
Object defaultTargetDataSource
保存了默认的数据源,用于找不到具体的数据源时使用 -
afterPropertiesSet()
方法
解析targetDataSources
数据源信息成<key,DataSource>
的形式,保存在Map<Object, DataSource> resolvedDataSources
中
将defaultTargetDataSource
中的默认数据源信息解析成DataSource
对象保存在DataSource resolvedDefaultDataSource
中 -
determineCurrentLookupKey()
提供给子类重写,指定当前线程使用的具体的数据源的key
-
determineTargetDataSource()
根据determineCurrentLookupKey()
方法返回的key
返回数据源DataSouce
对象,若没有,则使用默认数据源对象 -
getConnection()
根据determineTargetDataSource()
返回的数据源,与其建立连接
1.4.2 数据源分离配置application.properties
server.port=8081
spring.datasource.jdbcUrl=jdbc:mysql://127.0.0.1:3306/spring-db-1?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=*****
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
y2021.spring.datasource.jdbcUrl=jdbc:mysql://127.0.0.1:3306/spring-db-2?useUnicode=true&characterEncoding=utf8
y2021.spring.datasource.username=root
y2021.spring.datasource.password=*****
y2021.spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
logging.level.com.ijianghu.dynamic.web=debug
1.4.3 配置数据源和事务管理
@Configuration
public class BaseDataSourceConfig {
/**
* 配置默认数据源,并设置高优先级
* @return
*/
@Primary
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource(){
return DataSourceBuilder.create().build();
}
/**
* 配置多数据源
* @return
*/
@Bean("y2021DataSource")
@ConfigurationProperties(prefix = "y2021.spring.datasource")
public DataSource y2021DataSource(){
return DataSourceBuilder.create().build();
}
/**
* 设置多数据源,配置动态路由数据源
* @return
*/
@Bean("dynamincDataSource")
public DataSource dynamincDataSource(){
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
HashMap<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put("dataSource",dataSource());
dataSourceMap.put("y2021DataSource",y2021DataSource());
//配置默认目标数据源
dynamicRoutingDataSource.setDefaultTargetDataSource(dataSource());
//设置目标数据源集合,供路由选择
dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
return dynamicRoutingDataSource;
}
/**
* 设置会话工厂
* @return
* @throws Exception
*/
@Bean(name="sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
//配置数据源为多数据源
factoryBean.setDataSource(dynamincDataSource());
//设置mybaits的xml文件路径
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/*.xml"));
return factoryBean.getObject();
}
/**
* 配置事务管理器
* @return
*/
@Bean(name="transactionManager")
public PlatformTransactionManager transactionManager(){
return new DataSourceTransactionManager(dynamincDataSource());
}
}
1.4.4 继承AbstractRoutingDataSource类,实现选择目标数据源
/**
* Multiple DataSource Configurer
*/
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
protected Object determineCurrentLookupKey() {
logger.info("Current DataSource is {}",DynamicDataSourceContextHolder.getDataSourceKey());
//从动态数据源上下文持有者里面获取
return DynamicDataSourceContextHolder.getDataSourceKey();
}
}
1.4.5 配置多数据源上下文持有者
public class DynamicDataSourceContextHolder {
private final Logger logger = LoggerFactory.getLogger(getClass());
/**
* Maintain variable for every thread, to avoid effect other thread
*/
private static ThreadLocal<String> CONTEXT_HOLDER = ThreadLocal.withInitial(DataSourceKey.dataSource::name);;
/**
* All DataSource List
*/
public static List<Object> dataSourceKeys = new ArrayList<>();
/**
* Get current DataSource
* @return data source key
*/
public static String getDataSourceKey() {
return CONTEXT_HOLDER.get();
}
public static void setDataSourceKey(String key) {
CONTEXT_HOLDER.set(key);
}
public static void clearDataSourceKey() {
CONTEXT_HOLDER.remove();
}
public static boolean containDataSourceKey(String key) {
return dataSourceKeys.contains(key);
}
public static void useSlaveDataSource(String ds) {
DataSourceKey dataSourceKey = DataSourceKey.valueOf(ds);
//if there is no suitable enum,then use default dataSource
if(dataSourceKey == null){
setDataSourceKey(DataSourceKey.dataSource.dataSourceName);
}
setDataSourceKey(dataSourceKey.dataSourceName);
}
public static void useMasterDataSource() {
CONTEXT_HOLDER.set(DataSourceKey.dataSource.dataSourceName);
}
}
DataSourceKey类
public enum DataSourceKey {
dataSource("dataSource","dataSource"),
y2021("key","y2021DataSource");
public String key;
public String dataSourceName;
DataSourceKey(String key, String dataSourceName){
this.key = key;
this.dataSourceName = dataSourceName;
}
}
1.4.6 配置AOP切面
@Aspect
@Order(-1)//Order中的数字代表启动优先级,-1是比0还有更高的优先级
@Component
public class DynamicDataSourceAspect {
private final Logger logger = LoggerFactory.getLogger(getClass());
//@annotation用于匹配当前执行方法持有指定注解的方法
@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)||" +
"@annotation(org.springframework.web.bind.annotation.GetMapping)||" +
"@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void daoAspect(){ }
@Before("daoAspect()")
public void switchDataSource(JoinPoint point){
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String ds = request.getHeader("ds");
if(ds != null){
DynamicDataSourceContextHolder.useSlaveDataSource(ds);
}else{
DynamicDataSourceContextHolder.useMasterDataSource();
}
}
}
网友评论