基于spring的数据库读写分离

背景:
  Spring读写分离是大家都比较常见并一直在使用的技术。
  本博文再次对其进行阐述,一方面是为了更好的分享给大伙,一方面也是对最近做"XXX系统"遇到的问题做一次整理。方便大家以后遇到类似问题可以很快解决。
技术实现:
  1、多数据源配置。配置包括一个主库master_dataSource,一个个从库slave_dataSource。
  数据源托管给tomcat控制,系统通过jndi方式寻找。配置内容如下:

<beans profile="production">
<jee:jndi-lookup id="master_dataSource" jndi-name="java:comp/env/jdbc/master"/>
<jee:jndi-lookup id="slave_dataSource" jndi-name="java:comp/env/jdbc/slave"/>
<bean id="dataSource" class="com.mmb.framework.support.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="master" value-ref="master_dataSource"></entry>
<entry key="slave" value-ref="slave_dataSource"></entry>
<entry key="slave2" value-ref="slave2_dataSource"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="master_dataSource"></property>
</bean>
</beans>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />

  2、实现类
  从配置文件发现dataSource的实现类为:com.mmb.framework.support.DynamicDataSource,该类继承了spring中类:AbstractRoutingDataSource。并重写了方法:determineCurrentLookupKey。该方法是主从数据源切换的关键,通过该方法判定当前上下文的数据源key,进而跟进key通过Jndi找到对应的数据源。该类具体内容如下:

  

public class DynamicDataSource extends AbstractRoutingDataSource {
public static String MASTER = "master";
public static String SLAVE = "slave";
private static ThreadLocal<String> local = new ThreadLocal<String>();
@Override
protected Object determineCurrentLookupKey() {
String dString = local.get() == null ? MASTER : local.get();
setRoute(DynamicDataSource.MASTER);
return dString;
}
public static void setRoute(String route) {
if (route == null || route.equals("")) {
route = MASTER;
}
local.set(route);
}
public static void removeRoute() {
local.remove();
}
public static Object getRoute() {
return local.get();
}
}

 

  通过determineCurrentLookupKey方法可以看见,程序将当前线程key放入到ThreadLocal变量local中。这样就可以很好的解决多线程问题,避免争抢
数据库连接导致数据错误。那么setRoute方法在何时被调用,请读者继续向下看。

  3、使用方法
  系统采用ibatis作为持久层,并使用AbstractDaoSupport辅助获取数据库连接。AbstractDaoSupport属于org.apache.ibatis.spring.support包,我们在其中getSession和getJdbcTemplate。如下:

   

public Session getSession(String sessionFactorySign)
{
dynamicSession(sessionFactorySign);
return session;
}

public JdbcTemplate getJdbcTemplate(String sessionFactorySign)
{
dynamicSession(sessionFactorySign);
return JdbcTemplate;
}
private void dynamicSession(String sessionFactorySign)
{
if(null == sessionFactorySign || "".equals(sessionFactorySign))
DynamicDataSource.setRoute(DynamicDataSource.MASTER);
else
DynamicDataSource.setRoute(sessionFactorySign);
}

  通过代码可以发现,每次获取JdbcTemplate模板类或者获取session时,都会调用DynamicDataSource.setRoute进行数据源路由的设定。这里也回答了上面提到的问题。
具体使用:this.getJdbcTemplate(DynamicDataSource.MASTER).update();

  4、类图和序列图
  到此可能大家依然不是太清晰,到底如何做到了读写分离。下面我给出了上面涉及到的类关系和序列图。方便大家更好的理解。图形工具为:Enterprise Architect 11。

  类图:

  

  序列图:

  
1、TestMapper调用AbstractDaoSupport.getJdbcTemplate方法获取JdbcTemplate
2、AbstractDaoSupport调用自身dynamicSession方法,该方法中调用DynamicDataSource.setRoute方法设置数据源路由key。
3、TestMapper获取到jdbcTemplate后,调用JdbcTemplate.update方法
4、JdbcTemplate调用DataSourceUtil获取数据连接
5、DataSourceUtil调用自身doGetConnection,该方法中调用TransactionSynchronizationManager.getResource方法找到数据源
6、如果当前TransactionSynchronizationManager中包含ConnectionHolder并且开启了事务同步,并且ConnectionHolder中有连接,那么转到7.否则转到8.
7、调用ConnectionHolder.getConnection返回连接,转到9。
8、调用AbstractRoutingDataSource.getConnection方法,该方法调用DynamicDataSource.determineCurrentLookupKey()从ThreadLodal变量DynamicDataSource.local中获取
当前数据源路由key,根据key获取当前的数据源,调用具体数据源的getConnection方法,返回当前数据库连接。转到9。
9、JdbcTemplate接受到数据库连接后执行execute方法完成更新操作,结束。

在实际使用用还遇到如下两个问题,请参看博文:http://www.cnblogs.com/belen/p/4926206.html

posted @ 2015-10-31 20:03  熊仔下去遛弯了  阅读(1341)  评论(1编辑  收藏  举报