大家可以看到两个数据源就是这两个方法,第一个方法是biz数据库对应的数据源bizDataSource,第二个方法是guns数据库对应的数据源,他们对应的方法都是通过properties文件,之前介绍springboot的时候已经介绍过了,他们这个properties运行原理是通过读取yml里面的这些值,然后注入到这个properties不同的属性里面。
然后看一下它们是怎么配置的,怎么配置的就是通过这个config方法,这个config方法其实就是一系列的set,可以把spring初始化的这些值set到数据源里面。大家可以看到当用的时候,它会把这个datasource set进去properties这些值。大家可以看到这都是一系列的set方法。然后这样就配置了两个数据源。
。的意思就是决定当前使用哪个数据源。通过字面意思就是说他是一个路由datasource,执行当操作数据库的时候需要告诉它当前使用哪一个数据库,然后这个决定当前使用的哪个datasource。然后它是如何决定的?通过这个方法,决定当前的一个key,这个key就是在配置这个的时候注入的这个key,下面大家可以看一下这个方法,这个方法是配置DynamicDataSource,大家可以看到这个方法里面首先初始化了这两个数据源
,初始化了之后,它又初始化了一个map,map的key就是数据源的标识,大家可以看到guns数据源的标识定义了这样一个常量,biz的数据源定义了另外一个常量,那么以这个常量作为key标识这个数据源,然后set了一个TargetDataSource,就是设定了这个路由将会跳转的一个dataSource,就是当请求数据库的时候,会通过路由dataSource决定当前使用哪一个datasource,通过这个方法决定的。这个方法是如何决定使用哪个数据源呢?这里有一个,这个holder里面就是放的当前环境会使用哪一个数据源。这个类面大家可以看到,它用了一个ThreadLocal,ThreadLocal的意思就是为每一个线程创建一个副本,每一个线程里面的这一个变量的值都不会被其他线程影响。ThreaLocal这个变量来控制当前的dataSource是哪一个dataSource,然后设置这个dataSource的时候调用这个set,获取的时候通过get,然后清除。
这个就是刚才定义的这个枚举里面的常量,
,默认的就是我们的guns数据源
,,这里就相当于设定了一个开关,如果是false的话,就配置了一个单数据的数据源。
。这个AOP的作用就是你在写service的时候决定你当前的service使用哪一个数据源,然后这个AOP是通过注解的方式来拦截的的,大家可以通过扫描包的方式,就是说直接配置一个包的路径,当service包的方法执行的时候,他会走这个逻辑(AOP),这里是通过注解方式,当你标注了annotation的时候,这个AOP才会执行,拦截的是这个dataSource。
然后看一下这个aop的具体内容,
首先这个aop获取了当前方法执行的这个注解
,
然后获取了这个注解的名称。
这个注解里面就一个属性,这个name就是多数据源的名称,多数据源的名称就是枚举里面常量的名称,这个AOP首先获取到这个注解,获取当前方法的注解,如果注解不等于null,,如果当前有这个注解,就设置当前多数据源的名称。这里是一个切换,把它切换之后,这个DynamicDataSource才会起作用。他才会知道使用哪一个数据源,它才会知道当前应该使用哪一个数据源,然后如果这个注解它是空的话,就是默认使用guns的数据源
,数据源切换之后就会执行该方法,执行完之后,把当前数据源的名称清除掉。。
多数据源跟事物的一个顺序,多数据源的一个切换一定要放在事物的AOP之前,
一定要早于AOP的事物,这样多数据源的切换才能生效,所以说这里有一个order,这需要设置顺序,同样地也需要设置spring事物的顺序,spring事物的顺序在这里设置的
,事物要放在AOP之后,要不然事物它会不生效。
下面做一个演示,比如说测试一下biz数据源,这边写了一个测试用例
,比如说测试一下biz,这条记录就是读取id为1的这条记录,然后把它的id设成22,然后重新插入到数据库,
当前的biz表里面有一条数据,执行一个测试,刷新一下,大家可以看到又多了一条数据,,说明这个biz这个数据源生效了。
然后再测试一下guns,这个guns重新读取一条,然后再插入,执行成功后 就多了一条记录。这个记录跟预期的可能不太一样,因为这个业务本身意思是这样的,读取id为1的这条记录,然后把id设成33,然后再插入,然而这里是2,因为这里语句执行时这样的,因为我这个数据库的配置,,自增的话,他就不会写这个id了,不会写insert into test ('id')values(?).直接从id下边这个字段开始插入的。
然后再看一下这个TestAll,这里面分别调用了,两条同时执行看会不会生效,然后把他们都删掉先。执行后可以看到这两个插入命令都生效了,
如果这个两个多数据源同时调用的话,他必须是在两个不同的事务内,现在guns系统还没有做到两个数据源的操作在一个事务内完成,因为这需要引入一个分布式事务的框架,这个还没有引入,集成,所以大家在两个数据源在一个方法内操作的话,尽量避免同时写入,或者同时修改的操作,尽量是一个是读取,一个是操作。因为现在的情况是假如说,一个在失败的时候,这个是无法回滚的。这个可以回滚,而这个是无法回滚的。还有一点需要注意这个TestAll在调用的时候,假如说这个两个数据源,它这里不需要写事务。如果这里写Transactional的话,这里的操作是读取一个数据源的,读取默认的是guns的,如果这里是一个共同调度的方法,它就不要加事务。在这个里面各自加上事务。
后面会出一个分布式事务的解决方案。再把事务完善一下。