springboot+druid+mybatis plus的多数据源配置

springboot+druid+mybatis plus的多数据源配置

https://www.cnblogs.com/puretuo/p/11365359.html

记得面试时候,有面试官会问道,你们多数据源是怎么实现的呀。.......,一阵蒙蔽中,然后说道我们之前项目中,没有用到多数据源。

所幸,目前做得项目中有一个业务逻辑中,用到多个数据库数据情况,多数据源华丽上线。

一. mybatis plus

      因为我们项目是springboot+mybatis plus,有些人一看,mybatis还知道对吧,mybatis plus是什么鬼,其实字面意思可以理解,就是对mybatis进行一些功能改造,一些封装升级,然后用起来特别方便。

     核心功能的升级主要是以下三点:

     支持通用的 CRUD、代码生成器与条件构造器。

      通用 CRUD:定义好 Mapper 接口后,只需要继承 BaseMapper<T> 接口即可获得通用的增删改查功能,无需编写任何接口方法与配置文件
     条件构造器:通过 EntityWrapper<T> (实体包装类),可以用于拼接 SQL 语句,并且支持排序、分组查询等复杂的 SQL
     代码生成器:支持一系列的策略配置与全局配置,比 MyBatis 的代码生成更好用

二.多数据源配置开始

    思路:

  1、yml中配置多个数据源信息

  2、通过AOP切换不同数据源

  3、配合mybatis plus使用

     

1、yml配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
spring:
  aop:
      proxy-target-classtrue
      auto: true
  datasource:
    druid:
      db1:
        url: jdbc:mysql://localhost:3306/eboot
        username: root
        password: root
        driver-class-name: com.mysql.jdbc.Driver
        initialSize: 5
        minIdle: 5
        maxActive: 20
      db2:
        url: jdbc:oracle:thin:@192.168.136.222:ORCL
        username: sa
        password: sa123456
        driver-class-name: oracle.jdbc.OracleDriver
        initialSize: 5
        minIdle: 5
        maxActive: 20

  



2、启动多个数据源

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
@EnableTransactionManagement //开启事务
@Configuration  //spring中常用到注解,与xml配置相对立。是两种加载bean方式
@MapperScan("com.df.openapi.**.mapper.db*"// 扫描mapperdao的地址
public class MybatisPlusConfig {
 
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
//        paginationInterceptor.setLocalPage(true); // 由于版本问题,有些类可能招不到这个方法,需要升级jar包
        return paginationInterceptor;
    }
 
    @Bean(name = "db1")
    @ConfigurationProperties(prefix = "spring.datasource.druid.db1")
    public DataSource db1() {
        return DruidDataSourceBuilder.create().build();
    }
 
    @Bean(name = "db2")
    @ConfigurationProperties(prefix = "spring.datasource.druid.db2")
    public DataSource db2() {
        return DruidDataSourceBuilder.create().build();
    }
 
    /**
     * 动态数据源配置
     *
     * @return
     */
    @Bean
    @Primary
    public DataSource multipleDataSource(@Qualifier("db1") DataSource db1,
                                         @Qualifier("db2") DataSource db2) {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DBTypeEnum.db1.getValue(), db1);
        targetDataSources.put(DBTypeEnum.db2.getValue(), db2);
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(db2); // 程序默认数据源,这个要根据程序调用数据源频次,经常把常调用的数据源作为默认
        return dynamicDataSource;
    }
 
    @Bean("sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(multipleDataSource(db1(), db2()));
 
        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setJdbcTypeForNull(JdbcType.NULL);
        configuration.setMapUnderscoreToCamelCase(true);
        configuration.setCacheEnabled(false);
        sqlSessionFactory.setConfiguration(configuration);
// 设置xml文件路径
        sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().
                getResources("classpath*:mapper/*Mapper.xml"));
        //PerformanceInterceptor(),OptimisticLockerInterceptor()
        //添加分页功能
        sqlSessionFactory.setPlugins(new Interceptor[]{
                paginationInterceptor()
        });
//        sqlSessionFactory.setGlobalConfig(globalConfiguration()); //注释掉全局配置,因为在xml中读取就是全局配置
        return sqlSessionFactory.getObject();
    }
 
 /*   @Bean
    public GlobalConfiguration globalConfiguration() {
        GlobalConfiguration conf = new GlobalConfiguration(new LogicSqlInjector());
        conf.setLogicDeleteValue("-1");
        conf.setLogicNotDeleteValue("1");
        conf.setIdType(0);
        conf.setMetaObjectHandler(new MyMetaObjectHandler());
        conf.setDbColumnUnderline(true);
        conf.setRefresh(true);
        return conf;
    }*/
}

  

3、DBType枚举类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.df.openapi.config.db;
 
public enum DBTypeEnum {
 
    db1("db1"), db2("db2");
    private String value;
 
    DBTypeEnum(String value) {
        this.value = value;
    }
 
    public String getValue() {
        return value;
    }
}

  

4、动态数据源决策

1
2
3
4
5
package com.df.openapi.config.db;
 
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
 
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSource.class);

@Override
protected Object determineCurrentLookupKey() {
String datasource = DataSourceContextHolder.getDbType();
LOGGER.debug("使用数据源 {}", datasource);
return datasource;
}
1
}

  

5、设置、获取数据源

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
public class DataSourceContextHolder {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(DataSourceContextHolder.class);
 
    private static final ThreadLocal contextHolder = new ThreadLocal<>(); //实际上就是开启多个线程,每个线程进行初始化一个数据源
    /**
     * 设置数据源
     * @param dbTypeEnum
     */
    public static void setDbType(DBTypeEnum dbTypeEnum) {
        contextHolder.set(dbTypeEnum.getValue());
    }
 
    /**
     * 取得当前数据源
     * @return
     */
    public static String getDbType() {
        return (String) contextHolder.get();
    }
 
    /**
     * 清除上下文数据
     */
    public static void clearDbType() {
        contextHolder.remove();
    }
}

  

6、AOP实现的数据源切换

@Order设置的足够小是为了让他先执行

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
/**
 * aop的实现的数据源切换<br> * aop切点,实现mapper类找寻,找到所属大本营以后,如db1Aspect(),则会调用<br> * db1()前面之前的操作,进行数据源的切换。
 */
@Component
@Order(value = -100)
@Slf4j
@Aspect
public class DataSourceAspect {
 
    @Pointcut("execution(* com.zwyl.bazhong.dao.mapper.db1..*.*(..))")
    private void db1Aspect() {
    }
 
    @Pointcut("execution(* com.zwyl.bazhong.dao.mapper.db2..*.*(..))")
    private void db2Aspect() {
    }
 
    @Before("db1Aspect()")
    public void db1() {
        log.info("切换到db1 数据源...");
        DataSourceContextHolder.setDbType(DBTypeEnum.db1);
    }
 
    @Before("db2Aspect()")
    public void db2() {
        log.info("切换到db2 数据源...");
        DataSourceContextHolder.setDbType(DBTypeEnum.db2);
    }
}

  

7、mapper层结构

8、写一个service测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class DictServiceImpl implements IDictService {
 
    @Resource
    private PtDictMapper ptDictMapper; //来自db1
 
    @Resource
    private SysDictMapper sysDictMapper; // 来自db2
 
 
    @Override
    public void getById(String id) {
        PtDict dict = ptDictMapper.selectById("2bf6257fc8fe483c84c1ad7e89d632f6");
        SysDict sysDict = sysDictMapper.getById("49");
        System.out.println("123");
    }
}

 

9、测试结果

 

总结: 其实整个过程可以理解成,配置多数据源 xml中  -------> 然后通过加载多数源到spring工厂中-------->然后创建多线程,每个数据源对应一个数据源--------->然后实际调用时候,会先通过aop匹配到某一具体数据源------------->然后实例化当前数据源

记得面试时候,有面试官会问道,你们多数据源是怎么实现的呀。.......,一阵蒙蔽中,然后说道我们之前项目中,没有用到多数据源。

所幸,目前做得项目中有一个业务逻辑中,用到多个数据库数据情况,多数据源华丽上线。

一. mybatis plus

      因为我们项目是springboot+mybatis plus,有些人一看,mybatis还知道对吧,mybatis plus是什么鬼,其实字面意思可以理解,就是对mybatis进行一些功能改造,一些封装升级,然后用起来特别方便。

     核心功能的升级主要是以下三点:

     支持通用的 CRUD、代码生成器与条件构造器。

      通用 CRUD:定义好 Mapper 接口后,只需要继承 BaseMapper<T> 接口即可获得通用的增删改查功能,无需编写任何接口方法与配置文件
     条件构造器:通过 EntityWrapper<T> (实体包装类),可以用于拼接 SQL 语句,并且支持排序、分组查询等复杂的 SQL
     代码生成器:支持一系列的策略配置与全局配置,比 MyBatis 的代码生成更好用

二.多数据源配置开始

    思路:

  1、yml中配置多个数据源信息

  2、通过AOP切换不同数据源

  3、配合mybatis plus使用

     

1、yml配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
spring:
  aop:
      proxy-target-classtrue
      auto: true
  datasource:
    druid:
      db1:
        url: jdbc:mysql://localhost:3306/eboot
        username: root
        password: root
        driver-class-name: com.mysql.jdbc.Driver
        initialSize: 5
        minIdle: 5
        maxActive: 20
      db2:
        url: jdbc:oracle:thin:@192.168.136.222:ORCL
        username: sa
        password: sa123456
        driver-class-name: oracle.jdbc.OracleDriver
        initialSize: 5
        minIdle: 5
        maxActive: 20

  



2、启动多个数据源

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
@EnableTransactionManagement //开启事务
@Configuration  //spring中常用到注解,与xml配置相对立。是两种加载bean方式
@MapperScan("com.df.openapi.**.mapper.db*"// 扫描mapperdao的地址
public class MybatisPlusConfig {
 
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
//        paginationInterceptor.setLocalPage(true); // 由于版本问题,有些类可能招不到这个方法,需要升级jar包
        return paginationInterceptor;
    }
 
    @Bean(name = "db1")
    @ConfigurationProperties(prefix = "spring.datasource.druid.db1")
    public DataSource db1() {
        return DruidDataSourceBuilder.create().build();
    }
 
    @Bean(name = "db2")
    @ConfigurationProperties(prefix = "spring.datasource.druid.db2")
    public DataSource db2() {
        return DruidDataSourceBuilder.create().build();
    }
 
    /**
     * 动态数据源配置
     *
     * @return
     */
    @Bean
    @Primary
    public DataSource multipleDataSource(@Qualifier("db1") DataSource db1,
                                         @Qualifier("db2") DataSource db2) {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DBTypeEnum.db1.getValue(), db1);
        targetDataSources.put(DBTypeEnum.db2.getValue(), db2);
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(db2); // 程序默认数据源,这个要根据程序调用数据源频次,经常把常调用的数据源作为默认
        return dynamicDataSource;
    }
 
    @Bean("sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(multipleDataSource(db1(), db2()));
 
        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setJdbcTypeForNull(JdbcType.NULL);
        configuration.setMapUnderscoreToCamelCase(true);
        configuration.setCacheEnabled(false);
        sqlSessionFactory.setConfiguration(configuration);
        //PerformanceInterceptor(),OptimisticLockerInterceptor()
        //添加分页功能
        sqlSessionFactory.setPlugins(new Interceptor[]{
                paginationInterceptor()
        });
//        sqlSessionFactory.setGlobalConfig(globalConfiguration()); //注释掉全局配置,因为在xml中读取就是全局配置
        return sqlSessionFactory.getObject();
    }
 
 /*   @Bean
    public GlobalConfiguration globalConfiguration() {
        GlobalConfiguration conf = new GlobalConfiguration(new LogicSqlInjector());
        conf.setLogicDeleteValue("-1");
        conf.setLogicNotDeleteValue("1");
        conf.setIdType(0);
        conf.setMetaObjectHandler(new MyMetaObjectHandler());
        conf.setDbColumnUnderline(true);
        conf.setRefresh(true);
        return conf;
    }*/
}

  

3、DBType枚举类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.df.openapi.config.db;
 
public enum DBTypeEnum {
 
    db1("db1"), db2("db2");
    private String value;
 
    DBTypeEnum(String value) {
        this.value = value;
    }
 
    public String getValue() {
        return value;
    }
}

  

4、动态数据源决策

1
2
3
4
5
package com.df.openapi.config.db;
 
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
 
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSource.class);

@Override
protected Object determineCurrentLookupKey() {
String datasource = DataSourceContextHolder.getDbType();
LOGGER.debug("使用数据源 {}", datasource);
return datasource;
}
1
}

  

5、设置、获取数据源

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
public class DataSourceContextHolder {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(DataSourceContextHolder.class);
 
    private static final ThreadLocal contextHolder = new ThreadLocal<>(); //实际上就是开启多个线程,每个线程进行初始化一个数据源
    /**
     * 设置数据源
     * @param dbTypeEnum
     */
    public static void setDbType(DBTypeEnum dbTypeEnum) {
        contextHolder.set(dbTypeEnum.getValue());
    }
 
    /**
     * 取得当前数据源
     * @return
     */
    public static String getDbType() {
        return (String) contextHolder.get();
    }
 
    /**
     * 清除上下文数据
     */
    public static void clearDbType() {
        contextHolder.remove();
    }
}

  

6、AOP实现的数据源切换

@Order设置的足够小是为了让他先执行

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
/**
 * aop的实现的数据源切换<br> * aop切点,实现mapper类找寻,找到所属大本营以后,如db1Aspect(),则会调用<br> * db1()前面之前的操作,进行数据源的切换。
 */
@Component
@Order(value = -100)
@Slf4j
@Aspect
public class DataSourceAspect {
 
    @Pointcut("execution(* com.zwyl.bazhong.dao.mapper.db1..*.*(..))")
    private void db1Aspect() {
    }
 
    @Pointcut("execution(* com.zwyl.bazhong.dao.mapper.db2..*.*(..))")
    private void db2Aspect() {
    }
 
    @Before("db1Aspect()")
    public void db1() {
        log.info("切换到db1 数据源...");
        DataSourceContextHolder.setDbType(DBTypeEnum.db1);
    }
 
    @Before("db2Aspect()")
    public void db2() {
        log.info("切换到db2 数据源...");
        DataSourceContextHolder.setDbType(DBTypeEnum.db2);
    }
}

  

7、mapper层结构

8、写一个service测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class DictServiceImpl implements IDictService {
 
    @Resource
    private PtDictMapper ptDictMapper; //来自db1
 
    @Resource
    private SysDictMapper sysDictMapper; // 来自db2
 
 
    @Override
    public void getById(String id) {
        PtDict dict = ptDictMapper.selectById("2bf6257fc8fe483c84c1ad7e89d632f6");
        SysDict sysDict = sysDictMapper.getById("49");
        System.out.println("123");
    }
}

 

9、测试结果

 

总结: 其实整个过程可以理解成,配置多数据源 xml中  -------> 然后通过加载多数源到spring工厂中-------->然后创建多线程,每个数据源对应一个数据源--------->然后实际调用时候,会先通过aop匹配到某一具体数据源------------->然后实例化当前数据源

posted @ 2020-08-07 16:04  wjj1013  阅读(312)  评论(0编辑  收藏  举报