springBoot 使用AOP完成多数据源配置。写用主库,读用从库

前言

读写分离

通常我们往数据库中 “写” 的时候要比"读"慢很多,所以读写分离,解决的是,数据库的写入,影响了查询的效率

实现思路:

mysql端:
首先我们要搭建MySql主从复制的环境,也就是往主库中写入数据会同步到从库中。搭建教程:https://blog.csdn.net/qq_41594146/article/details/100121934

java端:
java端要做的就是当我们写入数据的时候用主库,读取数据的时候用从库。下面就直接上代码了

配置多数据源,实现读写分离

pom.xml

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.4</version>
        </dependency>

        <!--引入druid数据源 这是阿里巴巴提供的数据源-->
        <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>


        <!-- AOP -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!--Properties动态注入 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <!--导入热部署插件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional> <!-- 这个需要为 true 热部署才有效 -->
        </dependency>

首先我们配置两个数据源:

application.properties

#修改tomcat端口为80
server.port=8080
#设置Tomcat编码
server.tomcat.uri-encoding=UTF-8

#MyBatis配置
mybatis.config-location=classpath:mybatis-cfg.xml
mybatis.type-aliases-package=com.cpc.springboot.mapper
mybatis.mapper-locations=classpath*:com/cpc/springboot/mapper/*.xml
        
#数据库连接池配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.initial-size = 5
spring.datasource.druid.max-active = 20
spring.datasource.druid.min-idle = 5
spring.datasource.druid.max-wait= 30000
        

#数据库1配置(主)
spring.datasource.one.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.one.url=jdbc:mysql://192.168.43.161:3306/cpc
spring.datasource.one.username=root
spring.datasource.one.password=root

#数据库2配置(从)
spring.datasource.two.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.two.url=jdbc:mysql://192.168.43.160:3306/cpc
spring.datasource.two.username=root
spring.datasource.two.password=root

mybatis-cfg.xml:
这里我使用的是通过注解的方式配置,所以这个文件没啥配置咯

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

</configuration>

DruidDataSourceConfig

读取我们配置的数据源

package com.cpc.springboot.comfig;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

/**
 *
 * @ClassName: DruidDataSourceConfig
 * @Description: 多数据源切换
 *
 */
@Configuration
@MapperScan(value = "com.cpc.springboot.mapper",  sqlSessionFactoryRef = "sqlSessionFactory")
public class DruidDataSourceConfig {

    /**
     * 配置别名
     */
    @Value("${mybatis.type-aliases-package}")
    private String typeAliasesPackage;

    /**
     * 配置mapper的扫描,找到所有的mapper.xml映射文件
     */
    @Value("${mybatis.mapper-locations}")
    private String mapperLocations;

    /**
     * 加载全局的配置文件
     */
    @Value("${mybatis.config-location}")
    private String configLocation;

    /**
     * 数据源1
     */
    @Bean(name = "oneDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.one")
    public DataSource dataSourceOne() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 数据源2
     */
    @Bean(name = "twoDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.two")
    public DataSource dataSourceTwo() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 数据源管理
     */
    @Bean
    public DataSource dynamicDataSource() throws SQLException {
        DynamicDataSource dynmicDataSource = new DynamicDataSource();
        //这事将数据源放入到 targetDataSources 这个map集合中
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("oneDataSource", dataSourceOne());
        targetDataSources.put("twoDataSource", dataSourceTwo());
        //设置数据源
        dynmicDataSource.setTargetDataSources(targetDataSources);
        //设置默认数据源为  dataSourceOne
        dynmicDataSource.setDefaultTargetDataSource(dataSourceOne());
        return dynmicDataSource;
    }

    /**
     * SqlSessionFactory 配置并放入容器中
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource")DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        sqlSessionFactoryBean.setTypeAliasesPackage(typeAliasesPackage);
        sqlSessionFactoryBean.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
        return sqlSessionFactoryBean.getObject();
    }

    /**
     * 事物
     */
    @Bean
    public PlatformTransactionManager transactionManager(@Qualifier("dynamicDataSource")DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

}

DynamicDataSource


package com.cpc.springboot.comfig;

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

/**
 *
 * @ClassName: DynamicDataSource
 * @Description: 根据determineCurrentLookupKey 返回的key来决定本次使用那个数据源
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    /*
     * 这事获取当前  contextHolders中设置的数据源名称,框架内部会更具我们返回的数据源名称去找对应的数据源。使用那个数据
     * 源来对数据库进行操作
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getDataSource();
    }

}

DynamicDataSourceHolder

package com.cpc.springboot.comfig;

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

/**
 *
 * @ClassName: DynamicDataSourceHolder
 * @Description: 存放数据源对应的key
 *
 */
public class DynamicDataSourceHolder {

    /*
     * 线程本地环境,这个很重要,防止线程之间出现冲突的问题。详情参考: https://www.jianshu.com/p/3c5d7f09dfbd
     */
    private static final ThreadLocal<String> contextHolders = new ThreadLocal<String>();

    /*
     * 数据源列表
     */
    public static List<String> dataSourceIds = new ArrayList<String>();

    /*
     * 设置数据源
     */
    public static void setDataSource(String customerType) {
        contextHolders.set(customerType);
    }

    /*
     * 获取数据源
     */
    public static String getDataSource() {
        return (String) contextHolders.get();
    }

    /*
     * 清除数据源
     */
    public static void clearDataSource() {
        contextHolders.remove();
    }

    /*
     * 判断指定DataSrouce当前是否存在
     */
    public static boolean containsDataSource(String dataSourceId) {
        return dataSourceIds.contains(dataSourceId);
    }
}

TargetDataSource
这是指定要使用的数据源的注解

package com.cpc.springboot.comfig;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 *此注解在 service 的方法上指定数据源
 * @ClassName: TargetDataSource
 * @Description: 自定义切换数据源
 *
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
    //指定数据源名称 
    String dataSource();
}

DynamicDataSourceAspect
多数据源配置的AOP切面类

package com.cpc.springboot.aspect;

import com.cpc.springboot.comfig.DynamicDataSourceHolder;
import com.cpc.springboot.comfig.TargetDataSource;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;



/**
 *
 * @ClassName: DynamicDataSourceAspect
 * @Description: 定义切面用于切换数据源,此切面会更具  TargetDataSource 中  dataSource 这个值来切换数据源
 *
 */
@Aspect
@Component
public class DynamicDataSourceAspect {

	//请更具你的项目目录解构更改
    @Pointcut("@annotation(com.cpc.springboot.comfig.TargetDataSource)")
    public void point() {
    }
	
	//请更具你的项目目录解构更改
    @Pointcut("execution(public * com.cpc.springboot.service.*.*.*(..))")
    public void excudeService() {
    }

    /**
     * 着就是处理切面的方法
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around(value = "point()&&excudeService()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method targetMethod = methodSignature.getMethod();
        if (targetMethod.isAnnotationPresent(TargetDataSource.class)) {
            //这事获取注解中设置的数据源
            String targetDataSource = targetMethod.getAnnotation(TargetDataSource.class).dataSource();
            //这事更改当前的数据源
            DynamicDataSourceHolder.setDataSource(targetDataSource);
        }
        Object result = pjp.proceed();// 执行方法
        //清空刚刚设置的数据源,又成默认的数据源了
        DynamicDataSourceHolder.clearDataSource();
        return result;
    }
}

我们已经配置好了多数据源,默认我们使用呢的是 oneDataSource 这个数据源(不指定的情况下),下面我们可以在server层指定我们要使用的数据源为twoDataSource

    @Override
    //查询使用 twoDataSource 这个数据源
    @TargetDataSource(dataSource = "twoDataSource")
    public Department getDeptById(Integer id) {
        return departmentMapper.selectByPrimaryKey(id);
    }

其他方法不加注解使用的依然是我们默认的oneDataSource 这个数据源

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要实现PHP代码中连接数据分离服务器,可以按照以下步骤进行: 1. 配置从服务器:在数据配置服务器和从服务器,确保服务器和从服务器的数据同步正常,并且从服务器可以服务器的数据。 2. 安装负载均衡器:安装一个负载均衡器,如LVS、HAProxy等,用于实现分离和从切换的功能。 3. 配置负载均衡器:通过配置负载均衡器,将操作转发到从服务器,将操作转发到服务器,并且在服务器宕机时,自动切换到从服务器。 4. 编PHP代码:在PHP代码中,使用PDO或mysqli等连接数据,并且在连接参数中指定负载均衡器的IP地址和端口号,以实现自动访问或从进行操作。 举个例子,假设我们有一个数据服务器和两个从数据服务器,我们可以在PHP代码中使用如下的连接参数实现分离: ```php // 连接数据服务器 $dsn = 'mysql:host=服务器IP地址;port=服务器端口号;dbname=数据名'; $username = '用户名'; $password = '密码'; $options = array( PDO::ATTR_PERSISTENT => true, // 使用持久连接 PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION // 抛出异常 ); $pdo = new PDO($dsn, $username, $password, $options); // 连接从数据服务器 $dsn = 'mysql:host=从服务器IP地址;port=从服务器端口号;dbname=数据名'; $username = '用户名'; $password = '密码'; $options = array( PDO::ATTR_PERSISTENT => true, // 使用持久连接 PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION // 抛出异常 ); $pdo = new PDO($dsn, $username, $password, $options); ``` 在实际使用中,我们可以根据具体的需求和系统架构,进行灵活的配置和调整,以实现更高效和可靠的数据访问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值