spring+mybatis配置多数据源总结,重点是动态加载数据源,支持动态切换
2015-12-30 18:56
1266 查看
最近在做一款游戏的GM管理平台,需要连接游戏的数据库去查询数据;由于游戏的每个服的数据是独立的,所以就有了连接多个数据库的问题;经过一番查找,好在mybatis的学习资源还少,很快找到了配置多数据源的方法;感谢以下大牛分享的学习资源:
http://lvdong5830.iteye.com/blog/1626286 http://blog.csdn.net/thc1987/article/details/8969655 http://fenshen6046.iteye.com/blog/1810159
Spring整合MyBatis有两种方式,一种是配置MapperFactoryBean,另一种则是利用MapperScannerConfigurer进行扫描接口或包完成对象的自动创建。相对来说后者更方便些,多数据源的配置方法,既配置多个datasource,对应多个sqlsessionfactory,分别把不同的sqlsessionfactory配置到对应的MapperScannerConfigurer中去,mybatis会根据扫描的包路径,自动区分数据源,如下:
这样即可实现一个工程中不同包路径下的代码使用不同的数据源,配置事务管理器的原理也是一样的;于是欣喜地开始着手我的工程,刚动手就发现一个问题,通常情况下一款游戏会有成百上千个服,多款游戏需要连数以千万计的数据库,那么我要建千万个包吗?况且,游戏的开服和关服是非常频繁的,如此以来,我要跟着游戏一起更新代码?岂不累死!
于是又仔细分析,一款游戏的数据库结构都是一样的,写的sql也是一样的,那么能不能相同数据库结构的数据源,只配置一个源一个包,这样sql就可以共用了,那么问题来了,怎么配置一个数据源呢?前台查询多个服的数据,怎么做到切换数据源呢?前面也说过,游戏的开服和关服是很频繁的,如果游戏新开服务器,我怎么能做到不更新代码,就让他能够查询到数据库呢?就是说我需要做到动态加载数据源,并且自动切换多个数据源!这下可愁人了,多数据源的学习资料本来不是很多,又整这么复杂的配置;于是乎又开始叮咣五四的找各种资料,最后总算找到了一些可以借鉴的资源,学习的过程总是痛苦的,踩着前人的脚步,然后又结合自己的情况做了一番修改,才算有了点眉目;问题一步一步得以解决。
首先来解决怎么在一个数据源配置下,可以连接多个数据库,得以执行相同的sql语句的问题:这个数据源是必须要配的,但是不能写死数据库连接,所以我自己实现了一个DataSource类,sqlsessionfactory执行sql时,会调用对应datasource的getConnection方法,废话不多说,亮出代码:
大致流程是这样,前台查询服务器数据时,把serverId传过来,过滤器中接收到后会根据serverId获取serverInfo,然后调用switchDataSource方法切换数据源;等执行sql的时候,getConnection方法就知道该返回哪个数据源连接了;(切换中还有判断数据源是否存在等逻辑,这个是后话)
动态切换数据源的功能就这样实现了,再说下一个问题,新开一组游戏服务器后,我的服务器列表里面会加入这个服务器的连接数据,那么如何能动态的加入这个数据源呢?对于spring而言datasource也是一个普通的bean,那就是如何动态注册一个bean的问题了;这方面的学习资源有很多,我不多讲,列出代码供参考:
ok,核心代码都已亮出来,思路也已说明,有问题留言,欢迎讨论!
http://lvdong5830.iteye.com/blog/1626286 http://blog.csdn.net/thc1987/article/details/8969655 http://fenshen6046.iteye.com/blog/1810159
Spring整合MyBatis有两种方式,一种是配置MapperFactoryBean,另一种则是利用MapperScannerConfigurer进行扫描接口或包完成对象的自动创建。相对来说后者更方便些,多数据源的配置方法,既配置多个datasource,对应多个sqlsessionfactory,分别把不同的sqlsessionfactory配置到对应的MapperScannerConfigurer中去,mybatis会根据扫描的包路径,自动区分数据源,如下:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="sqlSessionFactoryBeanName" value="oneSqlSessionFactory"></property> <property name="basePackage" value="aaa.bbb.one.*.dao" /> </bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="sqlSessionFactoryBeanName" value="twoSqlSessionFactory"></property> <property name="basePackage" value="aaa.bbb.two.*.dao" /> </bean>
这样即可实现一个工程中不同包路径下的代码使用不同的数据源,配置事务管理器的原理也是一样的;于是欣喜地开始着手我的工程,刚动手就发现一个问题,通常情况下一款游戏会有成百上千个服,多款游戏需要连数以千万计的数据库,那么我要建千万个包吗?况且,游戏的开服和关服是非常频繁的,如此以来,我要跟着游戏一起更新代码?岂不累死!
于是又仔细分析,一款游戏的数据库结构都是一样的,写的sql也是一样的,那么能不能相同数据库结构的数据源,只配置一个源一个包,这样sql就可以共用了,那么问题来了,怎么配置一个数据源呢?前台查询多个服的数据,怎么做到切换数据源呢?前面也说过,游戏的开服和关服是很频繁的,如果游戏新开服务器,我怎么能做到不更新代码,就让他能够查询到数据库呢?就是说我需要做到动态加载数据源,并且自动切换多个数据源!这下可愁人了,多数据源的学习资料本来不是很多,又整这么复杂的配置;于是乎又开始叮咣五四的找各种资料,最后总算找到了一些可以借鉴的资源,学习的过程总是痛苦的,踩着前人的脚步,然后又结合自己的情况做了一番修改,才算有了点眉目;问题一步一步得以解决。
首先来解决怎么在一个数据源配置下,可以连接多个数据库,得以执行相同的sql语句的问题:这个数据源是必须要配的,但是不能写死数据库连接,所以我自己实现了一个DataSource类,sqlsessionfactory执行sql时,会调用对应datasource的getConnection方法,废话不多说,亮出代码:
public class AAAGameDBMultiDataSource implements DataSource, ApplicationContextAware { private ThreadLocal<String> local = new ThreadLocal<String>(); // 数据源的驱动,用户名,密码都是固定的,所以在这里写死了 @Value("${aaa.gamedb.driverClassName}") private String driverClassName; @Value("${aaa.gamedb.username}") private String username; @Value("${aaa.gamedb.password}") private String password; @Autowired private DynamicLoadBean dynamicLoadBean; private ApplicationContext applicationContext = null; private DataSource dataSource = null; public AAAGameDBMultiDataSource(DataSource dataSource) { this.setDataSource(dataSource); } public AAAGameDBMultiDataSource() { } @Override public PrintWriter getLogWriter() throws SQLException { return getDataSource().getLogWriter(); } @Override public void setLogWriter(PrintWriter out) throws SQLException { getDataSource().setLogWriter(out); } @Override public void setLoginTimeout(int seconds) throws SQLException { getDataSource().setLoginTimeout(seconds); } @Override public int getLoginTimeout() throws SQLException { return getDataSource().getLoginTimeout(); } public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; } @Override public <T> T unwrap(Class<T> iface) throws SQLException { return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; } @Override public Connection getConnection() throws SQLException { return getDataSource().getConnection(); } @Override public Connection getConnection(String username, String password) throws SQLException { return null; } public DataSource getDataSource() { // 获取本线程中存入的数据源标识 String dataSourceName = this.local.get(); return getDataSource(dataSourceName); } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public DataSource getDataSource(String dataSourceName) { try { if (GMUtil.isEmpty(dataSourceName)) { return null;//this.dataSource; } return (DataSource) this.applicationContext.getBean(dataSourceName); } catch (NoSuchBeanDefinitionException ex) { // throw new Exception("There is not the dataSource "); ex.printStackTrace(); } return dataSource; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } /** * @Desc 识别请求需要连接的数据源,动态切换数据源 * @param reqPath * @param projName * @param user * @return 0:不需要切换数据源,或已切换成功; -1:用户数据错误导致切换失败 */ public int switchDataSource(SmolArea serverInfo){ // 如果目标查询的游戏服为空,则返回-1 if(GMUtil.isEmpty(serverInfo)) return -1; String dataSourceName = "ds" + "_" + serverInfo.getGamedb_lan_ip() + "_gamedb"; if(!dynamicLoadBean.hasBean(dataSourceName)){ /* ============== 动态装配DataSource begin ==================*/ DataSourceDynamicBean dataSourceDynamicBean = new DataSourceDynamicBean(dataSourceName); dataSourceDynamicBean.setDriverClassName(driverClassName); dataSourceDynamicBean.setUrl(serverInfo.getGamedb_lan_ip(), 3306, "gamedb", driverClassName); dataSourceDynamicBean.setUsername(username); dataSourceDynamicBean.setPassword(password); dynamicLoadBean.loadBean(dataSourceDynamicBean);// /* ============== 动态装配DataSource end ==================*/ } // 切换数据源 this.local.set(dataSourceName); return 0; } }
大致流程是这样,前台查询服务器数据时,把serverId传过来,过滤器中接收到后会根据serverId获取serverInfo,然后调用switchDataSource方法切换数据源;等执行sql的时候,getConnection方法就知道该返回哪个数据源连接了;(切换中还有判断数据源是否存在等逻辑,这个是后话)
动态切换数据源的功能就这样实现了,再说下一个问题,新开一组游戏服务器后,我的服务器列表里面会加入这个服务器的连接数据,那么如何能动态的加入这个数据源呢?对于spring而言datasource也是一个普通的bean,那就是如何动态注册一个bean的问题了;这方面的学习资源有很多,我不多讲,列出代码供参考:
/** * 使用方法loadBean()向spring的beanFactory动态地装载bean,该方法的参数configLocationString等同于 * spring配置中的contextConfigLocation,同样支持诸如"/WEB-INF/ApplicationContext-*.xml"的写法。 * @author FanGang * */ public class DynamicLoadBean implements ApplicationContextAware{ private ConfigurableApplicationContext applicationContext = null; private XmlBeanDefinitionReader beanDefinitionReader; /* 初始化方法 */ public void init() { beanDefinitionReader = new XmlBeanDefinitionReader((BeanDefinitionRegistry) applicationContext.getBeanFactory()); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(applicationContext)); } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = (ConfigurableApplicationContext) applicationContext; } public ConfigurableApplicationContext getApplicationContext() { return applicationContext; } public boolean hasBean(String beanName){ return applicationContext.containsBean(beanName); } /** * 向spring的beanFactory动态地装载bean * @param configLocationString 要装载的bean所在的xml配置文件位置。 */ public void loadBean(String configLocationString){ XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader((BeanDefinitionRegistry)getApplicationContext().getBeanFactory()); beanDefinitionReader.setResourceLoader(getApplicationContext()); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(getApplicationContext())); try { String[] configLocations = new String[]{configLocationString}; for(int i=0;i<configLocations.length;i++) beanDefinitionReader.loadBeanDefinitions(getApplicationContext().getResources(configLocations[i])); } catch (BeansException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void loadBean(DynamicBean dynamicBean) { String beanName = dynamicBean.getBeanName(); if (applicationContext.containsBean(beanName)) { // LogUtil.clientLog("bean【" + beanName + "】已经加载!"); return; } beanDefinitionReader.loadBeanDefinitions(new DynamicResource(dynamicBean)); // LogUtil.clientLog("初始化bean【" + dynamicBean.getBeanName() + "】耗时" + (System.currentTimeMillis() - startTime) + "毫秒。"); } public void removeBean(String beanName) { if (applicationContext.containsBean(beanName)) { BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) applicationContext.getBeanFactory(); beanDefinitionRegistry.removeBeanDefinition(beanName); return; } } }
/** * 动态bean描述对象 */ public abstract class DynamicBean { protected String beanName; public DynamicBean(String beanName) { this.beanName = beanName; } public String getBeanName() { return beanName; } public void setBeanName(String beanName) { this.beanName = beanName; } /** * 获取bean 的xml描述 * * @return */ protected abstract String getBeanXml(); /** * 生成完整的xml字符串 * * @return */ public String getXml() { StringBuffer buf = new StringBuffer(); buf.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>") .append("<beans xmlns=\"http://www.springframework.org/schema/beans\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"") .append(" xmlns:p=\"http://www.springframework.org/schema/p\" xmlns:aop=\"http://www.springframework.org/schema/aop\"") .append(" xmlns:context=\"http://www.springframework.org/schema/context\" xmlns:jee=\"http://www.springframework.org/schema/jee\"") .append(" xmlns:tx=\"http://www.springframework.org/schema/tx\"") .append(" xsi:schemaLocation=\"") .append(" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd") .append(" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd") .append(" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd") .append(" http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd") .append(" http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd\">") .append(getBeanXml()).append("</beans>"); return buf.toString(); } }
public class DynamicResource implements Resource { private DynamicBean dynamicBean; public DynamicResource(DynamicBean dynamicBean) { this.dynamicBean = dynamicBean; } /* * (non-Javadoc) * * @see org.springframework.core.io.InputStreamSource#getInputStream() */ public InputStream getInputStream() throws IOException { return new ByteArrayInputStream(dynamicBean.getXml().getBytes("UTF-8")); } // 其他实现方法省略 @Override public long contentLength() throws IOException { return 0; } @Override public Resource createRelative(String arg0) throws IOException { return null; } @Override public boolean exists() { return false; } @Override public String getDescription() { return null; } @Override public File getFile() throws IOException { return null; } @Override public String getFilename() { return null; } @Override public URI getURI() throws IOException { return null; } @Override public URL getURL() throws IOException { return null; } @Override public boolean isOpen() { return false; } @Override public boolean isReadable() { return false; } @Override public long lastModified() throws IOException { return 0; } }
ok,核心代码都已亮出来,思路也已说明,有问题留言,欢迎讨论!
相关文章推荐
- 一个jar包里的网站
- 一个jar包里的网站之文件上传
- 一个jar包里的网站之返回对媒体类型
- 模拟Spring的简单实现
- spring+html5实现安全传输随机数字密码键盘
- Spring中属性注入详解
- 深入浅析mybatis oracle BLOB类型字段保存与读取
- SpringMVC框架下JQuery传递并解析Json格式的数据是如何实现的
- struts2 spring整合fieldError问题
- spring的jdbctemplate的crud的基类dao
- 读取spring配置文件的方法(spring读取资源文件)
- Spring Bean基本管理实例详解
- java实现简单美女拼图游戏
- 详解Java的Spring框架中的事务管理方式
- oracle+mybatis 使用动态Sql当插入字段不确定的情况下实现批量insert
- 解析Java的Spring框架的BeanPostProcessor发布处理器
- Java开发框架spring实现自定义缓存标签
- 浅析Mybatis 在CS程序中的应用
- Java Mybatis框架入门基础教程
- java基本教程之线程休眠 java多线程教程