最近在做一款游戏的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方法,废话不多说,亮出代码:
public class AAAGameDBMultiDataSource implements DataSource,
ApplicationContextAware {
private ThreadLocal local = new ThreadLocal();
// 数据源的驱动,用户名,密码都是固定的,所以在这里写死了
@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 unwrap(Class 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;
}
}
动态切换数据源的功能就这样实现了,再说下一个问题,新开一组游戏服务器后,我的服务器列表里面会加入这个服务器的连接数据,那么如何能动态的加入这个数据源呢?对于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
/**
* 动态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("")
.append("")
.append(getBeanXml()).append(" ");
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,核心代码都已亮出来,思路也已说明,有问题留言,欢迎讨论!