SSM框架之多数据源配置
多数据源的应用场景:主要是数据库拆分后,怎样让多个数据库结合起来来达到业务需求。 SSM框架(Spring+SpringMVC+MyBatis(MyBatis-Plus))是目前最常用的,此次仍然是maven工程。 关于这个多数据源例子,我已经上传到我的github上,地址为:https://github.com/youcong1996/study_simple_demo.git 不过需要注意的是,分支为demo1,不是主分支,如图所示: ? 如果下面的示例,你们看不懂或者不能理解,可以git clone我的地址 在编程的世界里,简洁即完美。 如何实现多数据源? 一句话,三个类加xml配置即可达到这个目的。 ? 一、编写三个类 ? AbstractDynamicDataSource.java package com.blog.datasource; import java.util.Map; javax.sql.DataSource; org.apache.commons.collections.MapUtils; org.slf4j.Logger; org.slf4j.LoggerFactory; org.springframework.beans.BeansException; org.springframework.context.ApplicationContext; org.springframework.context.ApplicationContextAware; org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * 动态数据源父类 * @create ll * @update * @updateDate */ public abstract class AbstractDynamicDataSource<T extends DataSource> extends AbstractRoutingDataSource implements ApplicationContextAware { 日志 */ protected Logger logger = LoggerFactory.getLogger(getClass()); 默认的数据源KEY protected static final String DEFAULT_DATASOURCE_KEY = "defaultDataSource"; 数据源KEY-VALUE键值对 public Map<Object,Object> targetDataSources; spring容器上下文 private static ApplicationContext ctx; void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ctx = applicationContext; } ApplicationContext getApplicationContext() { return ctx; } Object getBean(String name) { ctx.getBean(name); } * @param targetDataSources the targetDataSources to set void setTargetDataSources(Map<Object,1)"> targetDataSources) { this.targetDataSources = targetDataSources; super.setTargetDataSources(targetDataSources); // afterPropertiesSet()方法调用时用来将targetDataSources的属性写入resolvedDataSources中的 .afterPropertiesSet(); } * 创建数据源 * driverClassName 数据库驱动名称 * url 连接地址 * username 用户名 * password 密码 * @return 数据源{@link T} * @Author : ll. create at 2017年3月27日 下午2:44:34 abstract T createDataSource(String driverClassName,String url,String username,String password); * 设置系统当前使用的数据源 * <p>数据源为空或者为0时,自动切换至默认数据源,即在配置文件中定义的默认数据源 * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey() */ @Override protected Object determineCurrentLookupKey() { logger.info("【设置系统当前使用的数据源】"); Map<String,Object> configMap = DBContextHolder.getDBType(); logger.info("【当前数据源配置为:{}】",configMap); if (MapUtils.isEmpty(configMap)) { 使用默认数据源 DEFAULT_DATASOURCE_KEY; } 判断数据源是否需要初始化 this.verifyAndInitDataSource(); logger.info("【切换至数据源:{}】" configMap.get(DBContextHolder.DATASOURCE_KEY); } * 判断数据源是否需要初始化 * @Author : ll. create at 2017年3月27日 下午3:57:43 void verifyAndInitDataSource() { Map<String,1)"> DBContextHolder.getDBType(); Object obj = .targetDataSources.get(configMap.get(DBContextHolder.DATASOURCE_KEY)); if (obj != null) { ; } logger.info("【初始化数据源】"); T datasource = .createDataSource(configMap.get(DBContextHolder.DATASOURCE_DRIVER) .toString(),configMap.get(DBContextHolder.DATASOURCE_URL).toString(),configMap.get(DBContextHolder.DATASOURCE_USERNAME).toString(),configMap.get(DBContextHolder.DATASOURCE_PASSWORD).toString()); .addTargetDataSource(configMap.get(DBContextHolder.DATASOURCE_KEY).toString(),datasource); } * 往数据源key-value键值对集合添加新的数据源 * key 新的数据源键 * dataSource 新的数据源 addTargetDataSource(String key,T dataSource) { .targetDataSources.put(key,dataSource); super.setTargetDataSources(.targetDataSources); .afterPropertiesSet(); } } ? DBContextHolder.java java.util.HashMap; class DBContextHolder { 数据源的KEY final String DATASOURCE_KEY = "DATASOURCE_KEY"; 数据源的URL final String DATASOURCE_URL = "DATASOURCE_URL" 数据源的驱动 final String DATASOURCE_DRIVER = "DATASOURCE_DRIVER" 数据源的用户名 final String DATASOURCE_USERNAME = "DATASOURCE_USERNAME" 数据源的密码 final String DATASOURCE_PASSWORD = "DATASOURCE_PASSWORD"final ThreadLocal<Map<String,Object>> contextHolder = new ThreadLocal<Map<String,Object>>(); void setDBType(Map<String,1)"> dataSourceConfigMap) { contextHolder.set(dataSourceConfigMap); } static Map<String,1)"> getDBType() { Map<String,Object> dataSourceConfigMap = contextHolder.get(); if (dataSourceConfigMap == ) { dataSourceConfigMap = new HashMap<String,1)">(); } dataSourceConfigMap; } clearDBType() { contextHolder.remove(); } } ? DruidDynamicDataSource.java java.sql.SQLException; java.util.List; org.apache.commons.lang3.StringUtils; com.alibaba.druid.filter.Filter; com.alibaba.druid.pool.DruidDataSource; * Druid数据源 * @update * @updateDate class DruidDynamicDataSource extends AbstractDynamicDataSource<DruidDataSource> { boolean testWhileIdle = trueboolean testOnBorrow = falseboolean testOnReturn = 是否打开连接泄露自动检测 boolean removeAbandoned = 连接长时间没有使用,被认为发生泄露时长 long removeAbandonedTimeoutMillis = 300 * 1000 发生泄露时是否需要输出 log,建议在开启连接泄露检测时开启,方便排错 boolean logAbandoned = 只要maxPoolPreparedStatementPerConnectionSize>0,poolPreparedStatements就会被自动设定为true,使用oracle时可以设定此值。 private int maxPoolPreparedStatementPerConnectionSize = -1; 配置监控统计拦截的filters private String filters; 监控统计:"stat" 防SQL注入:"wall" 组合使用: "stat,wall" private List<Filter> filterList; /* * 创建数据源 * @see com.cdelabcare.pubservice.datasource.IDynamicDataSource#createDataSource(java.lang.String,java.lang.String,java.lang.String) public DruidDataSource createDataSource(String driverClassName,String password) { DruidDataSource parent = (DruidDataSource) .getApplicationContext().getBean( DEFAULT_DATASOURCE_KEY); DruidDataSource ds = new DruidDataSource(); ds.setUrl(url); ds.setUsername(username); ds.setPassword(password); ds.setDriverClassName(driverClassName); ds.setInitialSize(parent.getInitialSize()); ds.setMinIdle(parent.getMinIdle()); ds.setMaxActive(parent.getMaxActive()); ds.setMaxWait(parent.getMaxWait()); ds.setTimeBetweenConnectErrorMillis(parent.getTimeBetweenConnectErrorMillis()); ds.setTimeBetweenEvictionRunsMillis(parent.getTimeBetweenEvictionRunsMillis()); ds.setMinEvictableIdleTimeMillis(parent.getMinEvictableIdleTimeMillis()); ds.setValidationQuery(parent.getValidationQuery()); ds.setTestWhileIdle(testWhileIdle); ds.setTestOnBorrow(testOnBorrow); ds.setTestOnReturn(testOnReturn); ds.setRemoveAbandoned(removeAbandoned); ds.setRemoveAbandonedTimeoutMillis(removeAbandonedTimeoutMillis); ds.setLogAbandoned(logAbandoned); ds.setMaxPoolPreparedStatementPerConnectionSize(parent .getMaxPoolPreparedStatementPerConnectionSize()); (StringUtils.isNotBlank(filters)) try { ds.setFilters(filters); } catch (SQLException e) { throw RuntimeException(e); } addFilterList(ds); ds; } addFilterList(DruidDataSource ds) { if (filterList != ) { List<Filter> targetList = ds.getProxyFilters(); for (Filter add : filterList) { boolean found = ; (Filter target : targetList) { (add.getClass().equals(target.getClass())) { found = ; break; } } if (!found) targetList.add(add); } } } } ? 二、修改配置文件 主要是修改spring-mybatis.xml <!-- 配置数据源 --> <bean name="defaultDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> property ="url" value="${jdbc_url}"/> ="username"="${jdbc_username}"="password"="${jdbc_password}"/> 初始化连接大小 --> ="initialSize"="0" 连接池最大使用连接数量 ="maxActive"="20" 连接池最大空闲 ="maxIdle" 连接池最小空闲 ="minIdle" 获取连接最大等待时间 ="maxWait"="60000"="validationQuery"="${validationQuery}"="testOnBorrow"="false"="testOnReturn"="testWhileIdle"="true" 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 ="timeBetweenEvictionRunsMillis" 配置一个连接在池中最小生存的时间,单位是毫秒 ="minEvictableIdleTimeMillis"="25200000" 打开removeAbandoned功能 ="removeAbandoned" 1800秒,也就是30分钟 ="removeAbandonedTimeout"="1800" 关闭abanded连接时输出错误日志 ="logAbandoned" 监控数据库 ="filters"="mergeStat"/> </bean> id="druidDynamicDataSource"="com.blog.datasource.DruidDynamicDataSource"="defaultTargetDataSource" ref="defaultDataSource" ="targetDataSources"> map> entry key value-ref="defaultDataSource"/> 这里还可以加多个dataSource --> property> > Spring整合Mybatis,更多查看文档:http://mp.baomidou.com ="sqlSessionFactory"="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean"> ="dataSource"="druidDynamicDataSource" 自动扫描Mapping.xml文件 ="mapperLocations"="classpath:mybatis/system/*.xml"="configLocation"="classpath:mybatis/mybatis-config.xml"="typeAliasesPackage"="com.blog.entity"="plugins"array 分页插件配置 --> ="paginationInterceptor"="com.baomidou.mybatisplus.plugins.PaginationInterceptor" 全局配置注入 ="globalConfig"="globalConfig" > 配置事务管理 ="transactionManager"="org.springframework.jdbc.datasource.DataSourceTransactionManager"="druidDynamicDataSource"> ? 三、单元测试 import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.blog.datasource.DBContextHolder; import com.blog.entity.User; import com.blog.mapper.PostDao; import com.blog.service.UserService; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:spring/spring.xml") public class BlogTest { @Autowired private UserService ud; @Test public void testName() throws Exception { MapString> map = new HashMap>(); map.put(DBContextHolder.DATASOURCE_KEY,"localhost"); map.put(DBContextHolder.DATASOURCE_DRIVER,"com.mysql.jdbc.Driver"); map.put(DBContextHolder.DATASOURCE_URL,"jdbc:mysql://127.0.0.1:3306/blog_test?useUnicode=true&characterEncoding=UTF-8"); map.put(DBContextHolder.DATASOURCE_USERNAME,"root"); map.put(DBContextHolder.DATASOURCE_PASSWORD,"1234"); DBContextHolder.setDBType(map); ListUser list = ud.selectList(null); for (User user : list) { System.out.println(user); } } } ? 测试后,控制台如图: ? 小结: 其实配置多数据源有很多方式,有aop,也有配置多个bean的方式,当然了,只要能达到目的就是王道,当然了,我也强调一点,不是实现完就不管了,背后的为什么比只要实现就好更重要。 其实,有一点我想说的是,有些时候遇到难题,最好的方式是迎面而上解决这个问题,而不是逃避或者独自焦躁。同时直面问题,也是解决焦躁的最好方式。这个我已经深有体会了。 另外补充到,上传至github上的多数据源示例同时也是ssm框架的搭建。有哪位朋友不会搭建框架,可以参考我的这个。希望能对你们有什么帮助。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |