在线做章网站,宁波建设局网站郑建华,网站开发语言 知乎,天河做网站开发大约在19年的这个时候#xff0c;老同事公司在做医疗系统#xff0c;需要和HIS系统对接一些信息#xff0c;比如患者、医护、医嘱、科室等信息。但是起初并不知道如何与HIS无缝对接#xff0c;于是向我取经。最终经过讨论采用了视图对接的方式#xff0c;大致就是HIS系统提… 大约在19年的这个时候老同事公司在做医疗系统需要和HIS系统对接一些信息比如患者、医护、医嘱、科室等信息。但是起初并不知道如何与HIS无缝对接于是向我取经。最终经过讨论采用了视图对接的方式大致就是HIS系统提供视图他们进行对接。什么是多数据源最常见的单一应用中最多涉及到一个数据库即是一个数据源Datasource。那么顾名思义多数据源就是在一个单一应用中涉及到了两个及以上的数据库了。其实在配置数据源的时候就已经很明确这个定义了如以下代码Bean(name dataSource)public DataSource dataSource() {DruidDataSource druidDataSource new DruidDataSource();druidDataSource.setUrl(url);druidDataSource.setUsername(username);druidDataSource.setDriverClassName(driverClassName);druidDataSource.setPassword(password);return druidDataSource;}“url、username、password这三个属性已经唯一确定了一个数据库了DataSource则是依赖这三个创建出来的。则多数据源即是配置多个DataSource暂且这么理解。”何时用到多数据源正如前言介绍到的一个场景相信大多数做过医疗系统的都会和HIS打交道为了简化护士以及医生的操作流程必须要将必要的信息从HIS系统对接过来据我了解的大致有两种方案如下HIS提供视图比如医护视图、患者视图等而此时其他系统只需要定时的从HIS视图中读取数据同步到自己数据库中即可。HIS提供接口无论是webService还是HTTP形式都是可行的此时其他系统只需要按照要求调接口即可。“很明显第一种方案涉及到了至少两个数据库了一个是HIS数据库一个自己系统的数据库在单一应用中必然需要用到多数据源的切换才能达到目的。”当然多数据源的使用场景还是有很多的以上只是简单的一个场景。整合单一的数据源本文使用阿里的数据库连接池druid添加依赖如下!--druid连接池--
dependencygroupIdcom.alibaba/groupIdartifactIddruid-spring-boot-starter/artifactIdversion1.1.9/version
/dependency阿里的数据库连接池非常强大比如数据监控、数据库加密等等内容本文仅仅演示与Spring Boot整合的过程一些其他的功能后续可以自己研究添加。Druid连接池的starter的自动配置类是DruidDataSourceAutoConfigure类上标注如下一行注解EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})“EnableConfigurationProperties这个注解使得配置文件中的配置生效并且映射到指定类的属性。”DruidStatProperties中指定的前缀是spring.datasource.druid这个配置主要是用来设置连接池的一些参数。DataSourceProperties中指定的前缀是spring.datasource这个主要是用来设置数据库的url、username、password等信息。因此我们只需要在全局配置文件中指定数据库的一些配置以及连接池的一些配置信息即可前缀分别是spring.datasource.druid、spring.datasource以下是个人随便配置的(application.properties)spring.datasource.urljdbc\:mysql\://120.26.101.xxx\:3306/xxx?useUnicode\truecharacterEncoding\UTF-8zeroDateTimeBehavior\convertToNulluseSSL\falseallowMultiQueries\trueserverTimezoneAsia/Shanghai
spring.datasource.usernameroot
spring.datasource.passwordxxxx
spring.datasource.typecom.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-namecom.mysql.jdbc.Driver
#初始化连接大小
spring.datasource.druid.initial-size0
#连接池最大使用连接数量
spring.datasource.druid.max-active20
#连接池最小空闲
spring.datasource.druid.min-idle0
#获取连接最大等待时间
spring.datasource.druid.max-wait6000
spring.datasource.druid.validation-querySELECT 1
#spring.datasource.druid.validation-query-timeout6000
spring.datasource.druid.test-on-borrowfalse
spring.datasource.druid.test-on-returnfalse
spring.datasource.druid.test-while-idletrue
#配置间隔多久才进行一次检测检测需要关闭的空闲连接单位是毫秒
spring.datasource.druid.time-between-eviction-runs-millis60000
#置一个连接在池中最小生存的时间单位是毫秒
spring.datasource.druid.min-evictable-idle-time-millis25200000
#spring.datasource.druid.max-evictable-idle-time-millis
#打开removeAbandoned功能,多少时间内必须关闭连接
spring.datasource.druid.removeAbandonedtrue
#1800秒也就是30分钟
spring.datasource.druid.remove-abandoned-timeout1800
#!-- 1800秒也就是30分钟 --
spring.datasource.druid.log-abandonedtrue
spring.datasource.druid.filtersmergeStat“在全局配置文件application.properties文件中配置以上的信息即可注入一个数据源到Spring Boot中。其实这仅仅是一种方式下面介绍另外一种方式。”在自动配置类中DruidDataSourceAutoConfigure中有如下一段代码Bean(initMethod init)ConditionalOnMissingBeanpublic DataSource dataSource() {LOGGER.info(Init DruidDataSource);return new DruidDataSourceWrapper();}“ConditionalOnMissingBean和Bean这两个注解的结合意味着我们可以覆盖只需要提前在IOC中注入一个DataSource类型的Bean即可。”因此我们在自定义的配置类中定义如下配置即可/*** Bean向IOC容器中注入一个Bean* ConfigurationProperties使得配置文件中以spring.datasource为前缀的属性映射到Bean的属性中* return*/ConfigurationProperties(prefix spring.datasource)Beanpublic DataSource dataSource(){//做一些其他的自定义配置比如密码加密等......return new DruidDataSource();}“以上介绍了两种数据源的配置方式第一种比较简单第二种适合扩展按需选择。”整合MybatisSpring Boot 整合Mybatis其实很简单简单的几步就搞定首先添加依赖dependencygroupIdorg.mybatis.spring.boot/groupIdartifactIdmybatis-spring-boot-starter/artifactIdversion2.0.0/version
/dependency第二步找到自动配置类MybatisAutoConfiguration有如下一行代码EnableConfigurationProperties(MybatisProperties.class)“老套路了全局配置文件中配置前缀为mybatis的配置将会映射到该类中的属性。”可配置的东西很多比如XML文件的位置、类型处理器等等如下简单的配置mybatis.type-handlers-packagecom.demo.typehandler
mybatis.configuration.map-underscore-to-camel-casetrue如果需要通过包扫描的方式注入Mapper则需要在配置类上加入一个注解MapperScan其中的value属性指定需要扫描的包。“直接在全局配置文件配置各种属性是一种比较简单的方式其实的任何组件的整合都有不少于两种的配置方式下面来介绍下配置类如何配置。”MybatisAutoConfiguration自动配置类有如下一断代码BeanConditionalOnMissingBeanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {}“ConditionalOnMissingBean和Bean真是老搭档了意味着我们又可以覆盖只需要在IOC容器中注入SqlSessionFactoryMybatis六剑客之一生产者。”在自定义配置类中注入即可如下/*** 注入SqlSessionFactory*/Bean(sqlSessionFactory1)public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {SqlSessionFactoryBean sqlSessionFactoryBean new SqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dataSource);sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(classpath*:/mapper/**/*.xml));org.apache.ibatis.session.Configuration configuration new org.apache.ibatis.session.Configuration();// 自动将数据库中的下划线转换为驼峰格式configuration.setMapUnderscoreToCamelCase(true);configuration.setDefaultFetchSize(100);configuration.setDefaultStatementTimeout(30);sqlSessionFactoryBean.setConfiguration(configuration);return sqlSessionFactoryBean.getObject();}“以上介绍了配置Mybatis的两种方式其实在大多数场景中使用第一种已经够用了至于为什么介绍第二种呢当然是为了多数据源的整合而做准备了。”在MybatisAutoConfiguration中有一行很重要的代码如下ConditionalOnSingleCandidate(DataSource.class)“ConditionalOnSingleCandidate这个注解的意思是当IOC容器中只有一个候选Bean的实例才会生效。”这行代码标注在Mybatis的自动配置类中有何含义呢下面介绍哈哈哈~多数据源如何整合上文留下的问题为什么的Mybatis自动配置上标注如下一行代码ConditionalOnSingleCandidate(DataSource.class)“以上这行代码的言外之意当IOC容器中只有一个数据源DataSource这个自动配置类才会生效。”哦照这样搞多数据源是不能用Mybatis吗可能大家会有一个误解认为多数据源就是多个的DataSource并存的当然这样说也不是不正确。“多数据源的情况下并不是多个数据源并存的Spring提供了AbstractRoutingDataSource这样一个抽象类使得能够在多数据源的情况下任意切换相当于一个动态路由的作用作者称之为动态数据源。因此Mybatis只需要配置这个动态数据源即可。”什么是动态数据源动态数据源简单的说就是能够自由切换的数据源类似于一个动态路由的感觉Spring 提供了一个抽象类AbstractRoutingDataSource这个抽象类中哟一个属性如下private MapObject, Object targetDataSources;“targetDataSources是一个Map结构所有需要切换的数据源都存放在其中根据指定的KEY进行切换。当然还有一个默认的数据源。”AbstractRoutingDataSource这个抽象类中有一个抽象方法需要子类实现如下protected abstract Object determineCurrentLookupKey();“determineCurrentLookupKey()这个方法的返回值决定了需要切换的数据源的KEY就是根据这个KEY从targetDataSources取值数据源。”数据源切换如何保证线程隔离数据源属于一个公共的资源在多线程的情况下如何保证线程隔离呢不能我这边切换了影响其他线程的执行。“说到线程隔离自然会想到ThreadLocal了将切换数据源的KEY用于从targetDataSources中取值存储在ThreadLocal中执行结束之后清除即可。”单独封装了一个DataSourceHolder内部使用ThreadLocal隔离线程代码如下/*** 使用ThreadLocal存储切换数据源后的KEY*/
public class DataSourceHolder {//线程 本地环境private static final ThreadLocalString dataSources new InheritableThreadLocal();//设置数据源public static void setDataSource(String datasource) {dataSources.set(datasource);}//获取数据源public static String getDataSource() {return dataSources.get();}//清除数据源public static void clearDataSource() {dataSources.remove();}
}如何构造一个动态数据源上文说过只需继承一个抽象类AbstractRoutingDataSource重写其中的一个方法determineCurrentLookupKey()即可。代码如下/*** 动态数据源继承AbstractRoutingDataSource*/
public class DynamicDataSource extends AbstractRoutingDataSource {/*** 返回需要使用的数据源的key将会按照这个KEY从Map获取对应的数据源切换* return*/Overrideprotected Object determineCurrentLookupKey() {//从ThreadLocal中取出KEYreturn DataSourceHolder.getDataSource();}/*** 构造方法填充Map构建多数据源*/public DynamicDataSource(DataSource defaultTargetDataSource, MapObject, Object targetDataSources) {//默认的数据源可以作为主数据源super.setDefaultTargetDataSource(defaultTargetDataSource);//目标数据源super.setTargetDataSources(targetDataSources);//执行afterPropertiesSet方法完成属性的设置super.afterPropertiesSet();}
}上述代码很简单分析如下一个多参的构造方法指定了默认的数据源和目标数据源。重写determineCurrentLookupKey()方法返回数据源对应的KEY这里是直接从ThreadLocal中取值就是上文封装的DataSourceHolder。定义一个注解为了操作方便且低耦合不能每次需要切换的数据源的时候都要手动调一下接口吧可以定义一个切换数据源的注解如下/*** 切换数据源的注解*/
Target(value ElementType.METHOD)
Retention(value RetentionPolicy.RUNTIME)
Documented
public interface SwitchSource {/*** 默认切换的数据源KEY*/String DEFAULT_NAME hisDataSource;/*** 需要切换到数据的KEY*/String value() default DEFAULT_NAME;
}注解中只有一个value属性指定了需要切换数据源的KEY。有注解还不行当然还要有切面代码如下Aspect
//优先级要设置在事务切面执行之前
Order(1)
Component
Slf4j
public class DataSourceAspect {Pointcut(annotation(SwitchSource))public void pointcut() {}/*** 在方法执行之前切换到指定的数据源* param joinPoint*/Before(value pointcut())public void beforeOpt(JoinPoint joinPoint) {/*因为是对注解进行切面所以这边无需做过多判定直接获取注解的值进行环绕将数据源设置成远方然后结束后清楚当前线程数据源*/Method method ((MethodSignature) joinPoint.getSignature()).getMethod();SwitchSource switchSource method.getAnnotation(SwitchSource.class);log.info([Switch DataSource]: switchSource.value());DataSourceHolder.setDataSource(switchSource.value());}/*** 方法执行之后清除掉ThreadLocal中存储的KEY这样动态数据源会使用默认的数据源*/After(value pointcut())public void afterOpt() {DataSourceHolder.clearDataSource();log.info([Switch Default DataSource]);}
}这个ASPECT很容易理解beforeOpt()在方法之前执行取值SwitchSource中value属性设置到ThreadLocal中;afterOpt()方法在方法执行之后执行清除掉ThreadLocal中的KEY保证了如果不切换数据源则用默认的数据源。如何与Mybatis整合单一数据源与Mybatis整合上文已经详细讲解了数据源DataSource作为参数构建了SqlSessionFactory同样的思想只需要把这个数据源换成动态数据源即可。注入的代码如下/*** 创建动态数据源的SqlSessionFactory传入的是动态数据源* Primary这个注解很重要如果项目中存在多个SqlSessionFactory这个注解一定要加上*/PrimaryBean(sqlSessionFactory2)public SqlSessionFactory sqlSessionFactoryBean(DynamicDataSource dynamicDataSource) throws Exception {SqlSessionFactoryBean sqlSessionFactoryBean new SqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dynamicDataSource);sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(classpath*:/mapper/**/*.xml));org.apache.ibatis.session.Configuration configuration new org.apache.ibatis.session.Configuration();configuration.setMapUnderscoreToCamelCase(true);configuration.setDefaultFetchSize(100);configuration.setDefaultStatementTimeout(30);sqlSessionFactoryBean.setConfiguration(configuration);return sqlSessionFactoryBean.getObject();}“与Mybatis整合很简单只需要把数据源替换成自定义的动态数据源DynamicDataSource。”那么动态数据源如何注入到IOC容器中呢看上文自定义的DynamicDataSource构造方法肯定需要两个数据源了因此必须先注入两个或者多个数据源到IOC容器中如下/*** Bean向IOC容器中注入一个Bean* ConfigurationProperties使得配置文件中以spring.datasource为前缀的属性映射到Bean的属性中*/ConfigurationProperties(prefix spring.datasource)Bean(dataSource)public DataSource dataSource(){return new DruidDataSource();}/*** 向IOC容器中注入另外一个数据源* 全局配置文件中前缀是spring.datasource.his*/Bean(name SwitchSource.DEFAULT_NAME)ConfigurationProperties(prefix spring.datasource.his)public DataSource hisDataSource() {return DataSourceBuilder.create().build();}“以上构建的两个数据源一个是默认的数据源一个是需要切换到的数据源targetDataSources这样就组成了动态数据源了。数据源的一些信息比如urlusername需要自己在全局配置文件中根据指定的前缀配置即可代码不再贴出。”动态数据源的注入代码如下/*** 创建动态数据源的SqlSessionFactory传入的是动态数据源* Primary这个注解很重要如果项目中存在多个SqlSessionFactory这个注解一定要加上*/PrimaryBean(sqlSessionFactory2)public SqlSessionFactory sqlSessionFactoryBean(DynamicDataSource dynamicDataSource) throws Exception {SqlSessionFactoryBean sqlSessionFactoryBean new SqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dynamicDataSource);org.apache.ibatis.session.Configuration configuration new org.apache.ibatis.session.Configuration();configuration.setMapUnderscoreToCamelCase(true);configuration.setDefaultFetchSize(100);configuration.setDefaultStatementTimeout(30);sqlSessionFactoryBean.setConfiguration(configuration);return sqlSessionFactoryBean.getObject();}“这里还有一个问题IOC中存在多个数据源了那么事务管理器怎么办呢它也懵逼了到底选择哪个数据源呢因此事务管理器肯定还是要重新配置的。”事务管理器此时管理的数据源将是动态数据源DynamicDataSource配置如下/*** 重写事务管理器管理动态数据源*/PrimaryBean(value transactionManager2)public PlatformTransactionManager annotationDrivenTransactionManager(DynamicDataSource dataSource) {return new DataSourceTransactionManager(dataSource);}至此Mybatis与多数据源的整合就完成了。演示使用也是很简单在需要切换数据源的方法上方标注SwitchSource切换到指定的数据源即可如下//不开启事务Transactional(propagation Propagation.NOT_SUPPORTED)//切换到HIS的数据源SwitchSourceOverridepublic ListDeptInfo list() {return hisDeptInfoMapper.listDept();}这样只要执行到这方法将会切换到HIS的数据源方法执行结束之后将会清除执行默认的数据源。总结本篇文章讲了Spring Boot与单数据源、Mybatis、多数据源之间的整合希望这篇文章能够帮助读者理解多数据源的整合虽说用的不多但是在有些领域仍然是比较重要的。往期推荐Spring Boot 中实现跨域的 5 种方式你一定要知道下个十年高性能 JSON 库来了fastjson2ThreadLocal夺命11连问