好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

springboot 动态数据源的实现方法(Mybatis+Druid)

spring多数据源实现的方式大概有2中,一种是新建多个mapperscan扫描不同包,另外一种则是通过继承abstractroutingdatasource实现动态路由。今天作者主要基于后者做的实现,且方式1的实现比较简单这里不做过多探讨。

实现方式

方式1的实现(核心代码):

?

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

@configuration

@mapperscan (basepackages = "com.goofly.test1" , sqlsessiontemplateref = "test1sqlsessiontemplate" )

public class datasource1config1 {

 

   @bean (name = "datasource1" )

   @configurationproperties (prefix = "spring.datasource.test1" )

   @primary

   public datasource testdatasource() {

     return datasourcebuilder.create().build();

   }

   // .....略

 

}

 

@configuration

@mapperscan (basepackages = "com.goofly.test2" , sqlsessiontemplateref = "test1sqlsessiontemplate" )

public class datasourceconfig2 {

 

   @bean (name = "datasource2" )

   @configurationproperties (prefix = "spring.datasource.test2" )

   @primary

   public datasource testdatasource() {

     return datasourcebuilder.create().build();

   }

   // .....略

 

}

方式2的实现(核心代码):

?

1

2

3

4

5

6

7

8

9

public class dynamicroutingdatasource extends abstractroutingdatasource {

   private static final logger log = logger.getlogger(dynamicroutingdatasource. class );

  

   @override

   protected object determinecurrentlookupkey() {

     //从threadlocal中取值

     return dynamicdatasourcecontextholder.get();

   }

}

第1种方式虽然实现比较加单,劣势就是不同数据源的mapper文件不能在同一包名,就显得不太灵活了。所以为了更加灵活的作为一个组件的存在,作者采用的第二种方式实现。

设计思路

当请求经过被注解修饰的类后,此时会进入到切面逻辑中。 切面逻辑会获取注解中设置的key值,然后将该值存入到threadlocal中 执行完切面逻辑后,会执行abstractroutingdatasource.determinecurrentlookupkey()方法,然后从threadlocal中获取之前设置的key值,然后将该值返回。 由于abstractroutingdatasource的targetdatasources是一个map,保存了数据源key和数据源的对应关系,所以能够顺利的找到该对应的数据源。

源码解读

org.springframework.jdbc.datasource.lookup.abstractroutingdatasource,如下:

?

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

public abstract class abstractroutingdatasource extends abstractdatasource implements initializingbean {

  

   private map<object, object> targetdatasources;

   private object defaulttargetdatasource;

   private boolean lenientfallback = true ;

   private datasourcelookup datasourcelookup = new jndidatasourcelookup();

   private map<object, datasource> resolveddatasources;

   private datasource resolveddefaultdatasource;

  

     protected datasource determinetargetdatasource() {

     assert .notnull( this .resolveddatasources, "datasource router not initialized" );

     object lookupkey = determinecurrentlookupkey();

     datasource datasource = this .resolveddatasources.get(lookupkey);

     if (datasource == null && ( this .lenientfallback || lookupkey == null )) {

       datasource = this .resolveddefaultdatasource;

     }

     if (datasource == null ) {

       throw new illegalstateexception( "cannot determine target datasource for lookup key [" + lookupkey + "]" );

     }

     return datasource;

   }

 

   /**

    * determine the current lookup key. this will typically be

    * implemented to check a thread-bound transaction context.

    * <p>allows for arbitrary keys. the returned key needs

    * to match the stored lookup key type, as resolved by the

    * {@link #resolvespecifiedlookupkey} method.

    */

   protected abstract object determinecurrentlookupkey();

  

   //........略

targetdatasources是一个map结构,保存了key与数据源的对应关系;

datasourcelookup是一个datasourcelookup类型,默认实现是jndidatasourcelookup。点开该类源码会发现,它实现了通过key获取datasource的逻辑。当然,这里可以通过setdatasourcelookup()来改变其属性,因为关于此处有一个坑,后面会讲到。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

public class jndidatasourcelookup extends jndilocatorsupport implements datasourcelookup {

 

   public jndidatasourcelookup() {

     setresourceref( true );

   }

 

   @override

   public datasource getdatasource(string datasourcename) throws datasourcelookupfailureexception {

     try {

       return lookup(datasourcename, datasource. class );

     }

     catch (namingexception ex) {

       throw new datasourcelookupfailureexception(

           "failed to look up jndi datasource with name '" + datasourcename + "'" , ex);

     }

   }

 

}

组件使用

多数据源

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

# db1

spring.datasource.master.url = jdbc:mysql: //127.0.0.1:3306/test?useunicode=true&characterencoding=utf8&usessl=false

spring.datasource.master.username = root

spring.datasource.master.password = 123456

spring.datasource.master.driverclassname = com.mysql.jdbc.driver

spring.datasource.master.validationquery = true

spring.datasource.master.testonborrow = true

## db2

spring.datasource.slave.url = jdbc:mysql: //127.0.0.1:3306/test1?useunicode=true&characterencoding=utf8&usessl=false

spring.datasource.slave.username = root

spring.datasource.slave.password = 123456

spring.datasource.slave.driverclassname = com.mysql.jdbc.driver

spring.datasource.slave.validationquery = true

spring.datasource.slave.testonborrow = true

 

#主数据源名称

spring.maindb=master

#mapperper包路径

mapper.basepackages =com.btps.xli.multidb.demo.mapper

单数据源

为了让使用者能够用最小的改动实现最好的效果,作者对单数据源的多种配置做了兼容。

示例配置1(配置数据源名称):

?

1

2

3

4

5

6

7

8

9

10

11

spring.datasource.master.url = jdbc:mysql: //127.0.0.1:3306/test?useunicode=true&characterencoding=utf8&usessl=false

spring.datasource.master.username = root

spring.datasource.master.password = 123456

spring.datasource.master.driverclassname = com.mysql.jdbc.driver

spring.datasource.master.validationquery = true

spring.datasource.master.testonborrow = true

 

# mapper包路径

mapper.basepackages = com.goofly.xli.multidb.demo.mapper

# 主数据源名称

spring.maindb=master

示例配置2(不配置数据源名称):

?

1

2

3

4

5

6

7

8

9

spring.datasource.url = jdbc:mysql: //127.0.0.1:3306/test?useunicode=true&characterencoding=utf8&usessl=false

spring.datasource.username = root

spring.datasource.password = 123456

spring.datasource.driverclassname = com.mysql.jdbc.driver

spring.datasource.validationquery = true

spring.datasource.testonborrow = true

 

# mapper包路径

mapper.basepackages = com.goofly.xli.multidb.demo.mapper

踩坑之路

多数据源的循环依赖

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

description:

 

the dependencies of some of the beans in the application context form a cycle:

 

   happinesscontroller (field private com.db.service.happinessservice com.db.controller.happinesscontroller.happinessservice)

    ↓

   happinessserviceimpl (field private com.db.mapper.masterdao com.db.service.happinessserviceimpl.masterdao)

    ↓

   masterdao defined in file [e:\gitrepository\framework-gray\test-db\target\classes\com\db\mapper\masterdao. class ]

    ↓

   sqlsessionfactory defined in class path resource [com/goofly/xli/datasource/core/dynamicdatasourceconfiguration. class ]

┌─────┐

| dynamicdatasource defined in class path resource [com/goofly/xli/datasource/core/dynamicdatasourceconfiguration. class ]

↑   ↓

| firstdatasource defined in class path resource [com/goofly/xli/datasource/core/dynamicdatasourceconfiguration. class ]

↑   ↓

| datasourceinitializer

解决方案:

在spring boot启动的时候排除datasourceautoconfiguration即可。如下:

?

1

2

3

4

5

6

@ springboot application (exclude = {datasourceautoconfiguration. class })

public class dbmain {

   public static void main(string[] args) {

     springapplication.run(dbmain. class , args);

   }

}

但是作者在创建多数据源的时候由于并未创建多个datasource的bean,而是只创建了一个即需要做 动态数据源 的那个bean。 其他的datasource则直接创建实例然后存放在map里面,然后再设置到dynamicroutingdatasource#settargetdatasources即可。

因此这种方式也不会出现循环依赖的问题!

动态刷新数据源

笔者在设计之初是想构建一个动态刷新数据源的方案,所以利用了springcloud的@refreshscope去标注数据源,然后利用refreshscope#refresh实现刷新。但是在实验的时候发现由druid创建的数据源会因此而关闭,由spring的datasourcebuilder创建的数据源则不会发生任何变化。
? 最后关于此也没能找到解决方案。同时思考,如果只能的可以实现动态刷新的话,那么数据源的原有连接会因为刷新而中断吗还是会有其他处理?

多数据源事务

有这么一种特殊情况,一个事务中调用了两个不同数据源,这个时候动态切换数据源会因此而失效。

翻阅了很多文章,大概找了2中解决方案,一种是atomikos进行事务管理,但是貌似性能并不是很理想。

另外一种则是通过优先级控制,切面的的优先级必须要大于数据源的优先级,用注解@order控制。

此处留一个坑!

git代码地址 

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

原文链接:https://segmentfault测试数据/a/1190000017989247

查看更多关于springboot 动态数据源的实现方法(Mybatis+Druid)的详细内容...

  阅读:17次