java 通过继承类AbstractRoutingDataSource 而实现的 多数据源切换 的 缺陷(二)

最近在项目开发中,出现了一些见鬼的问题。保存或删除某些操作,bug的出现是偶发性出现,bug 还讲 概率事件啊,伤不起啊。在刚开始开发中,也偶尔出现,但是当时还不以为然。可是,项目上线后,用户也反映这个问题。 唉,这不是严重影响用户体验么。看来,这已经成了一个必然的bug, 不改不行了。


看来, Java中 偶发性 bug,多半跟 多线程 有关系。


先贴代码

spring-context.xml  spring 的配置文件

    
  	<bean id="yyzy" class="org.springframework.jndi.JndiObjectFactoryBean">
		<property name="jndiName">
			 <value>yyzy_bwcs</value>
			<!-- <value>java:comp/env/jndi/yyzy_db2</value> -->
		</property>
	</bean>
	<bean id="metadata" class="org.springframework.jndi.JndiObjectFactoryBean">
		<property name="jndiName">
			 <value>meta_db2</value> 
			<!-- <value>java:comp/env/jndi/meta_db2</value> -->
		</property>
	</bean>

	<!-- 交换平台 -->
	<bean id="jhpt" class="org.springframework.jndi.JndiObjectFactoryBean">
		<property name="jndiName">
			<value>jhpt_ds</value>
			<!-- <value>java:comp/env/jndi/jhpt_ds</value> -->
		</property>
	</bean>
	

    <bean id="dynamicDataSource" class="com.conserv.tsas.common.bean.DynamicDataSource">  
        <property name="targetDataSources">  
            <map key-type="java.lang.String">  
                <entry value-ref="yyzy" key="yyzy"></entry>  
                <entry value-ref="metadata" key="metadata"></entry>
                <entry value-ref="jhpt" key="jhpt"></entry>  
            </map>  
        </property>  
        <property name="defaultTargetDataSource" ref="yyzy">  
        </property>  
    </bean>
	
	<!-- sessionFactory -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dynamicDataSource"/>
        <property name="configLocation" value="classpath:mybatis-content.xml"></property>
        <property name="mapperLocations"  value="classpath:com/conserv/tsas/**/mapper/*.xml"/>
        <property name="typeAliasesPackage"  value="com.conserv.tsas.test.entity,com.conserv.tsas.front.entity"/>
	</bean>

com.conserv.tsas.common.bean.DynamicDataSource.java

package com.conserv.tsas.common.bean;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {
	@Override
	protected Object determineCurrentLookupKey() {
		return DatabaseContextHolder.getCustomerType();
	}

}

DatabaseContextHolder.java

package com.conserv.tsas.common.bean;

public class DatabaseContextHolder {
	private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

	public static final String YYZY = "yyzy";
	public static final String METADATA = "metadata";
	public static final String JHPT = "jhpt"; 

	public enum DS {
		YYZY, METADATA,JHPT 
	};
	
	public static void setCustomerType(String customerType) {
		contextHolder.set(customerType);
	}

	public static String getCustomerType() {
		return contextHolder.get();
	}

	public static void clearCustomerType() {
		contextHolder.remove();
	}
}
上面的代码就是 在web 程序执行的时候,通过 private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();这个变量来获取 数据源名称。


那么有如何给 private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); 中设置值呢?

采用面向切面的方式 进行 java 的自定义注释方式 给 contextHolder  中存储值。


首先实现Java的自定义注释。

DataSource.java

package com.conserv.tsas.common.bean;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import com.conserv.tsas.common.bean.DatabaseContextHolder.DS;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface DataSource {
	public DS value()default DS.YYZY;
}

注释如何实用呢?

在 DAO 层中,在需要切换数据源的方法(method)前面加上 @DataSource(value=DS.YYZY) , DS.YYZY就是我要的数据源

例子:

	@DataSource(value=DS.JHPT)
	@Override
	public void deleteNotWriteOff_DDS(Map params) {
		externalProcessMapper.deleteNotWriteOff_DDS(params);
	}


先在再来实现上面注释的拦截方法,这里采用面向切面的方式。

DynamicDataSourceAspect.java

package com.conserv.tsas.common.bean;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import com.conserv.tsas.common.bean.DatabaseContextHolder.DS;

@Component
@Aspect
@Order(1)
public class DynamicDataSourceAspect {
	@Pointcut("execution(* com.conserv.tsas.*.dao..*.*(..))")
	public void serviceExecution(){}
	
	@Before("serviceExecution()")
	public void setDynamicDataSource(JoinPoint jp) throws SecurityException, NoSuchMethodException {
		String methodName = jp.getSignature().getName();
		Method [] ms = jp.getTarget().getClass().getMethods();
		Method method = null;
		for(int i=0;i<ms.length;i++){
			if(ms[i].getName().equals(methodName)){
				method = ms[i];
				break;
			}
		}
		DataSource ds = method.getAnnotation(DataSource.class);
		if(ds != null){
			if(ds.value() == DS.YYZY){
				DatabaseContextHolder.setCustomerType(DatabaseContextHolder.YYZY);
			}else if(ds.value() == DS.METADATA){
				DatabaseContextHolder.setCustomerType(DatabaseContextHolder.METADATA);
			} else if (ds.value() == DS.JHPT) {
				DatabaseContextHolder.setCustomerType(DatabaseContextHolder.JHPT);
			}
		}else{
			DatabaseContextHolder.setCustomerType(DatabaseContextHolder.YYZY);
		}
		
	}
}


本文重点, 缺陷。

这种方式实现多数据源的切换,是通过 ThreadLocal 方式来共享 数据。  如果在service 层中使用 事务 @Transactional

例:

	@Override
	@Transactional
	public void deleteT_YYZY_WJG_YL_ZCB() {
		externalProcessDao.deleteT_YYZY_WJG_YL_CB();
		externalProcessDao.deleteT_YYZY_WJG_YL_ZB();
	}
此时,数据源容易混淆, 即本该执行 A 数据源,却执行了 C 数据源。

这是因为 ThreadLocal  类的内部

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }


    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

无论是set()  还是  get(),都是通过Thread.currentThread() 来作为 键值 进行存储。

而在 Java web 开发中, 在 service 层使用了事务,对 DAO 的 数据源拦截造成错乱,也即是ThreadLocal  中获取当前线程,出现错乱。








评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值