Spring+myBatis的动态多数据源 未能成功切换

KING_Liu 网易 服务器开发工程师  2013-01-30 08:40:32
这不科学,实在不科学.
应用场景:
本系统是一个公共服务模块.提供接口给其它系统调用.根据不同的对接系统,需要对不同的数据源进行操作.
数据源信息存储于数据库的DbInfo表中.

现在遇到的问题,见回复1中.

问题说明:

/**对接开发应用表*/
CREATE TABLE `cmm_dev` (
`DevID` varchar(16) NOT NULL,
`Name` varchar(128) DEFAULT NULL,
`ServerID` int(11) DEFAULT NULL, -----与DbInfo的Id对应,但未设置外键关系
PRIMARY KEY (`DevID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/**数据源表*/
CREATE TABLE `cmm_db_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`IP` varchar(50) DEFAULT NULL,
`Port` int(11) DEFAULT NULL,
`DbName` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;


在网上找了相关资料.最后决定用extends AbstractRoutingDataSource方式进解决.

动态数据源类:
package cn.util.dao.Helper;

import java.util.Map;

import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import cn.util.comment.domain.DbInfo;

public class DynamicDataSource extends AbstractRoutingDataSource {

private Map<Object, Object> _targetDataSources;

@Override
protected Object determineCurrentLookupKey() {
Object obj = DbContextHolder.getDbType();
if(obj == null){
obj = "default";
}
System.out.println("OBJ-->"+obj);
return obj;
}

public void setTargetDataSources(Map<Object, Object> targetDataSources) {
this._targetDataSources = targetDataSources;
super.setTargetDataSources(this._targetDataSources);
afterPropertiesSet();
}

public void addTargetDataSource(String key, BasicDataSource dataSource) {
this._targetDataSources.put(key, dataSource);
this.setTargetDataSources(this._targetDataSources);
}

public BasicDataSource createDataSource(String driverClassName,String url,
String username, String password,int maxIdle,int maxActive) {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setMaxActive(maxActive);
dataSource.setMaxIdle(maxIdle);
dataSource.setInitialSize(1);
dataSource.setDefaultAutoCommit(true);

return dataSource;
}

public void selectDataSource(Integer serverId){
Object obj = DbContextHolder.getDbType();
if("0".equals(serverId+"")){
DbContextHolder.setDbType("default");
return;
}
if(obj != null && obj.equals(serverId+"")){
return;
}else{
BasicDataSource dataSource = this.getDataSource(serverId);
this.setDataSource(serverId, dataSource);
}
}

public BasicDataSource getDataSource(Integer serverId){
String driver = DBConfig.getDriver();
String username = DBConfig.getUsername();
String password = DBConfig.getPass();
int maxActive = DBConfig.getMaxActive();
int maxIdle = DBConfig.getMaxIdle();
DbInfo dbInfo = DataSourceHelper.getDbInfo(serverId);

String url = "jdbc:mysql://"+dbInfo.getIp()+":"+dbInfo.getPort()+"/"+dbInfo.getDbName()
+"?characterEncoding=utf-8&allowMultiQueries=true&autoReconnect=true";

BasicDataSource dataSource = this.createDataSource(driver, url, username, password, maxIdle, maxActive);

return dataSource;
}

public void setDataSource(Integer serverId,BasicDataSource dataSource){
this.addTargetDataSource(serverId+"", dataSource );
DbContextHolder.setDbType(serverId+"");
}
}

============================================================================
帮助类,主要是加载DbInfo用.

package cn.util.dao.Helper;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import cn.util.comment.dao.DbInfoDao;
import cn.util.comment.domain.CommentInfo;
import cn.util.comment.domain.DbInfo;
import cn.util.comment.helper.ApplicationContextHelper;
import cn.util.comment.helper.DateHelper;
import cn.util.comment.service.logic.CommentInfoLogic;

/**
* @createTime 2013-1-29上午10:23:52
* @author KING
* @version 1.0.0.0
*/
public class DataSourceHelper {
private DbInfoDao dbInfoDao;
private static Map<Integer,DbInfo> dataSourceMap = new ConcurrentHashMap<Integer,DbInfo>();

public void loadAllDbInfo(){
List<DbInfo> dbinfoList = dbInfoDao.findListByParams(null);
if(dbinfoList != null && dbinfoList.size() >0){
for (DbInfo dbInfo : dbinfoList) {
if(dbInfo != null ){
dataSourceMap.put(dbInfo.getId(), dbInfo);
}
}
}
}

public static DbInfo getDbInfo(Integer id){
if(!dataSourceMap.containsKey(id)){
DataSourceHelper.reLoadDbInfo();
}
return dataSourceMap.get(id);
}

public static void reLoadDbInfo(){
Map<Integer,DbInfo> tempMap = new ConcurrentHashMap<Integer,DbInfo>();
DbInfoDao dbInfoDao = (DbInfoDao) ApplicationContextHelper.getBean("dbInfoDao");
List<DbInfo> dbinfoList = dbInfoDao.findListByParams(null);
if(dbinfoList != null && dbinfoList.size() >0){
for (DbInfo dbInfo : dbinfoList) {
if(dbInfo != null ){
tempMap.put(dbInfo.getId(), dbInfo);
}
}
}
dataSourceMap = tempMap;
}

public static void main(String... args){
CommentInfoLogic logic = (CommentInfoLogic) ApplicationContextHelper.getBean("commentInfoLogic");
CommentInfo info = new CommentInfo();
info.setId(DateHelper.getUUID());
info.setCreateTime(new Date());
logic.addCommentInfo(1+"", info);
logic.addCommentInfo("test", info);
}
}

============================================================================
Context用于存放当环境的上下文标识类

package cn.util.dao.Helper;

public class DbContextHolder {

private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

public static void setDbType(String dbType) {
contextHolder.set(dbType);
}

public static String getDbType() {
return ((String)contextHolder.get());
}
public static void clearDbType() {
contextHolder.remove();
}
}

============================SPRING DataSource配置============================
<!-- 加载Properties资源文件配置 -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:/jdbc.properties</value>
</list>
</property>
</bean>

<!-- 数据库连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${db.driver}" />
<property name="url" value="${dbmaster.url}" />
<property name="username" value="${dbmaster.username}" />
<property name="password" value="${dbmaster.password}" />
<property name="maxActive" value="${db.maxActive}"/>
<property name="maxIdle" value="${db.maxIdle}"/>
<property name="initialSize" value="1"/>
<property name="defaultAutoCommit" value="true" />
</bean>

<bean id="dynDataSource" class="cn.util.dao.Helper.DynamicDataSource">
<property name="targetDataSources">
<map>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSource" />
</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:config/mybatis/mybatis-config.xml"/>
<property name="dataSource" ref="dynDataSource" />
</bean>

<!-- 数据库的事务管理器配置 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dynDataSource" />
</bean>

<!-- 使用annotation定义数据库事务,这样可以在类或方法中直接使用@Transactional注解来声明事务 -->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>

<!--sqlSession模板定义-->
<bean id="sqlReadSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>

<bean id="commentInfoDao" class="cn.util.comment.dao.CommentInfoDao">
<property name="sqlReadSession">
<ref bean="sqlReadSession"/>
</property>
</bean>

<bean id="devDao" class="cn.util.comment.dao.DevDao">
<property name="sqlReadSession">
<ref bean="sqlReadSession"/>
</property>
</bean>

<!-- logic层注入 -->
<bean id="commentInfoLogic" class="cn.util.comment.service.logic.CommentInfoLogic">
<property name="commentInfoDao">
<ref bean="commentInfoDao"/>
</property>
<property name="devDao">
<ref bean="devDao"/>
</property>
</bean>

<bean id="commentInfoService" class="cn.util.comment.service.CommentInfoService">
<property name="commentInfoLogic">
<ref bean="commentInfoLogic"/>
</property>
</bean>


====================================STRUTS 配置==================================
<struts>
<package name="cmmService" extends="struts-default" namespace="/service/CmmService">
<action name="Add" class="commentInfoService" method="addCommentInfo">
<result>/index.jsp</result>
</action>
</package>
</struts>





...全文
636 17 点赞 打赏 收藏 举报
写回复
17 条回复
切换为时间正序
当前发帖距今超过3年,不再开放新的回复
发表回复
willowyxd 2013-07-01
楼主问题解决了没有 能分享一下么 ?
  • 打赏
  • 举报
回复
GOLF_R20 2013-05-21
  • 打赏
  • 举报
回复
KING_Liu 2013-05-21
引用 13 楼 yeness 的回复:
1、消息中间件,能解决 2、 private Map<Object, Object> ,以前写的时候,第二个参数定义的是sessionfactory ,但当时的系统是统一的数据库
我的也全部是mysql
  • 打赏
  • 举报
回复
KING_Liu 2013-05-21
引用 12 楼 wef 的回复:
楼主,这个问题你解决了吗? 有没有代码 .提供一份呢?谢谢!!!
代码基本上都贴出来了
  • 打赏
  • 举报
回复
xuan.ye 2013-05-14
1、消息中间件,能解决 2、 private Map<Object, Object> ,以前写的时候,第二个参数定义的是sessionfactory ,但当时的系统是统一的数据库
  • 打赏
  • 举报
回复
老吴老吴 2013-05-14
楼主,这个问题你解决了吗? 有没有代码 .提供一份呢?谢谢!!!
  • 打赏
  • 举报
回复
剑气凌人 2013-03-02
看看这个 http://jijun87120681.iteye.com/blog/1320799 ApplicationContextHelper在XML中注册没
  • 打赏
  • 举报
回复
KING_Liu 2013-02-27
没有找到方法解决
  • 打赏
  • 举报
回复
剑气凌人 2013-02-25
楼主最后怎么解决的
  • 打赏
  • 举报
回复
KING_Liu 2013-01-31
没有人来科谱呀.
  • 打赏
  • 举报
回复
KING_Liu 2013-01-30
这是为什么呢?不科学呀, 求科普
  • 打赏
  • 举报
回复
KING_Liu 2013-01-30
引用 5 楼 fangmingshijie 的回复:
debug下,看哪一步有问题。
已经看到了,就是getSession出来的那个SqlSeesion里面的_targetDataSources没有保存,我后来动态set进去的datasource.
  • 打赏
  • 举报
回复
debug下,看哪一步有问题。
  • 打赏
  • 举报
回复
KING_Liu 2013-01-30
引用 2 楼 fangmingshijie 的回复:
给id="dynDataSource"的bean加上scope="prototype"看下。
加上之后,代码测试也不行了.

public static void main(String... args){
		CommentInfoLogic logic = (CommentInfoLogic) ApplicationContextHelper.getBean("commentInfoLogic");
		CommentInfo info = new CommentInfo();
		info.setId(DateHelper.getUUID());
		info.setCreateTime(new Date());
		logic.addCommentInfo("1", info);
		logic.addCommentInfo("2", info);
	}
结果两条数据都是插入到了默认数据库了.这样就和web请求是一样的结果. 就是获得的session里面没有动态添加进去的datasource了. 这是那里的原因呢. 好像差一点点,就能解决了,就是不能捅破这最后一层纸呀.郁闷哟.
  • 打赏
  • 举报
回复
KING_Liu 2013-01-30
引用 2 楼 fangmingshijie 的回复:
给id="dynDataSource"的bean加上scope="prototype"看下。
好的,马上试试. 谢谢
  • 打赏
  • 举报
回复
给id="dynDataSource"的bean加上scope="prototype"看下。
  • 打赏
  • 举报
回复
KING_Liu 2013-01-30
/////////////////////////////////ACTION类///////////////////////////////////////// private CommentInfoLogic commentInfoLogic; private static Logger logger = Logger.getLogger(CommentInfoService.class); public String addCommentInfo(){ String devID = getRequest().getParameter(MsgCodeInfo.DEV_ID); ....... CommentInfo commentInfo = new CommentInfo(id ,appID, productID, rID, rName, uid, ip,userArea, comment, reserve); reInfo = commentInfoLogic.addCommentInfo(devID,commentInfo); return NONE; } public CommentInfoLogic getCommentInfoLogic() { return commentInfoLogic; } public void setCommentInfoLogic(CommentInfoLogic commentInfoLogic) { this.commentInfoLogic = commentInfoLogic; } ///////////////////////////////LOGIC类/////////////////////////////////////////// private CommentInfoDao commentInfoDao; private DevDao devDao; public String addCommentInfo(String devID, CommentInfo commentInfo) { Map<String, Object> params = new HashMap<String, Object>(); Dev dev = devDao.findByParams(params); if(dev != null && !StringHelper.Empty.equals(dev.getServerID())){ //变库 DynamicDataSource dynamicDataSource = (DynamicDataSource) ApplicationContextHelper.getBean("dynDataSource"); dynamicDataSource.selectDataSource(dev.getServerID()); } commentInfo = commentInfoDao.insert(commentInfo); return ""; } 两个属性的getter & settter方法 //////////////////////////////数据库操作类///////////////////////////////// public class BaseDaoImpl<T, PK extends Serializable> extends SqlSessionDaoSupport implements BaseDao<T, PK> { @Autowired(required = true) @Resource(name = "sqlSessionFactory") public void setSuperSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { super.setSqlSessionFactory(sqlSessionFactory); } // 保存 public T insert(T entity) { try { SqlSession session = this.getSqlSession(); session.insert(this.getClassSimpleName(entity) + ".save",entity); return entity; } catch (RuntimeException re) { logger.error("insert " + entity.getClass().getName() + " failed :{}",re); return null; } } } [/code] 现在Dbinfo有如下数据: 问题描述: 我用以代码测试,发现结果是正确的,在两个数据库分别插入进去了一条CommentInfo记录.
	public static void main(String... args){
		CommentInfoLogic logic = (CommentInfoLogic) ApplicationContextHelper.getBean("commentInfoLogic");
		CommentInfo info = new CommentInfo();
		info.setId(DateHelper.getUUID());
		info.setCreateTime(new Date());
		logic.addCommentInfo("1", info);
		logic.addCommentInfo("2", info);
	}
但是,现在通过web请求,做如下测试. 请求URL: http://localhost:8081/comment_service_webv3/service/CmmService/Add?参数串 将logic代码稍做调整.

public String addCommentInfo(String devID, CommentInfo commentInfo) {
		Map<String, Object> params = new HashMap<String, Object>();
		Dev dev = devDao.findByParams(params);
		if(dev != null && !StringHelper.Empty.equals(dev.getServerID())){
			//变库
			DynamicDataSource dynamicDataSource = (DynamicDataSource) ApplicationContextHelper.getBean("dynDataSource");
                        //将数据库强制变为2库.
			dynamicDataSource.selectDataSource(2);
		}
		
		commentInfo = commentInfoDao.insert(commentInfo);
		return "";
	}
结果: 预期该条数据应该查入2库,但实际却仍然插入spring配置的默认datasource库(<property name="defaultTargetDataSource" ref="dataSource" />)里面. 思考:这不科学呀,我两种方法基本没有区别呀.最后经过比对,找了大概出错的地方,是数据库层getSession出的不一样,但是不知道是为什么. 第一种通过代码测试,debug截图如下图,发现其获得session中有动态添加进去的datasource. 但是通过web请求过来的,然后获得session里面,就没有动态添加进去的datasource,所以只能操作默认配置的数据库了,如下图 实在是想不通,两都方式对logic类来说,都是spring里面得到的对象,为什么结果会不一样呢. 请大家指点,为什么?应当如何修改? 谢谢.
  • 打赏
  • 举报
回复
相关推荐
发帖
Java EE
加入

6.7w+

社区成员

J2EE只是Java企业应用。我们需要一个跨J2SE/WEB/EJB的微容器,保护我们的业务核心组件(中间件),以延续它的生命力,而不是依赖J2SE/J2EE版本。
申请成为版主
帖子事件
创建了帖子
2013-01-30 08:40
社区公告
暂无公告