最近公司一个项目需要连接两个数据库(A和B)操作,有的模块查询A库,有的模块查询B库,因此需要改造下,项目后台用的是SpringMVC+Mybatis+MySQL架构,折腾了两天后终于搞定了,在这里记录过改造过程。
使用场景
多数据源的使用的场景一般有:
- 主从数据库切换
- 读写分离
- 兼容旧库
实现原理
Spring2.x的版本中采用Proxy模式,就是在方案中实现一个虚拟的数据源,并且用它来封装数据源选择逻辑,这样就可以有效地将数据源选择逻辑从Client中分离出来。Client提供选择所需的上下文,由虚拟的DynamicDataSource根据Client提供的上下文来实现数据源的选择。
具体的实现是虚拟的DynamicDataSource仅需继承AbstractRoutingDataSource实现determineCurrentLookupKey(),该方法返回需要使用的DataSource的key值,然后根据这个key从resolvedDataSources这个map里取出对应的DataSource,如果找不到则用默认的resolvedDefaultDataSource。
详细实现过程
- 修改spring的配置文件
配置文件里需要配置多个数据源,改造后主要配置如下:
<bean id=\"dataSourceTargetA\" class=\"com.mchange.v2.c3p0.ComboPooledDataSource\" destroy-method=\"close\" scope=\"singleton\"> <property name=\"driverClass\" value=\"${jdbc.a.driverClassName}\" /> <property name=\"jdbcUrl\" value=\"${jdbc.a.url}\" /> <property name=\"user\" value=\"${jdbc.a.username}\" /> <property name=\"password\" value=\"${jdbc.a.password}\" /> <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 --> <property name=\"acquireIncrement\" value=\"${c3p0.acquireIncrement}\"></property> <!--初始化时获取三个连接,取值应在minPoolSize与maxPoolSize之间。Default: 3 --> <property name=\"initialPoolSize\" value=\"${c3p0.initialPoolSize}\"></property> <!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 --> <property name=\"maxIdleTime\" value=\"${c3p0.maxIdleTime}\"></property> <!--连接池中保留的最大连接数。Default: 15 --> <property name=\"maxPoolSize\" value=\"${c3p0.maxPoolSize}\"></property> <property name=\"minPoolSize\" value=\"${c3p0.minPoolSize}\"></property> <!--两次连接中间隔时间,单位毫秒。Default: 1000 --> <property name=\"acquireRetryDelay\" value=\"1000\"></property> <!--定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 --> <property name=\"acquireRetryAttempts\" value=\"60\"></property> <!--获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试获取连接失败后该数据源将申明已断开并永久关闭。Default: false --> <property name=\"breakAfterAcquireFailure\" value=\"false\"></property> </bean> <bean id=\"dataSourceTargetB\" class=\"com.mchange.v2.c3p0.ComboPooledDataSource\" destroy-method=\"close\" scope=\"singleton\"> <property name=\"driverClass\" value=\"${jdbc.b.driverClassName}\" /> <property name=\"jdbcUrl\" value=\"${jdbc.b.url}\" /> <property name=\"user\" value=\"${jdbc.b.username}\" /> <property name=\"password\" value=\"${jdbc.b.password}\" /> <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 --> <property name=\"acquireIncrement\" value=\"${c3p0.acquireIncrement}\"></property> <!--初始化时获取三个连接,取值应在minPoolSize与maxPoolSize之间。Default: 3 --> <property name=\"initialPoolSize\" value=\"${c3p0.initialPoolSize}\"></property> <!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 --> <property name=\"maxIdleTime\" value=\"${c3p0.maxIdleTime}\"></property> <!--连接池中保留的最大连接数。Default: 15 --> <property name=\"maxPoolSize\" value=\"${c3p0.maxPoolSize}\"></property> <property name=\"minPoolSize\" value=\"${c3p0.minPoolSize}\"></property> <!--两次连接中间隔时间,单位毫秒。Default: 1000 --> <property name=\"acquireRetryDelay\" value=\"1000\"></property> <!--定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 --> <property name=\"acquireRetryAttempts\" value=\"60\"></property> <!--获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试获取连接失败后该数据源将申明已断开并永久关闭。Default: false --> <property name=\"breakAfterAcquireFailure\" value=\"false\"></property> </bean> <!-- 动态数据源 --> <bean id=\"dynamicDataSource\" class=\"com.test.util.DynamicDataSource\" > <property name=\"targetDataSources\"> <map key-type=\"java.lang.String\"> <entry value-ref=\"dataSourceTargetA\" key=\"dataSourceTargetA\"></entry> <entry value-ref=\"dataSourceTargetB\" key=\"dataSourceTargetB\"></entry> </map> </property> <property name=\"defaultTargetDataSource\" ref=\"dataSourceTargetA\" > </property> </bean> <bean id=\"txManager\" class=\"org.springframework.jdbc.datasource.DataSourceTransactionManager\"> <property name=\"dataSource\" ref=\"dynamicDataSource\" /> </bean> <!-- MyBatis ORM operation class --> <bean id=\"sqlSessionFactory\" class=\"org.mybatis.spring.SqlSessionFactoryBean\"> <property name=\"dataSource\" ref=\"dynamicDataSource\" /> <property name=\"mapperLocations\"> <list> <value>classpath*:com/test/**/*Mapper.xml</value> </list> </property> <property name=\"configLocation\" value=\"classpath:provider-sql-map-config.xml\" /> </bean>