背景:
使用动态数据源方式实现SAAS化,请参见文章:https://blog.csdn.net/wuwen2049/article/details/111216837。
和本文所描述异常相关的类为:DynamicDataSource
问题描述:
当单个post请求发送到后台,服务端进行数据库插入、更新操作,此时没有异常。(估计是请求并发数量只有1个,服务问题没有暴露出来。)
使用SoapUI同时模拟发送10个post请求给服务端,这时异常出现:
java.lang.IllegalStateException: No value for key [DynamicDataSource(defaultDataSources={jnhz={
CreateTime:"2021-01-12 16:50:14",
ActiveCount:0,
PoolingCount:1,
CreateCount:1,
DestroyCount:0,
CloseCount:1,
ConnectCount:1,
Connections:[
{ID:1844410541, ConnectTime:"2021-01-12 16:50:15", UseCount:1, LastActiveTime:"2021-01-12 16:50:15"}
]
}})] bound to thread [http-nio-8381-exec-6]
at org.springframework.transaction.support.TransactionSynchronizationManager.unbindResource(TransactionSynchronizationManager.java:213)
at org.springframework.jdbc.datasource.DataSourceTransactionManager.doCleanupAfterCompletion(DataSourceTransactionManager.java:367)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.cleanupAfterCompletion(AbstractPlatformTransactionManager.java:1007)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:793)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:714)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:533)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:304)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
这个异常发生的位置在:TransactionSynchronizationManager.unbindResource(Object key)这个方法。
public static Object unbindResource(Object key) throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Object value = doUnbindResource(actualKey);
if (value == null) {
throw new IllegalStateException(
"No value for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
}
return value;
}
为何value==null,继续进到私有方法doUnbindResource(actualKey);
@Nullable
private static Object doUnbindResource(Object actualKey) {
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
Object value = map.remove(actualKey);
// Remove entire ThreadLocal if empty...
if (map.isEmpty()) {
resources.remove();
}
// Transparently suppress a ResourceHolder that was marked as void...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
value = null;
}
if (value != null && logger.isTraceEnabled()) {
logger.trace("Removed value [" + value + "] for key [" + actualKey + "] from thread [" +
Thread.currentThread().getName() + "]");
}
return value;
}
问题出现在 Object value = map.remove(actualKey)这一行,入参actualKey不在map中,所以value值为null,导致外面的异常抛出。
分析:
debug模式下在Object value = map.remove(actualKey)这一行打上断点,发现诡异的地方:
actualKey的hashCode与map的一个key的hashCode是一样的,但map处理是却找不到actualKey这个值的key!
这里就要提到两个类的对比。map.remove的时候,key的hash与value都相等才认为这个key是map中的一个key。
刚才debug时候看到了key的hash值是一样的,出现问题的地方就又想到是value,其实value按理说也一定是一模一样的,但为何就不相等呢。于是乎,暴力的解决方式:
解决
自己动手,丰衣足食。
既然是DynamicDataSource在进行类相等比较的时候出现了问题,那我就自己override一下equals方法。
@Override
public boolean equals(Object obj) {
boolean result = super.equals(obj);
if(this == obj){
return true;//地址相等
}
if(obj == null){
return false;//非空性:对于任意非空引用x,x.equals(null)应该返回false。
}
if(obj instanceof DynamicDataSource ){
DynamicDataSource dynamicDataSource = (DynamicDataSource)obj;
Map<Object, Object> objDataSourceMap = dynamicDataSource.getDefaultDataSources();
if(objDataSourceMap.equals(this.defaultDataSources)) {
result = true;
}
}
return result;
}
这样,问题居然解决了!
延伸:
探求问题的时候,顺便也了解了spring在多数据源事务方面的处理。
AbstractPlatformTransactionManager是spring事务管理的关键类,本案例涉及到的DataSourceTransactionManager就集成这个抽象类。
TransactionSynchronizationManager是另个关键的类。当前线程进行数据库更新操作前,会进行bindResource,把动态数据源和数据连接加到resources这个ThreadLocal中。事务commit前后,分别会进行两次unbindResource。