数据库读写分离程序实现--AOP方式(SpringMVC+Mybatis+Mysql5.6)

简介

笔者上篇文章写到数据库主从配置–<数据库主从配置入门级>,通过主从复制的方式来同步数据,再通过读写分离来提升数据库的并发负载能力
笔者在<数据库读写分离程序实现–ReplicationDriver方式>介绍了通过ReplicationDriver+Transactional注解的方式实现读写分离
本文笔者将使用AOP+多数据源动态切换的方式实现读写分离
在目前项目开发中,由于用户流量的逐渐增加、业务模块的增加,应用单数据库已无法满足业务需要,从而衍生了数据库读写分离、业务数据分库存储、数据库集群等数据库操作模式来提高访问效率、数据归类、减轻数据数据库压力。
这里写图片描述
本图为引用借鉴

单一数据源

如上图第一部分,一般中小型项目普遍使用单一数据源的模式

多数据源

图第二部分

图二部分可以看到,配置了两个sessionfactory分别对应两个datasoure,在做数据层操作时,可在到层接口上指定数据源,从而达到多库访问。(淡然可配置多个datasoure+sessionfactory)

图第三部分

图三部分可以看到,配置了两个datasoure,使用AbstractRoutingDataSource的实现类通过AOP或者手动处理实现动态的使用我们的数据源,这样的入侵性较低。通过AOP或者手动编程设置当前的DataSource,不用修改我们编写的数据访问层接口

本文使用AbstractRoutingDataSource动态切换数据源的形式实现数据库读写分离

db.properties

##DB1
db.c3p0.driverClassName=com.mysql.jdbc.ReplicationDriver   
## 使用的是jdbc:mysql:replication://  
db.c3p0.url=jdbc:mysql:replication://XXXX:XXX,XXXX:XXX/smart_db_study?characterEncoding=utf8
db.c3p0.username=XXX
db.c3p0.password=XXX
##DB2
db1.domain=log
db1.c3p0.driverClassName=com.mysql.jdbc.Driver
db1.c3p0.url=jdbc:mysql://XXX:XXX/smart_db_study?useUnicode=true&characterEncoding=utf8
db1.c3p0.username=${db.c3p0.username}
db1.c3p0.password=${db.c3p0.password}

db.mybatis.aliases=com.lswd.mapper
db.mybatis.mapperloc=classpath:com/**/mapper/**/*Mapper.xml

MultipleC3p0DataSource数据源配置

package com.lswd.db.config;

import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.util.StringUtils;

import com.lswd.db.dynamic.DynamicDataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;

@Configuration
@PropertySource("classpath:db.properties") 
public class MultipleC3p0DataSource {

    @Autowired
    private Environment environment;

    private DataSource getDataSource(int index){
        try {

            String i = index > 0 ? String.format("%d", index) : "";
            //读取配置文件数据源配置信息,通过index区分
            String url = environment.getProperty("db"+i+".c3p0.url");
            if (StringUtils.isEmpty(url)){
                return null;
            }

            String prefix = StringUtils.isEmpty(i) ? "" : "db"+i;

             ComboPooledDataSource dataSource = new ComboPooledDataSource();
             String dbclass = environment.getProperty("db"+i+".c3p0.driverClassName", String.class, "com.mysql.jdbc.Driver");
             dataSource.setDriverClass(dbclass);
             dataSource.setJdbcUrl(url);
             dataSource.setUser(environment.getProperty("db"+i+".c3p0.username"));
             dataSource.setPassword(environment.getProperty("db"+i+".c3p0.password"));
             dataSource.setAcquireIncrement(environment.getProperty(prefix+"c3p0.acquireIncrement", int.class, 5));
             dataSource.setInitialPoolSize(environment.getProperty(prefix+"c3p0.initialPoolSize", int.class, 5));
             dataSource.setMaxPoolSize(environment.getProperty(prefix+"c3p0.maxPoolSize", int.class, 200));
             dataSource.setMinPoolSize(environment.getProperty(prefix+"c3p0.minPoolSize", int.class, 5));
             dataSource.setMaxIdleTime(environment.getProperty(prefix+"c3p0.maxIdleSize", int.class, 1800));
             dataSource.setMaxIdleTimeExcessConnections(environment.getProperty(prefix+"c3p0.maxIdleTimeExcessConnections", int.class, 1200));
             dataSource.setMaxConnectionAge(environment.getProperty(prefix+"c3p0.maxConnectionAge", int.class, 1000));
             dataSource.setPreferredTestQuery(environment.getProperty(prefix+"c3p0.preferredTestQuery", "select 1 from dual"));
             dataSource.setIdleConnectionTestPeriod(environment.getProperty(prefix+"c3p0.idleConnectionTestPeriod", int.class, 120));
             dataSource.setAutoCommitOnClose(false);

            return dataSource;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    @Bean
    @Primary
    //创建数据源
    public DynamicDataSource dataSource() {
        DynamicDataSource dataSource = new DynamicDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        int i = 0;
        DataSource ds = getDataSource(i);
        while (ds != null) {
            String key = environment.getProperty("db" + i + ".domain");
            if (StringUtils.isEmpty(key)){
                key = "mapper";
                dataSource.setDefaultTargetDataSource(ds);// 默认的datasource设置为mapper
            }
            targetDataSources.put(key, ds);
            i ++;
            ds = getDataSource(i);
        }

        dataSource.setTargetDataSources(targetDataSources);
        return dataSource;
    }

    // use mybatis
    @Bean
    public SqlSessionFactory sqlSessionFactory(DynamicDataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        sessionFactory.setTypeAliasesPackage(environment.getProperty("db.mybatis.aliases")));
        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources(environment.getProperty("db.mybatis.mapperloc"))) )); 
        return sessionFactory.getObject();
    }

    @Bean(value="txManager")
    public DataSourceTransactionManager dataSourceTransactionManager(DynamicDataSource dataSource) {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }

}

DatabaseContextHolder–当前线程使用的数据源名

package com.lswd.db.dynamic;

import java.util.ArrayList;
import java.util.List;

public class DatabaseContextHolder {
    //当前线程使用的数据源名
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
     // 设置当前线程使用的数据源名
    public static void setDatabaseType(String type){
        contextHolder.set(type);
    }
    // 获取前线程使用的数据源名
    public static String getDatabaseType(){
        return contextHolder.get();
    }
    // 清除前线程使用的数据源名
    public static void clearDataSourceType() {  
        contextHolder.remove();  
    } 
}

DynamicDataSource–多数据源的选择

package com.lswd.db.dynamic;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {
    //根据Key获取数据源的信息,上层抽象函数的钩子
    @Override
    protected Object determineCurrentLookupKey() {
       // 获取前线程使用的数据源名
        String key = DatabaseContextHolder.getDatabaseType();
        return key;
    }
}

通过AOP切面的方式拦截查询方法,将数据源切换至从库

package com.lswd.db.dynamic;

import java.lang.reflect.Method;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.aspectj.lang.reflect.MethodSignature;
@Aspect
@Component
public class MultipleDataSourceSelectorAspect {

    /**
     * 使用空方法定义切点表达式
     */
    @Pointcut("execution(public * com.lswd.mapper..*Mapper..*(..))")
    public void declareJointPointExpression() {
    }

    private static Pattern p = Pattern.compile("\\.([^\\.]+)\\.[^\\.]+$");

    /**
     * 使用定义切点表达式的方法进行切点表达式的引入
     */
    @Around("declareJointPointExpression()")
    public Object setDataSourceKey(ProceedingJoinPoint point) throws Throwable {
        Object result = null;
        try {
            MethodSignature ms= (MethodSignature)point.getSignature();
            Method mt = ms.getMethod();
            if(mt.getName().contains("query")){
                System.out.println(mt.getName() + ": set datasource to query");
                DatabaseContextHolder.setDatabaseType("log");
            }else{
                DatabaseContextHolder.setDatabaseType("mapper");
            }       
            result = point.proceed();
        } finally {
            DatabaseContextHolder.clearDataSourceType();
        }
        return result;
    }
}

测试

    //@Transactional(value = "txManager",propagation=Propagation.REQUIRED,rollbackFor=Exception.class,readOnly=true)
   Integer selectTalCount();
      Integer queryTalCount();
@Test
    public  void pushMeun() throws SQLException{
        System.out.println("条数为frist1:"+manageImageMapper.queryTalCount());
        System.out.println("条数为frist2:"+manageImageMapper.selectTalCount());

    }

这里写图片描述
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值