java 通过继承类AbstractRoutingDataSource 而实现的 多数据源切换 的 缺陷(二)
2015-03-17 17:26
441 查看
最近在项目开发中,出现了一些见鬼的问题。保存或删除某些操作,bug的出现是偶发性出现,bug 还讲 概率事件啊,伤不起啊。在刚开始开发中,也偶尔出现,但是当时还不以为然。可是,项目上线后,用户也反映这个问题。 唉,这不是严重影响用户体验么。看来,这已经成了一个必然的bug, 不改不行了。
看来, Java中 偶发性 bug,多半跟 多线程 有关系。
先贴代码
spring-context.xml spring 的配置文件
com.conserv.tsas.common.bean.DynamicDataSource.java
DatabaseContextHolder.java
那么有如何给 private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); 中设置值呢?
采用面向切面的方式 进行 java 的自定义注释方式 给 contextHolder 中存储值。
首先实现Java的自定义注释。
DataSource.java
注释如何实用呢?
在 DAO 层中,在需要切换数据源的方法(method)前面加上 @DataSource(value=DS.YYZY) , DS.YYZY就是我要的数据源
例子:
先在再来实现上面注释的拦截方法,这里采用面向切面的方式。
DynamicDataSourceAspect.java
本文重点, 缺陷。
这种方式实现多数据源的切换,是通过 ThreadLocal 方式来共享 数据。 如果在service 层中使用 事务 @Transactional
例:
这是因为 ThreadLocal 类的内部
无论是set() 还是 get(),都是通过Thread.currentThread() 来作为 键值 进行存储。
而在 Java web 开发中, 在 service 层使用了事务,对 DAO 的 数据源拦截造成错乱,也即是ThreadLocal 中获取当前线程,出现错乱。
看来, 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 中获取当前线程,出现错乱。
相关文章推荐
- 【原】继承AbstractRoutingDataSource再通过AOP实现动态数据源切换
- 继承AbstractRoutingDataSource再通过AOP实现动态数据源切换(转)
- 继承AbstractRoutingDataSource再通过AOP实现动态数据源切换
- Spring+Mybatis项目中通过继承AbstractRoutingDataSource实现数据库热切换
- 利用AbstractRoutingDataSource实现动态数据源切换
- AbstractRoutingDataSource实现数据源切换
- Spring+Mybatis多数据源配置(四)——AbstractRoutingDataSource实现数据源动态切换
- SSH框架(二) 利用AbstractRoutingDataSource实现动态数据源切换
- SpringMVC 使用jndi 多个数据源且利用AbstractRoutingDataSource实现动态数据源切换
- Spring(AbstractRoutingDataSource)实现动态数据源切换
- SpringMVC 利用AbstractRoutingDataSource实现动态数据源切换
- 利用AbstractRoutingDataSource实现动态数据源切换
- Spring(AbstractRoutingDataSource)实现动态数据源切换--转载
- 利用AbstractRoutingDataSource实现动态数据源切换 (Spring+Hibernate)
- Spring的AbstractRoutingDataSource 实现 数据源DB的动态切换
- Spring(AbstractRoutingDataSource)实现动态数据源切换--转载
- 利用AbstractRoutingDataSource实现动态数据源切换
- Spring(AbstractRoutingDataSource)实现动态数据源切换
- Spring(AbstractRoutingDataSource)实现动态数据源切换
- 利用AbstractRoutingDataSource实现动态数据源切换