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实现数据源的动态代理是比较方便,在不需要切换数据源的情况,去除切换代码不影响其他功能,同时易于程序扩展。

  Connection getConnection(String us
ername, String password)
    throws SQLException;
相关文章
相关标签/搜索