之前写过一篇极易上手的操作多数据源,基本看了就能整合实现多数据源了。
当前这篇文章,区别为,我们将会把多个数据源的信息放在一张数据库配置表 jdbc_config里面去,项目开始运行的时候,会从默认连接的数据库的这个配置表获取多数据源的信息,进行数据源的加载设置。
OK,现在开始,首先我们把项目默认数据库写下,jdbc.properties:
jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/game_message?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8 jdbc.username=root jdbc.password=root jdbc.initialSize=50 jdbc.maxActive=100 jdbc.minIdle=10 jdbc.maxIdle=100 jdbc.maxWait=60000 jdbc.validationQuery=SELECT 1 jdbc.testOnBorrow=false jdbc.testOnReturn=false jdbc.testWhileIdle=true jdbc.timeBetweenEvictionRunsMillis=30000 jdbc.minEvictableIdleTimeMillis=180000 jdbc.removeAbandoned=true jdbc.removeAbandonedTimeout=1800 jdbc.logAbandoned=true jdbc.poolPreparedStatements=false jdbc.maxOpenPreparedStatements=20 jdbc.filters=stat
这个数据库里面有什么呢?就是我们的各个数据库的相关配置信息:
jdbc_config表生成的SQL语句:
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for jdbc_config -- ---------------------------- DROP TABLE IF EXISTS `jdbc_config`; CREATE TABLE `jdbc_config` ( `id` int(11) NOT NULL AUTO_INCREMENT, `dbName` varchar(254) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `url` varchar(254) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `username` varchar(254) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `password` varchar(254) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `driverClassName` varchar(254) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `status` int(11) NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 9520 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; SET FOREIGN_KEY_CHECKS = 1;
PS:当然这个表结构不是必须固定这样的,等你看完整篇文章后就知道可以怎么修改了,表仅仅作为数据源信息的提供。
OK,数据源的表已经完成(当然你要把你的数据源信息都录入这个表里),接下来是修改xml文件,
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd "> <!--配置和创建jdbc数据源 --> <context:property-placeholder location="classpath:jdbc.properties" ignore-unresolvable="true" /> <bean id="Source" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <!-- 初始化连接大小 --> <property name="initialSize" value="${jdbc.initialSize}" /> <!-- 连接池最大使用连接数量 --> <property name="maxActive" value="${jdbc.maxActive}" /> <property name="maxIdle" value="${jdbc.maxIdle}"/> <!-- 连接池最小空闲 --> <property name="minIdle" value="${jdbc.minIdle}" /> <!-- 获取连接最大等待时间 --> <property name="maxWait" value="${jdbc.maxWait}" /> <property name="validationQuery" value="${jdbc.validationQuery}" /> <property name="testOnBorrow" value="${jdbc.testOnBorrow}" /> <property name="testOnReturn" value="${jdbc.testOnReturn}" /> <property name="testWhileIdle" value="${jdbc.testWhileIdle}" /> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}" /> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}" /> <!-- 打开removeAbandoned功能 --> <property name="removeAbandoned" value="${jdbc.removeAbandoned}" /> <!-- 1800秒,也就是30分钟 --> <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}" /> <!-- 关闭abanded连接时输出错误日志 --> <property name="logAbandoned" value="${jdbc.logAbandoned}" /> <!-- 监控数据库 --> <property name="filters" value="${jdbc.filters}" /> </bean> <bean class="com.springmvc.dynamicsource.MutiDataSourceBean" id="dataSource"> <property name="defaultTargetDataSource" ref="Source"/> </bean> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 配置会话工厂SqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 数据源 --> <property name="dataSource" ref="dataSource"/> <property name="configLocation" value="classpath:mybatis.xml" /> <property name="mapperLocations" value="classpath:sqlmap/*Mapper.xml"/> <property name="typeAliasesPackage" value="com.springmvc.entity" /> </bean> <!-- 在spring容器中配置mapper的扫描器产生的动态代理对象在spring的容器中自动注册,bean的id就是mapper类名(首字母小写)--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 指定扫描包的路径,就是mapper接口的路径,多个包中间以 半角逗号隔开 --> <property name="basePackage" value="com.springmvc.dao"/> <!-- 配置sqlSessionFactoryBeanName --> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> </beans>
这里的配置信息,需要关注的是:
第一个蓝色框的 “Source”,是给数据库起的ID;
而第二个蓝框里面的“Source”,可以结合前面的defaultTargetDataSource,这里是配置项目跑起来默认连接的数据库;
红框的“dataSource”,非常重要,这是后面手动切换数据源获取信息的地方。 所以可以看到无论是事务管理器、sql工厂都是关联了这个dataSource。
OK,接下来是我们手动实现多数据源加载切换的java类:
后面提供手动切换数据库的方法类,DynamicDataSourceHolder.java:
1.public class DynamicDataSourceHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); /** * @Description: 设置数据源类型 @param dataSourceType 数据库类型 @return void @throws */ public static void setDataSourceType(String dataSourceType) { contextHolder.set(dataSourceType); } /** * @Description: 获取数据源类型 @param @return String @throws */ public static String getDataSourceType() { return contextHolder.get(); } /** * @Description: 清除数据源类型 @param @return void @throws */ public static void clearDataSourceType() { contextHolder.remove(); } }
用于jdbc连接数据库的类,JdbcPOJO.java:
import java.io.Serializable; public class JdbcPOJO implements Serializable { private String driverClassName; private String id; private String url; private String username; private String password; public String getDriverClassName() { return driverClassName; } public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
关键的多数据源加载类,MutiDataSourceBean.java:
import com.alibaba.druid.pool.DruidDataSource; import com.springmvc.util.PropertyUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import org.springframework.jdbc.datasource.lookup.DataSourceLookup; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class MutiDataSourceBean extends AbstractRoutingDataSource implements ApplicationContextAware { private static Logger log = LoggerFactory.getLogger(MutiDataSourceBean.class); private static ApplicationContext ac; private static Map<String, String> buildDataSources = new HashMap(); @Override public void afterPropertiesSet() { log.info("初始化多数据源"); try { initailizeMutiDataSource(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } log.info("多数据源加入spring容器中成功!"); super.afterPropertiesSet(); } @Override public void setApplicationContext(ApplicationContext ctx) throws BeansException { // TODO Auto-generated method stub ac = ctx; } private List<JdbcPOJO> getServers() { List<JdbcPOJO> list = new ArrayList(); try { Class.forName(PropertyUtil.getPropery("jdbc.driverClassName")); Connection conn = DriverManager.getConnection(PropertyUtil.getPropery("jdbc.url").trim(), PropertyUtil.getPropery("jdbc.username").trim(), PropertyUtil.getPropery("jdbc.password").trim()); PreparedStatement ps = conn.prepareStatement( "SELECT id,dbName,url,username,password,driverClassName FROM jdbc_config WHERE status=0"); ResultSet rs = ps.executeQuery(); while (rs.next()) { JdbcPOJO obj = new JdbcPOJO(); obj.setId("Source" + rs.getString("dbName")); obj.setUrl(rs.getString("url")); obj.setUsername(rs.getString("username")); obj.setPassword(rs.getString("password")); obj.setDriverClassName(rs.getString("driverClassName")); list.add(obj); } if (rs != null) { rs.close(); } if (ps != null) { ps.close(); } if (conn != null) { conn.close(); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return list; } public static boolean getSourcesExistsByKey(String key) { if (buildDataSources.containsKey(key)) { return true; } else { return false; } } private void initailizeMutiDataSource() throws Exception { List<JdbcPOJO> servers = getServers(); DefaultListableBeanFactory acf = (DefaultListableBeanFactory) ac.getAutowireCapableBeanFactory(); Map<Object, DruidDataSource> dsMap = new HashMap<Object, DruidDataSource>(); for (JdbcPOJO obj : servers) { log.debug("初始化数据源:{}",obj.getId()); System.out.println("数据源加载: ----------"+obj.getId()); DruidDataSource ds = new DruidDataSource(); String id = obj.getId(); ds.setDriverClassName(obj.getDriverClassName()); ds.setUsername(obj.getUsername()); ds.setUrl(obj.getUrl()); ds.setPassword(obj.getPassword()); ds.setInitialSize(Integer.parseInt(PropertyUtil.getPropery("jdbc.initialSize").trim())); ds.setMaxActive(Integer.parseInt(PropertyUtil.getPropery("jdbc.maxActive").trim())); ds.setMinIdle(Integer.valueOf(PropertyUtil.getPropery("jdbc.minIdle").trim())); ds.setMaxWait(Integer.valueOf(PropertyUtil.getPropery("jdbc.maxWait").trim())); ds.setTestOnBorrow(Boolean.parseBoolean(PropertyUtil.getPropery("jdbc.testOnBorrow").trim())); ds.setTestOnReturn(Boolean.parseBoolean(PropertyUtil.getPropery("jdbc.testOnReturn").trim())); ds.setTestWhileIdle(Boolean.parseBoolean(PropertyUtil.getPropery("jdbc.testWhileIdle").trim())); ds.setTimeBetweenEvictionRunsMillis( Long.valueOf(PropertyUtil.getPropery("jdbc.timeBetweenEvictionRunsMillis").trim())); ds.setMinEvictableIdleTimeMillis( Long.valueOf(PropertyUtil.getPropery("jdbc.minEvictableIdleTimeMillis").trim())); ds.setRemoveAbandoned(Boolean.parseBoolean(PropertyUtil.getPropery("jdbc.removeAbandoned").trim())); ds.setRemoveAbandonedTimeout( Integer.valueOf(PropertyUtil.getPropery("jdbc.removeAbandonedTimeout").trim())); ds.setLogAbandoned(Boolean.parseBoolean(PropertyUtil.getPropery("jdbc.logAbandoned").trim())); ds.setPoolPreparedStatements( Boolean.parseBoolean(PropertyUtil.getPropery("jdbc.poolPreparedStatements").trim())); ds.setMaxOpenPreparedStatements( Integer.parseInt(PropertyUtil.getPropery("jdbc.maxOpenPreparedStatements").trim())); ds.setFilters(PropertyUtil.getPropery("jdbc.filters").trim()); acf.registerSingleton(id, ds); dsMap.put(id, ds); buildDataSources.put(id, id); } this.setTargetDataSources(dsMap); // setDefaultTargetDataSource(dsMap.get("Source"));//设置默认数据源 } @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceHolder.getDataSourceType(); } @Override public void setDataSourceLookup(DataSourceLookup dataSourceLookup) { super.setDataSourceLookup(dataSourceLookup); } @Override public void setDefaultTargetDataSource(Object defaultTargetDataSource) { super.setDefaultTargetDataSource(defaultTargetDataSource); } @Override public void setTargetDataSources(Map targetDataSources) { super.setTargetDataSources(targetDataSources); } }
简单介绍下上面这个类,项目跑起来后,简单的几步是:
然后读取数据库表jdbc_config里面的多数据源信息是:
顺便说下, 这里的id, 我项目里面写的是 Source拼接dbName的值(这个就是用于给我们切换数据源的id,规则可以自己修改):
OK,到这里多数据的整合其实已经完成了,我们来写个接口测试下,看看效果:
@Autowired MessageboardService messageboardServiceImpl; @Autowired JdbcConfigService jdbcConfigServiceImpl; @RequestMapping(value = "/testDbSource", produces = "application/json; charset=utf-8") public void testDbSource() { System.out.println("当前数据源ID:"+DynamicDataSourceHolder.getDataSourceType()); List<JdbcConfig> list1 = jdbcConfigServiceImpl.getList(); System.out.println("获取结果数:"+list1.size()); //默认是 默认连接的数据库 DynamicDataSourceHolder.setDataSourceType("Source"+"db1".trim()); //切换到数据源db1 System.out.println("当前数据源ID:"+DynamicDataSourceHolder.getDataSourceType()); Messageboard message = messageboardServiceImpl.selectByPrimaryKey(3); System.out.println(message.toString()); DynamicDataSourceHolder.setDataSourceType("Source"); //切换回来默认数据库 System.out.println("当前数据源ID:"+DynamicDataSourceHolder.getDataSourceType()); List<JdbcConfig> list2 = jdbcConfigServiceImpl.getList(); System.out.println("获取结果数:"+list2.size()); //默认数据库随便查询一个表信息 }
把项目跑起来:
用postman运行一下测试接口:
看下控制台输出情况:
刚开始,我们没有手动去切换数据源,可以看到从动态数据数据源控制器里获取的数据源为null,但是我们依然查出了数据,
这是因为我们项目里面配置了默认数据源,也就是这些数据是从默认数据库获取的。
接着我们切换到了数据源db1(规则是我们前面的数据源id规则:Source拼接dbName的值),可以看到从db1数据库成功查询出了相关的值:
最后,我们再切换回来默认数据库(默认数据源的ID就是xml文件里面的“Source”),随意切换后,查询也是正常的:
好了,到此,该篇操作多数据源的教程完毕。