基于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