前言
读写分离
通常我们往数据库中 “写” 的时候要比"读"慢很多,所以读写分离,解决的是,数据库的写入,影响了查询的效率。
实现思路:
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
这个数据源