采用读写分离技术的目标:有效减轻master库的压力,又可以把用户查询数据的请求分发到不同的slave库,从而保证系统的健壮性。
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean { private Map<Object, Object> targetDataSources; private Object defaultTargetDataSource; private boolean lenientFallback = true; private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup(); private Map<Object, DataSource> resolvedDataSources; private DataSource resolvedDefaultDataSource; ......AbstractRoutingDataSource继承了AbstractDataSource ,而AbstractDataSource 又是DataSource 的子类。DataSource是javax.sql的数据源接口,定义如下:
public interface DataSource extends CommonDataSource, Wrapper { Connection getConnection() throws SQLException; Connection getConnection(String username, String password) throws SQLException; }DataSource 接口定义了2个方法,都是获取数据库连接。在来看下AbstractRoutingDataSource 如何实现了DataSource接口:
public Connection getConnection() throws SQLException { return determineTargetDataSource().getConnection(); } public Connection getConnection(String username, String password) throws SQLException { return determineTargetDataSource().getConnection(username, password); }AbstractRoutingDataSource通过调用determineTargetDataSource()方法获取到connection。determineTargetDataSource方法定义如下:
protected DataSource determineTargetDataSource() { Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); Object lookupKey = determineCurrentLookupKey(); DataSource dataSource = this.resolvedDataSources.get(lookupKey); if (dataSource == null && (this.lenientFallback || lookupKey == null)) { dataSource = this.resolvedDefaultDataSource; } if (dataSource == null) { throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); } return dataSource; }determineCurrentLookupKey()方法返回lookupKey, resolvedDataSources()方法就是根据 lookupKey从Map中获得数据源。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean id="m_dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://127.0.0.1:3306/zero" /> <property name="username" value="root/> <property name="password" value="123456" /> </bean> <bean id="s_dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://127.0.0.1:3306/zero_test" /> <property name="username" value="root" /> <property name="password" value="123456" /> </bean> <bean id="dynamicDataSource" class="com.zero.springjdbc.DynamicDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <!-- write --> <entry key="master" value-ref="m_dataSource" /> <!-- read --> <entry key="slave" value-ref="s_dataSource" /> </map> </property> <property name="defaultTargetDataSource" ref="m_dataSource" /> </bean> <bean id="zeroDaoImpl" class="com.zero.springjdbc.ZeroDaoImpl"> <property name="dataSource" ref="dynamicDataSource"></property> </bean> </beans>以上的beans.xml中,配置了两个DataSource,m_dataSource与s_dataSource,然后将这两个DataSource交给com.zero.springjdbc.DynamicDataSource管理,由于com.zero.springjdbc.DynamicDataSource是继承了AbstractRoutingDataSource,所以给它的两个属性targetDataSources、defaultTargetDataSource注入值。而DAO层的dataSource属性所使用的是dynamicDataSource。
package com.zero.springjdbc; public class DynamicDataSourceHolder { public static final ThreadLocal<String> holder = new ThreadLocal<String>(); public static void setDataSource(String name) { holder.set(name); } public static String getDataSouce() { return holder.get(); } }这里利用了ThreadLocal,来指明每个线程在进行数据库操作时所用到的数据库。
package com.zero.springjdbc; import java.sql.SQLFeatureNotSupportedException; import java.util.logging.Logger; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSource extends AbstractRoutingDataSource{ @Override protected Object determineCurrentLookupKey() { // TODO Auto-generated method stub return DynamicDataSourceHolder.getDataSouce(); } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { // TODO Auto-generated method stub return null; } }ZeroDaoImpl.java
package com.zero.springjdbc; import org.springframework.jdbc.core.JdbcTemplate; public class ZeroDaoImpl extends JdbcTemplate { public void update1() { DynamicDataSourceHolder.setDataSource("master"); String sql01 = "update lineitem_record set fee=111111 where lineitemId='11238'"; System.out.println("update1 lineitemId='11238' : " + this.update(sql01)); String sql02 = "update lineitem_record set fee=111111 where lineitemId='111'"; System.out.println("update1 lineitemId='111' : " + this.update(sql02)); } public void update2() { try { DynamicDataSourceHolder.setDataSource("slave"); String sql01 = "update lineitem_record set fee=111111 where lineitemId='11238'"; System.out.println("update2 lineitemId='11238' : " + this.update(sql01)); String sql02 = "update lineitem_record set fee=222222 where lineitemId='111'"; System.out.println("update2 lineitemId='111' : " + this.update(sql02)); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } }这里的每个方法都DynamicDataSourceHolder.setDataSource(),来指明接下来的操作将使用的是什么数据库,由于被ThreadLocal隔离了,所以是线程安全的。
package com.zero.springjdbc; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { // TODO Auto-generated method stub ApplicationContext ctx = new ClassPathXmlApplicationContext( "/com/zero/springjdbc/beans.xml"); ZeroDaoImpl zeroDaoImpl = (ZeroDaoImpl) ctx.getBean("zeroDaoImpl"); for (int i = 0; i < 5; i++) { new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub zeroDaoImpl.update1(); } }, "thread1-" + i).start(); ; new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub zeroDaoImpl.update2(); } }, "thread2-" + i).start(); ; } } }这里建了10个线程,每个线程执行zeroDaoImpl.update1()或zeroDaoImpl.update2()方法来检验是否是线程安全的。
update2 lineitemId='11238' : 0 update1 lineitemId='11238' : 1 update2 lineitemId='11238' : 0 update1 lineitemId='11238' : 1 update2 lineitemId='11238' : 0 update1 lineitemId='11238' : 1 update1 lineitemId='11238' : 1 update1 lineitemId='11238' : 1 update2 lineitemId='11238' : 0 update1 lineitemId='111' : 0 update2 lineitemId='111' : 1 update1 lineitemId='111' : 0 update1 lineitemId='111' : 0 update2 lineitemId='111' : 1 update2 lineitemId='111' : 1 update1 lineitemId='111' : 0 update2 lineitemId='11238' : 0 update2 lineitemId='111' : 1 update1 lineitemId='111' : 0 update2 lineitemId='111' : 1由结果可知, 利用Spring的AbstractRoutingDataSource可以解决多数据源的问题。
package org.springframework.jdbc.datasource.lookup; import java.sql.Connection; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; import javax.sql.DataSource; import org.springframework.beans.factory.InitializingBean; import org.springframework.jdbc.datasource.AbstractDataSource; import org.springframework.util.Assert; public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean { private Map<Object, Object> targetDataSources; private Object defaultTargetDataSource; private boolean lenientFallback = true; private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup(); private Map<Object, DataSource> resolvedDataSources; private DataSource resolvedDefaultDataSource; public void setTargetDataSources(Map<Object, Object> targetDataSources) { this.targetDataSources = targetDataSources; } public void setDefaultTargetDataSource(Object defaultTargetDataSource) { this.defaultTargetDataSource = defaultTargetDataSource; } public void setLenientFallback(boolean lenientFallback) { this.lenientFallback = lenientFallback; } public void setDataSourceLookup(DataSourceLookup dataSourceLookup) { this.dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup()); } public void afterPropertiesSet() { if (this.targetDataSources == null) { throw new IllegalArgumentException("Property 'targetDataSources' is required"); } this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size()); for (Map.Entry entry : this.targetDataSources.entrySet()) { Object lookupKey = resolveSpecifiedLookupKey(entry.getKey()); DataSource dataSource = resolveSpecifiedDataSource(entry.getValue()); this.resolvedDataSources.put(lookupKey, dataSource); } if (this.defaultTargetDataSource != null) { this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource); } } protected Object resolveSpecifiedLookupKey(Object lookupKey) { return lookupKey; } protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException { if (dataSource instanceof DataSource) { return (DataSource) dataSource; } else if (dataSource instanceof String) { return this.dataSourceLookup.getDataSource((String) dataSource); } else { throw new IllegalArgumentException( "Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource); } } public Connection getConnection() throws SQLException { return determineTargetDataSource().getConnection(); } public Connection getConnection(String username, String password) throws SQLException { return determineTargetDataSource().getConnection(username, password); } @Override @SuppressWarnings("unchecked") public <T> T unwrap(Class<T> iface) throws SQLException { if (iface.isInstance(this)) { return (T) this; } return determineTargetDataSource().unwrap(iface); } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return (iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface)); } protected DataSource determineTargetDataSource() { Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); Object lookupKey = determineCurrentLookupKey(); DataSource dataSource = this.resolvedDataSources.get(lookupKey); if (dataSource == null && (this.lenientFallback || lookupKey == null)) { dataSource = this.resolvedDefaultDataSource; } if (dataSource == null) { throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); } return dataSource; } protected abstract Object determineCurrentLookupKey(); }