From 06aa27eac9381f65f73ce5f2bc17e61b59299fba Mon Sep 17 00:00:00 2001 From: caoshd Date: Sun, 3 Mar 2024 22:29:51 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96=E5=B7=A5=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 38 ++++++++ pom.xml | 91 +++++++++++++++++++ .../space/caoshd/multi_ds/Application.java | 11 +++ .../framework/config/DataSourceRegister.java | 61 +++++++++++++ .../framework/config/FilterConfig.java | 24 +++++ .../framework/entity/DataSourceEntity.java | 22 +++++ .../filter/DataSourceSwitchFilter.java | 32 +++++++ .../framework/mapper/DatasourceMapper.java | 14 +++ src/main/resources/application.yml | 41 +++++++++ src/main/resources/schema.sql | 41 +++++++++ .../caoshd/multi_ds/entity/TenantEntity.java | 10 ++ .../caoshd/multi_ds/mapper/TenantMapper.java | 25 +++++ .../multi_ds/service/DatasourceTest.java | 91 +++++++++++++++++++ 13 files changed, 501 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/space/caoshd/multi_ds/Application.java create mode 100644 src/main/java/space/caoshd/multi_ds/framework/config/DataSourceRegister.java create mode 100644 src/main/java/space/caoshd/multi_ds/framework/config/FilterConfig.java create mode 100644 src/main/java/space/caoshd/multi_ds/framework/entity/DataSourceEntity.java create mode 100644 src/main/java/space/caoshd/multi_ds/framework/filter/DataSourceSwitchFilter.java create mode 100644 src/main/java/space/caoshd/multi_ds/framework/mapper/DatasourceMapper.java create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/schema.sql create mode 100644 src/test/java/space/caoshd/multi_ds/entity/TenantEntity.java create mode 100644 src/test/java/space/caoshd/multi_ds/mapper/TenantMapper.java create mode 100644 src/test/java/space/caoshd/multi_ds/service/DatasourceTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..5b9dc25 --- /dev/null +++ b/pom.xml @@ -0,0 +1,91 @@ + + + + 4.0.0 + + space.caoshd + multi-ds + 1.0.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 3.2.1 + + + + + 17 + 17 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 3.0.3 + + + com.alibaba + druid-spring-boot-starter + 1.2.21 + + + com.baomidou + dynamic-datasource-spring-boot3-starter + 4.2.0 + + + mysql + mysql-connector-java + 8.0.31 + + + com.h2database + h2 + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.mybatis.spring.boot + mybatis-spring-boot-starter-test + 3.0.3 + test + + + + + multi-ds + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + \ No newline at end of file diff --git a/src/main/java/space/caoshd/multi_ds/Application.java b/src/main/java/space/caoshd/multi_ds/Application.java new file mode 100644 index 0000000..28d80b5 --- /dev/null +++ b/src/main/java/space/caoshd/multi_ds/Application.java @@ -0,0 +1,11 @@ +package space.caoshd.multi_ds; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} \ No newline at end of file diff --git a/src/main/java/space/caoshd/multi_ds/framework/config/DataSourceRegister.java b/src/main/java/space/caoshd/multi_ds/framework/config/DataSourceRegister.java new file mode 100644 index 0000000..925c7e6 --- /dev/null +++ b/src/main/java/space/caoshd/multi_ds/framework/config/DataSourceRegister.java @@ -0,0 +1,61 @@ +package space.caoshd.multi_ds.framework.config; + +import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; +import com.baomidou.dynamic.datasource.creator.DataSourceProperty; +import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.stereotype.Component; +import space.caoshd.multi_ds.framework.entity.DataSourceEntity; +import space.caoshd.multi_ds.framework.mapper.DatasourceMapper; + +import javax.sql.DataSource; +import java.util.ArrayList; +import java.util.List; + +@Slf4j +@Component +public class DataSourceRegister implements ApplicationListener { + @Autowired + private DefaultDataSourceCreator dataSourceCreator; + @Autowired + private DatasourceMapper datasourceMapper; + @Autowired + private DynamicRoutingDataSource dynamicRoutingDataSource; + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + + List dataSources = datasourceMapper.findAll(); + + List successPoolNames = new ArrayList<>(); + for (DataSourceEntity dataSourceEntity : dataSources) { + DataSourceProperty dataSourceProperty = createDataSourceProperty(dataSourceEntity); + String poolName = dataSourceProperty.getPoolName(); + try { + DataSource dynamicDataSource = dataSourceCreator.createDataSource(dataSourceProperty); + dynamicRoutingDataSource.addDataSource(poolName, dynamicDataSource); + successPoolNames.add(poolName); + } catch (Exception e) { + log.error("create datasource: {} failed.", poolName); + } + } + + if (!successPoolNames.isEmpty()) { + log.info("{}", successPoolNames); + } + } + + private DataSourceProperty createDataSourceProperty(DataSourceEntity dataSourceEntity) { + DataSourceProperty dsProperties = new DataSourceProperty(); + dsProperties.setPoolName(dataSourceEntity.getDsName()); + dsProperties.setUrl(dataSourceEntity.getUrl()); + dsProperties.setUsername(dataSourceEntity.getUsername()); + dsProperties.setPassword(dataSourceEntity.getPassword()); + dsProperties.setDriverClassName(dataSourceEntity.getDriver()); + return dsProperties; + } + +} \ No newline at end of file diff --git a/src/main/java/space/caoshd/multi_ds/framework/config/FilterConfig.java b/src/main/java/space/caoshd/multi_ds/framework/config/FilterConfig.java new file mode 100644 index 0000000..848c74a --- /dev/null +++ b/src/main/java/space/caoshd/multi_ds/framework/config/FilterConfig.java @@ -0,0 +1,24 @@ +package space.caoshd.multi_ds.framework.config; + +import jakarta.servlet.Filter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import space.caoshd.multi_ds.framework.filter.DataSourceSwitchFilter; + +@Configuration +public class FilterConfig { + + @Autowired + private DataSourceSwitchFilter dataSourceSwitchFilter; + + @Bean + public FilterRegistrationBean dataSourceSwitchFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean<>(dataSourceSwitchFilter); + registration.addUrlPatterns("/*"); + registration.setName("dataSourceSwitchFilter"); + return registration; + } + +} diff --git a/src/main/java/space/caoshd/multi_ds/framework/entity/DataSourceEntity.java b/src/main/java/space/caoshd/multi_ds/framework/entity/DataSourceEntity.java new file mode 100644 index 0000000..b0c9948 --- /dev/null +++ b/src/main/java/space/caoshd/multi_ds/framework/entity/DataSourceEntity.java @@ -0,0 +1,22 @@ +package space.caoshd.multi_ds.framework.entity; + +import lombok.Data; + +@Data +public class DataSourceEntity { + + private Long id; + + private String dsName; + + private String driver; + + private String url; + + private String username; + + private String password; + + private String initSql; + +} diff --git a/src/main/java/space/caoshd/multi_ds/framework/filter/DataSourceSwitchFilter.java b/src/main/java/space/caoshd/multi_ds/framework/filter/DataSourceSwitchFilter.java new file mode 100644 index 0000000..880e2b7 --- /dev/null +++ b/src/main/java/space/caoshd/multi_ds/framework/filter/DataSourceSwitchFilter.java @@ -0,0 +1,32 @@ +package space.caoshd.multi_ds.framework.filter; + +import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.io.IOException; + +@Slf4j +@Component +public class DataSourceSwitchFilter implements Filter { + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws ServletException, IOException { + HttpServletRequest request = (HttpServletRequest) servletRequest; + + String dsName = request.getParameter("ds_name"); + if (StringUtils.hasText(dsName)) { + try { + DynamicDataSourceContextHolder.push(dsName); + filterChain.doFilter(servletRequest, servletResponse); + } catch (Exception e) { + log.error("switch ds error.", e); + DynamicDataSourceContextHolder.clear(); + } + } + + filterChain.doFilter(servletRequest, servletResponse); + } +} diff --git a/src/main/java/space/caoshd/multi_ds/framework/mapper/DatasourceMapper.java b/src/main/java/space/caoshd/multi_ds/framework/mapper/DatasourceMapper.java new file mode 100644 index 0000000..10eb524 --- /dev/null +++ b/src/main/java/space/caoshd/multi_ds/framework/mapper/DatasourceMapper.java @@ -0,0 +1,14 @@ +package space.caoshd.multi_ds.framework.mapper; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; +import space.caoshd.multi_ds.framework.entity.DataSourceEntity; + +import java.util.List; + +@Mapper +public interface DatasourceMapper { + @Select("select * from datasource") + List findAll(); + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..8ebffc0 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,41 @@ +#mysql environment +spring: + datasource: + druid: + stat-view-servlet: + enabled: true + loginUsername: admin + loginPassword: 123456 + allow: + web-stat-filter: + enabled: true + dynamic: + druid: + initial-size: 1 + min-idle: 2 + maxActive: 8 + maxWait: 60000 + timeBetweenEvictionRunsMillis: 60000 + minEvictableIdleTimeMillis: 300000 + validationQuery: SELECT 1 + testWhileIdle: true + testOnBorrow: false + testOnReturn: false + poolPreparedStatements: true + maxPoolPreparedStatementPerConnectionSize: 20 + filters: stat,wall,slf4j + connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000 + primary: master + strict: false + datasource: + master: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:master;INIT=RUNSCRIPT FROM 'classpath:schema.sql' + username: root + password: 123456 +mybatis: + configuration: + map-underscore-to-camel-case: true +logging: + level: + com.baomidou.dynamic.datasource: debug \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 0000000..b6b279c --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,41 @@ +DROP TABLE IF EXISTS DATASOURCE; +CREATE TABLE DATASOURCE ( + ID BIGINT, + DS_NAME VARCHAR(100), + DRIVER VARCHAR(255), + URL VARCHAR(255), + USERNAME VARCHAR(100), + PASSWORD VARCHAR(100) +); + +INSERT INTO DATASOURCE ( + ID, + DS_NAME, + DRIVER, + URL, + USERNAME, + PASSWORD +) VALUES ( + 1, + 'ds1', + 'org.h2.Driver', + 'jdbc:h2:mem:ds1', + 'ds1', + '123456' +); + +INSERT INTO DATASOURCE ( + ID, + DS_NAME, + DRIVER, + URL, + USERNAME, + PASSWORD +) VALUES ( + 2, + 'ds2', + 'org.h2.Driver', + 'jdbc:h2:mem:ds2', + 'ds2', + '123456' +); diff --git a/src/test/java/space/caoshd/multi_ds/entity/TenantEntity.java b/src/test/java/space/caoshd/multi_ds/entity/TenantEntity.java new file mode 100644 index 0000000..7fee76a --- /dev/null +++ b/src/test/java/space/caoshd/multi_ds/entity/TenantEntity.java @@ -0,0 +1,10 @@ +package space.caoshd.multi_ds.entity; + +import lombok.Data; + +@Data +public class TenantEntity { + private Long id; + private String username; + private String password; +} diff --git a/src/test/java/space/caoshd/multi_ds/mapper/TenantMapper.java b/src/test/java/space/caoshd/multi_ds/mapper/TenantMapper.java new file mode 100644 index 0000000..7ff733b --- /dev/null +++ b/src/test/java/space/caoshd/multi_ds/mapper/TenantMapper.java @@ -0,0 +1,25 @@ +package space.caoshd.multi_ds.mapper; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; +import space.caoshd.multi_ds.entity.TenantEntity; + +import java.util.List; + +@Mapper +public interface TenantMapper { + @Select("SELECT * FROM TENANT;") + List selectAll(); + + @Update("INSERT INTO TENANT (ID, USERNAME, PASSWORD) VALUES (#{tenant.id}, #{tenant.username}, #{tenant.password});") + void insert(@Param("tenant") TenantEntity tenantEntity); + + @Update("CREATE TABLE TENANT (ID BIGINT, USERNAME VARCHAR(100), PASSWORD VARCHAR(100));") + void create(); + + @Update("DROP TABLE TENANT;") + void drop(); + +} \ No newline at end of file diff --git a/src/test/java/space/caoshd/multi_ds/service/DatasourceTest.java b/src/test/java/space/caoshd/multi_ds/service/DatasourceTest.java new file mode 100644 index 0000000..8decf26 --- /dev/null +++ b/src/test/java/space/caoshd/multi_ds/service/DatasourceTest.java @@ -0,0 +1,91 @@ +package space.caoshd.multi_ds.service; + +import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; +import com.baomidou.dynamic.datasource.creator.DataSourceProperty; +import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator; +import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import space.caoshd.multi_ds.entity.TenantEntity; +import space.caoshd.multi_ds.mapper.TenantMapper; + +import javax.sql.DataSource; +import java.util.Map; + +@Slf4j +@SpringBootTest +public class DatasourceTest { + @Autowired + private DataSource dataSource; + + @Autowired + private DefaultDataSourceCreator dataSourceCreator; + + @Autowired + private TenantMapper tenantMapper; + + @Test + public void createDataSource() { + + DataSourceProperty dsProperties = new DataSourceProperty(); + dsProperties.setPoolName("ds3"); + dsProperties.setUrl("jdbc:h2:mem:ds3"); + dsProperties.setDriverClassName("org.h2.Driver"); + dsProperties.setUsername("root"); + dsProperties.setPassword("123456"); + + DataSource dataSource = dataSourceCreator.createDataSource(dsProperties); + + DynamicRoutingDataSource drDataSource = (DynamicRoutingDataSource) this.dataSource; + drDataSource.addDataSource(dsProperties.getPoolName(), dataSource); + + Map dataSources = drDataSource.getDataSources(); + for (String dsName : dataSources.keySet()) { + log.info(dsName); + } + } + + @Test + public void switchDataSource() { + + try { + DynamicDataSourceContextHolder.push("ds1"); + log.info("ds1: create tenant"); + tenantMapper.create(); + TenantEntity tenantEntity = new TenantEntity(); + tenantEntity.setId(1L); + tenantEntity.setUsername("ds2username"); + tenantEntity.setPassword("ds2password"); + log.info("ds1: insert tenant"); + tenantMapper.insert(tenantEntity); + log.info("ds1: select tenant"); + tenantMapper.selectAll(); + log.info("ds1: drop tenant"); + tenantMapper.drop(); + } finally { + DynamicDataSourceContextHolder.clear(); + } + + try { + DynamicDataSourceContextHolder.push("ds2"); + log.info("ds2: create tenant"); + tenantMapper.create(); + TenantEntity tenantEntity = new TenantEntity(); + tenantEntity.setId(2L); + tenantEntity.setUsername("ds2username"); + tenantEntity.setPassword("ds2password"); + log.info("ds2: insert tenant"); + tenantMapper.insert(tenantEntity); + log.info("ds2: select tenant"); + tenantMapper.selectAll(); + log.info("ds2: drop tenant"); + tenantMapper.drop(); + } finally { + DynamicDataSourceContextHolder.clear(); + } + + } + +} \ No newline at end of file