Spring中多数据库的管理
悬赏:15 发布时间:2008-07-23 提问人:reggie (初级程序员)
情况是这样的:
应用服务器要连接20个数据库,只有1个Postgres数据库是可写的,其余19个Mysql都是只读的。
原来的方案是用XAPool,和Global transaction Manager,管理所有的data source,但连接Postgres数据库时经常扔出链接错误的Exception。
考虑到只有一个数据库可写,于是改用DBCP做data source,配置了一个JDBC transaction manager,管理指向Postgres的data source,其余data source没有配Transaction Manager。发现这样配服务器速度快了很多。
问题出在服务器跑一段时间后,就会发生连接不上Mysql数据库。推测是因为连接池耗尽引起的。查了一下Spring的资料,发现JDBCTemplate在没有transaction manager的情况下应该自己会管理connection。只能推测因为有一个Transaction Manager(针对Postgres的,注入到Manager层,所有的DAO都受其管理),所以jdbctemplate放弃了对connection的管理,而那个Transaction manager 只能管Postgres的连接,导致Mysql的连接没人管。
由于对Spring的机理不熟,以上纯属猜测。有谁能解惑一下?或给个方案?
多谢多谢
该问题已经关闭: 超过15天由系统自动关闭,悬赏平分给所有参与回答的会员
应用服务器要连接20个数据库,只有1个Postgres数据库是可写的,其余19个Mysql都是只读的。
原来的方案是用XAPool,和Global transaction Manager,管理所有的data source,但连接Postgres数据库时经常扔出链接错误的Exception。
考虑到只有一个数据库可写,于是改用DBCP做data source,配置了一个JDBC transaction manager,管理指向Postgres的data source,其余data source没有配Transaction Manager。发现这样配服务器速度快了很多。
问题出在服务器跑一段时间后,就会发生连接不上Mysql数据库。推测是因为连接池耗尽引起的。查了一下Spring的资料,发现JDBCTemplate在没有transaction manager的情况下应该自己会管理connection。只能推测因为有一个Transaction Manager(针对Postgres的,注入到Manager层,所有的DAO都受其管理),所以jdbctemplate放弃了对connection的管理,而那个Transaction manager 只能管Postgres的连接,导致Mysql的连接没人管。
由于对Spring的机理不熟,以上纯属猜测。有谁能解惑一下?或给个方案?
多谢多谢
该问题已经关闭: 超过15天由系统自动关闭,悬赏平分给所有参与回答的会员
回答
public abstract class JdbcDaoSupport extends DaoSupport {
...
protected final Connection getConnection() throws CannotGetJdbcConnectionException {
return DataSourceUtils.getConnection(getDataSource());
}
...
}
这个DataSourceUtils.getConnection(getDataSource())具体内容是:
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
这个TransactionSynchronizationManager.getResource(dataSource)的具体内容是:
public static Object getResource(Object key) {
Assert.notNull(key, "Key must not be null");
Map map = (Map) resources.get();
if (map == null) {
return null;
}
Object value = map.get(key);
...
}
也就是ThreadLocal中维护了一个Map, key是DataSource Value是ConnectionHolder, ConnectionHolder中有一个被使用过的且会被再使用的Connection. 如果,上面Map中的Connection为空, 就会去dataSource.getConnection(), 并将这个Connection放进新的ConnectionHolder,并将这个ConnectionHolder放进ThreadLocal的Map中.
也就是TransactionSynchronizationManager是支持多DataSource,每个DataSource对应一个Connection的.
lggege (架构师) 2008-07-24
再看
doBegin()中会将你所说的Postgres和当前操作的Connection维护在一起,也就是key=Postgres的DataSource,Connection则有可能来自于Mysql或Postgres.
doCleanupAfterCompletion()的DataSourceUtils.releaseConnection具体实现:
也就是会用DataSource去查询Threadlocal的Map,从Map中拿到DataSource对应的Connection, 并将这个Connection释放掉. 当然这个DataSource还是你的Postgres的.
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager implements InitializingBean {
private DataSource dataSource;
public DataSource getDataSource() {
return dataSource;
}
protected void doBegin(Object transaction, TransactionDefinition definition) {
...
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
}
protected void doCleanupAfterCompletion(Object transaction) {
...
DataSourceUtils.releaseConnection(con, this.dataSource);
...
}
doBegin()中会将你所说的Postgres和当前操作的Connection维护在一起,也就是key=Postgres的DataSource,Connection则有可能来自于Mysql或Postgres.
doCleanupAfterCompletion()的DataSourceUtils.releaseConnection具体实现:
if (dataSource != null) {
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && conHolder.hasConnection() && connectionEquals(conHolder.getConnection(), con)) {
// It's the transactional Connection: Don't close it.
conHolder.released();
return;
}
}
也就是会用DataSource去查询Threadlocal的Map,从Map中拿到DataSource对应的Connection, 并将这个Connection释放掉. 当然这个DataSource还是你的Postgres的.
lggege (架构师) 2008-07-24
但回到JdbcDaoSupport的代码:
DataSourceUtils.getConnection()的实现是这样的:
也就是你用Mysql的JdbcDaoSupport去连接Mysql数据库,还是会将Mysql的DataSource和Mysql的Connection放进ThreadLocal的Map中.
问题应该查到了, 由于DataSourceTransactionManager 只会释放他自己的Postgres的DataSource为key的Connection, 但没有释放Mysql对应DataSource为key所对应的Connection.
protected final Connection getConnection() throws CannotGetJdbcConnectionException {
return DataSourceUtils.getConnection(getDataSource());
}
DataSourceUtils.getConnection()的实现是这样的:
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(dataSource.getConnection());
}
return conHolder.getConnection();
}
// Else we either got no holder or an empty thread-bound holder here.
logger.debug("Fetching JDBC Connection from DataSource");
Connection con = dataSource.getConnection();
...
}
也就是你用Mysql的JdbcDaoSupport去连接Mysql数据库,还是会将Mysql的DataSource和Mysql的Connection放进ThreadLocal的Map中.
问题应该查到了, 由于DataSourceTransactionManager 只会释放他自己的Postgres的DataSource为key的Connection, 但没有释放Mysql对应DataSource为key所对应的Connection.
lggege (架构师) 2008-07-24
不知道是否写明白了.
解决方法: 继承DataSourceTransactionManager, 在事务结束时,将ThreadLocal中的Map的所有DataSource为key对应的所有Connection都关闭掉就可以了.
解决方法: 继承DataSourceTransactionManager, 在事务结束时,将ThreadLocal中的Map的所有DataSource为key对应的所有Connection都关闭掉就可以了.
lggege (架构师) 2008-07-24
注: 完全的理论,没法实践,可能有误. 
lggege (架构师) 2008-07-24
其他只读数据库可不可以做一个同义词(synonym)放在可写的数据库中?
Ben.Sin (初级程序员) 2008-07-24
一个字,牛啊。这回答竟然把spring的bug翻出来了。
llade (资深程序员) 2008-07-24
Iggege看下triggerAfterCompletion和ConnectionSynchronization
triggerAfterCompletion在任何事务结束的情况下被调用,通过ConnectionSynchronization,connection被逐一的销毁。
triggerAfterCompletion在任何事务结束的情况下被调用,通过ConnectionSynchronization,connection被逐一的销毁。
nihongye (中级程序员) 2008-07-24
不过这里面确实存在问题,如果逐一关闭的过程发生异常,后面没关闭的连接就遗留着了,这种情况,应该有 TransactionSynchronization.afterCompletion threw exception
这样的日志信息
这样的日志信息
nihongye (中级程序员) 2008-07-24
看到nihongye所指的代码了, :oops: 丢人了, 回家研究.
lggege (架构师) 2008-07-24
DBCP有一个不被建议使用的叫做abbon..什么的选项,可以设置超时时间,超时时自动回收,能给出一个比较有用的堆栈日志信息(具体怎样配,查一下。。。)
nihongye (中级程序员) 2008-07-24
楼上让我想起一个办法,做一个超时会自动打印出调用堆栈的DataSource
参考http://www.javaeye.com/problems/873
不过做下修正:
参考http://www.javaeye.com/problems/873
不过做下修正:
public class MyDataSource extends BasicDataSource {
public final static Timer timer = new Timer();
Log logger = LogFactory.getLog(MyDataSource.class);
class ProxyConnection implements Connection{
Connection innnerConnection;
String uuid;
TimerTask task;
public ProxyConnection(Connection conn,String uuid){
final RuntimeException ex = new RuntimeException();
task=new TimerTask(){
@Override
public void run() {
ex.printStackTrace();
}
};
timer.schedule(task, 5000);
this.innnerConnection=conn;
this.uuid=uuid;
}
public void clearWarnings() throws SQLException {
this.innnerConnection.clearWarnings();
}
public void close() throws SQLException {
this.task.cancel();
this.innnerConnection.close();
}
......
llade (资深程序员) 2008-07-25




