1. 程式人生 > >Java實現讀寫分離

Java實現讀寫分離

一前言

                    在網際網路專案中,隨著業務的增加,訪問的增長,對系統性能,擴充套件性,伸縮性提出更高的同時,資料庫的壓力也陡然增加。越來越多的系統採用分散式系統架構,在資料方面,也採用資料庫叢集,與此同時,基於系統訪問,資料的查詢較多,增刪改較少,為了減少資料壓力,提高系統響效率,採用資料庫讀寫分離也是一個較好的選擇。採用此種策略,不僅使提高系統的響應時效,而且利於資料庫的擴充套件。

二 理論

                   java的形式的讀寫分離有兩種:

                  1.配置多個數據源,根據業務需求訪問不同的資料,指定對應的策略

,增加,刪除,修改操作訪問對應資料,查詢訪問對應資料,不同資料庫做好的資料一致性的處理。

 由於此方法相對易懂,簡單,不做過多介紹。

                2.動態切換資料來源,根據配置的檔案,業務動態切換訪問的資料庫此方案通過Spring的AOP,AspactJ來實現動態織入,通過程式設計繼承實現Spring中的AbstractRoutingDataSource,來實現資料庫訪問的動態切換,不僅可以方便擴充套件,不影響現有程式,而且對於此功能的增刪也比較容易。下面做詳細介紹。

                  a.首先AbstractRoutingDataSource繼承自Spirng的AbstractDataSource,AbstractDataSource實現了java.sql的DataSource資料介面。

                  DataSource有如下兩個方法:下述方法獲取資料庫連線。

                   //獲取資料庫連線

          Connection getConnection() throws SQLException;

 
                    //需通過安全認證,獲取資料庫連線
          Connection getConnection(String username, String password)  throws SQLException;

          AbstractDataSource實現上述方法如下:
         //通過determineTargetDataSource()方法獲取的例項獲取資料庫連線。

         public Connection getConnection() throws SQLException {
                return determineTargetDataSource().getConnection();
              }
        //通過determineTargetDataSource()方法獲取的例項獲取需要安全認證的資料庫連線。
public Connection getConnection(String username, String password) throws SQLException { return determineTargetDataSource().getConnection(username, password); } //獲取資料來源
protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
            //獲取對應資料來源的屬性鍵(因resolvedDataSources是map資料結構)
        Object lookupKey = determineCurrentLookupKey();
            //根據該引數獲取資料來源(因resolvedDataSources是map資料結構
        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;
    }

            b.編寫一個工具類繼承AbstractRoutingDataSource來動態獲取資料來源。


    public class DataSourceTool extends AbstractRoutingDataSource {

        //多個副本,實現多執行緒的資源共享
         public static final ThreadLocal<String> holder = new ThreadLocal<String>();
    
@Override
    protected Object determineCurrentLookupKey() {
        return   DataSourceTool .getDataSouce();
    }


    public static void putDataSource(String name) {
        holder.set(name);
    }


    public static String getDataSouce() {
        return holder.get();
    }

       }

   c.通過AspactJ實現動態織入程式碼,實現程式碼的無浸入新增。


public class DataSourceAspectJ {
    //在方法呼叫之前進行切面操作
    public void before(JoinPoint point)
    {   //獲取目標物件
        Object target = point.getTarget();
//獲取方法簽名信息:然後獲取於方法名
        String method = point.getSignature().getName();
        //獲取目標物件類實現的介面
        Class<?>[] classz = target.getClass().getInterfaces();
        
//獲取該方法簽名對應方法的引數型別
        Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
                .getMethod().getParameterTypes();
        try {
    //獲取目標物件的方法
            Method m = classz[0].getMethod(method, parameterTypes);
//若方法不為空,且方法上的註解是DataSource
            if (m != null && m.isAnnotationPresent(DataSource.class)) {
                DataSource data = m
                        .getAnnotation(DataSource.class);
                DynamicDataSourceHolder.putDataSource(data.value());
                System.out.println(data.value());
            }
            
        } catch (Exception e) {
            e.printStrace();
        }
    }
}

 d.Spring檔案配置:

<!--資料來源配置,主從庫配置  -->

 <bean id="masterdataSource"
        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/master" />
        <property name="username" value="root" />
        <property name="password" value="root" />
    </bean>


    <bean id="slavedataSource"
        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/slave" />
        <property name="username" value="root" />
        <property name="password" value="root" />
    </bean>
    
        <beans:bean id="dataSource" class="com.myd.cn.db.DataSourceTool ">
        <property name="targetDataSources">  
              <map key-type="java.lang.String">  
                  <!-- write -->
                 <entry key="master" value-ref="masterdataSource"/>  
                 <!-- read -->
                 <entry key="slave" value-ref="slavedataSource"/>  
              </map>  
              
        </property>  
        <property name="defaultTargetDataSource" ref="masterdataSource"/>  
    </beans:bean>


    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>




    <!-- 配置SqlSessionFactoryBean -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:config/mybatis-config.xml" />
    </bean>


<!--AspactJ配置 -->


<!--AspactJ動態代理 -->

 <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    <beans:bean id="manyDataSourceAspect" class="com.myd.cn.proxy.DataSourceAspectJ" />
    <aop:config>
        <aop:aspect id="c" ref="manyDataSourceAspect">
            <aop:pointcut id="tx" expression="execution(* com.myd.cn.mapper.*.*(..))"/>
            <aop:before pointcut-ref="tx" method="before"/>
        </aop:aspect>
    </aop:config>

e:以下是MyBatis的UserMapper的定義

public interface UserMapper {
    @DataSource("master")
    public void add(User user);


    @DataSource("master")
    public void update(User user);


    @DataSource("master")
    public void delete(int id);


    @DataSource("slave")
    public User loadbyid(int id);


    @DataSource("master")
    public User loadbyname(String name);
    
    @DataSource("slave")
    public List<User> list();
}

三 總結

總的來說,AspectJ實現資料來源的動態代理是比較方便,在不需要切換資料來源的情況,去除切換程式碼不影響其他功能,同時易於程式擴充套件。