目录
1:MybatisPlus(读写分离)
1.1:首先创建三个数据库1主2从
1.2:代码实例
1.3:优缺点分析
2:SpringBoot路由数据源(读写分离)
2.1:实现原理
2.2:代码实现
2.3:测试代码
2.4:优缺点分析
这里列举了两种读写分离实现方案,如下
1:MybatisPlus(读写分离)
1.1:首先创建三个数据库1主2从
表名是user表
1.2:代码实例
1:导入pom
<!--MybatisPlus的jar 3.0基于jdk8-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- mybatisPlus多数据源依赖 实现读写分离-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.6.1</version>
</dependency>
<!-- mysql的依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!--直接使用druid的starter 连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
2:配置spring的主从
server:
port: 8082
spring:
datasource:
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为master
strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
datasource:
master:
url: jdbc:mysql://localhost:3306/W1?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource #配置德鲁伊数据源
druid:
initial-size: 10 #连接池初始化大小
min-idle: 10 #最小空闲连接数
max-active: 20 #最大连接数
slave_1:
url: jdbc:mysql://localhost:3306/W1R1?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource #配置德鲁伊数据源
druid:
initial-size: 10 #连接池初始化大小
min-idle: 10 #最小空闲连接数
max-active: 20 #最大连接数
slave_2:
url: jdbc:mysql://localhost:3306/W1R2?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource #配置德鲁伊数据源
druid:
initial-size: 10 #连接池初始化大小
min-idle: 10 #最小空闲连接数
max-active: 20 #最大连接数
#......省略
#以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2
3:代码实例 @DS("slave") 注解用来切换数据源
/**
* @description 针对表【User】的数据库操作Service实现
* @createDate 2023-11-01 17:17:57
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{
@Resource
UserMapper userMapper;
//多个从库 负载均衡轮训查询 也可以注解到类上
//@DS("slave")
@DS("slave")
public User selectRW_S(Integer id) {
return userMapper.selectById(id);
}
//主库查询数据
//@DS("master")
@DS("master")
public User selectRW_M(Integer id) {
return userMapper.selectById(id);
}
//无注解,不指定数据, 默认查询主库
@Override
public User selectRW(Integer id) {
return userMapper.selectById(id);
}
}
controller代码根据请求不同访问不同的数据源
@RestController
public class UserControllerRW {
@Resource
UserServiceImpl userService;
//查询主库 @DS("master")
@GetMapping(value = "selectRW1")
public User selectRW_M(@RequestParam(name = "id",required = true) Integer id){
User user = userService.selectRW_M(id);
System.out.println(user);
return user;
}
//查询从库 @DS("slave") 轮训查询
@GetMapping(value = "selectRW2")
public User selectRW_S(@RequestParam(name = "id",required = true) Integer id){
User user = userService.selectRW_S(id);
System.out.println(user);
return user;
}
//无注解,不指定数据, 默认查询主库
@GetMapping(value = "selectRW3")
public User selectRW(@RequestParam(name = "id",required = true) Integer id){
User user = userService.selectRW(id);
System.out.println(user);
return user;
}
}
查询结果截图
1.3:优缺点分析
dynamic-datasource的jar包的官方文档
基础必读(免费) · dynamic-datasource · 看云
本框架只做切换数据的事情,不限制你的具体操作,从库也可以增删改查,读写分离只是他的一个小功能,分库分表要自己实现
- 支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。
- 支持数据库敏感配置信息 加密(可自定义) ENC()。
- 支持每个数据库独立初始化表结构schema和数据库database。
- 支持无数据源启动,支持懒加载数据源(需要的时候再创建连接)。
- 支持 自定义注解 ,需继承DS(3.2.0+)。
- 提供并简化对Druid,HikariCp,BeeCp,Dbcp2的快速集成。
- 提供对Mybatis-Plus,Quartz,ShardingJdbc,P6sy,Jndi等组件的集成方案。
- 提供 自定义数据源来源 方案(如全从数据库加载)。
- 提供项目启动后 动态增加移除数据源 方案。
- 提供Mybatis环境下的 纯读写分离 方案。
- 提供使用 spel动态参数 解析数据源方案。内置spel,session,header,支持自定义。
- 支持 多层数据源嵌套切换 。(ServiceA >>> ServiceB >>> ServiceC)。
- 提供 基于seata的分布式事务方案 。
- 提供 本地多数据源事务方案。
2:SpringBoot路由数据源(读写分离)
2.1:实现原理
实现方案是通过在spring容器中注入多个数据源,通过不同的key来找到指定的数据源,AbstractRoutingDataSource的源码知道,AbstractRoutingDataSource里边通过Map可以过个数据源,通过key查找。
2.2:代码实现
1:maven依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
<!--aop依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- mysql的依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!--直接使用druid的starter 连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
</dependencies>
2:配置数据源的1主2从的application.properties
#读写分离数据源配置
#读写分离数据源配置
#配置主数据库
spring.datasource.master.jdbc-url=jdbc:mysql://localhost:3306/W1?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.master.username=root
spring.datasource.master.password=123456
spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
#配置读数据库1
spring.datasource.read1.jdbc-url=jdbc:mysql://localhost:3306/W1R1?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.read1.username=root
spring.datasource.read1.password=123456
spring.datasource.read1.driver-class-name=com.mysql.cj.jdbc.Driver
#配置读数据库2
spring.datasource.read2.jdbc-url=jdbc:mysql://localhost:3306/W1R2?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.read2.username=root
spring.datasource.read2.password=123456
spring.datasource.read2.driver-class-name=com.mysql.cj.jdbc.Driver
3:代码注入多个数据源
@Configuration
public class MyDataSourceConfig {
/**
* 主库
* @return
*/
@Bean(value = "master")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource(){
return DataSourceBuilder.create().build();
}
/**
* 从库1
* @return
*/
@Bean(value = "read1")
@ConfigurationProperties(prefix = "spring.datasource.read1")
public DataSource read1DataSource(){
return DataSourceBuilder.create().build();
}
/**
* 从库2
* @return
*/
@Bean(value = "read2")
@ConfigurationProperties(prefix = "spring.datasource.read2")
public DataSource read2DataSource(){
return DataSourceBuilder.create().build();
}
/**
* 配置4个数据源 1个路由数据源 3个自定义数据源
* 自定义的路由数据源
* @return
*/
@Bean(value = "myRoutingDataSource")
public DataSource myRoutingDataSource(@Qualifier(value="master")DataSource master,
@Qualifier(value="read1")DataSource read1,
@Qualifier(value="read2") DataSource read2){
//自定义路由数据源
MyRoutingDataSource routingDataSource=new MyRoutingDataSource();
//目标数据源 master read1 read2
Map<Object, Object> targetDataSources=new HashMap<>();
targetDataSources.put("master",master);
targetDataSources.put("read1",read1);
targetDataSources.put("read2",read2);
//放入默认数据源是 master
routingDataSource.setDefaultTargetDataSource(master);
routingDataSource.setTargetDataSources(targetDataSources);
return routingDataSource;
}
}
4:数据源切换代码实现
public class DBContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal();
private static final AtomicInteger count = new AtomicInteger(1);
//ThreadLocal的get、set方法设置数据源
public static String get() {
return contextHolder.get();
}
public static void set(String dataSource) {
contextHolder.set(dataSource);
}
public static void remove() {
contextHolder.remove();
}
/**
* 获取 主数据库
*/
public static void master() {
set("master");
System.out.println("切换到master数据源");
}
/**
* 获取从数据库 随机获取 或者随机获取
*/
public static void read() {
// //轮询数据源进行读操作
// Random random=new Random();
// int index = random.nextInt(10);
// System.out.println("随机数:"+index);
// if(index>5){
// set("read1");
// System.out.println("切换到read1数据源");
// }else {
// set("read2");
// System.out.println("切换到read2数据源");
// }
int index = count.getAndIncrement()%2;
System.out.println("模数:" + index);
System.out.println("随机数:" + count);
//轮询数据源进行读操作
if (count.get() > 10) {
count.set(1);
}
if (index == 0) {
set("read1");
System.out.println("切换到slave1数据源");
} else {
set("read2");
System.out.println("切换到slave2数据源");
}
}
public static void main(String[] args) {
//轮询数据源进行读操作
Random random = new Random();
int index = random.nextInt(10);
System.out.println(index);
}
}
5:获取路由数据源
/**
* 自定义路由数据源 继承AbstractRoutingDataSource
*/
public class MyRoutingDataSource extends AbstractRoutingDataSource {
/**
* 通过指定的key来确认当前的数据源方法
*
* 目标数据源 master read1 read2
* Map<Object, Object> targetDataSources=new HashMap<>();
* targetDataSources.put("master",master);
* targetDataSources.put("read1",read1);
* targetDataSources.put("read2",read2);
*
*
* 根据Aop 在执行不同方法之前 设置不同的数据源的key
*/
@Override
protected Object determineCurrentLookupKey() {
return DBContextHolder.get();
}
}
6:mybatis的数据源配置
/**
* mybatis的数据源配置
* 由于Spring容器中现在有4个数据源,所以我们需要为事务管理器和MyBatis手动指定一个明确的数据源。
*/
@Configuration
public class MyBatisConfig {
@Resource(name = "myRoutingDataSource")
private DataSource myRoutingDataSource;
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(myRoutingDataSource);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
return sqlSessionFactoryBean.getObject();
}
@Bean
public PlatformTransactionManager platformTransactionManager() {
return new DataSourceTransactionManager(myRoutingDataSource);
}
}
7:AOP切入处理,在service方法中通过切面提前设置数据源的key
@Aspect
@Component
public class DataSourceAop {
/**
* 不存在自定义的Master注解
* 所有service包下的的所有select和get方法
* 设置为读从库数据
*/
@Pointcut("!@annotation(com.example.springboot08_rw1.annotation.Master) " +
"&& (execution(* com.example.springboot08_rw1.service..*.select*(..)) " +
"|| execution(* com.example.springboot08_rw1.service..*.get*(..)))")
public void readPointcut() {
}
/**
* 存在Master注解或者add insert等方法 去主库操作
*/
@Pointcut("@annotation(com.example.springboot08_rw1.annotation.Master) " +
"|| execution(* com.example.springboot08_rw1.service..*.insert*(..)) " +
"|| execution(* com.example.springboot08_rw1.service..*.add*(..)) " +
"|| execution(* com.example.springboot08_rw1.service..*.update*(..)) " +
"|| execution(* com.example.springboot08_rw1.service..*.edit*(..)) " +
"|| execution(* com.example.springboot08_rw1.service..*.delete*(..)) " +
"|| execution(* com.example.springboot08_rw1.service..*.remove*(..))")
public void writePointcut() {
}
@Before("readPointcut()")
public void read() {
DBContextHolder.read();
}
@Before("writePointcut()")
public void write() {
DBContextHolder.master();
}
// /**
// * 另一种写法:if...else... 判断哪些需要读从数据库,其余的走主数据库
// */
// @Before("execution(* com.example.springboot08_rw1.service.UserServiceImpl.select*(..))")
// public void before(JoinPoint jp) {
// String methodName = jp.getSignature().getName();
// System.out.println("方法名字:"+methodName);
if (StringUtils.startsWithAny(methodName, "get", "select", "find")) {
}else {
DBContextHolder.master();
}
//
// DBContextHolder.read();
//
// }
}
//自定义注解
public @interface Master {
}
2.3:测试代码
service代码
@Service
public class UserServiceImpl implements UserService {
@Resource
UserMapper userMapper;
/**
* 添加自定义注解 @Master 主库查询
*/
@Master
public User selectById_Master(Integer id) {
return userMapper.selectById(1);
}
/**
* 不使用注解 走从库查询
*/
public User selectById_MoRen(Integer id) {
return userMapper.selectById(1);
}
/**
* 不使用注解 add方法 走主库
*/
public User add(Integer id) {
return userMapper.selectById(1);
}
}
controller层的代码测试
@RestController
public class UserController {
@Autowired
UserServiceImpl userService;
//两个从库切换查询
@GetMapping(value = "selectUser")
public User select(){
return userService.selectById_MoRen(1);
}
//使用注解 查询主库
@GetMapping(value = "selectUser1")
public User select1(){
return userService.selectById_Master(1);
}
//方法是add 查询主库
@GetMapping(value = "selectUser2")
public User select2(){
return userService.add(1);
}
}
页面效果
http://localhost:8082/selectUser两个从切换查询
http://localhost:8082/selectUser1主库查询
http://localhost:8082/selectUser2主库查询
2.4:优缺点分析
SpringBoot读写分离_springboot 读写分离-CSDN博客文章来源:https://www.toymoban.com/news/detail-738437.html
代码比较复杂,需要制定好spring的数据源切换的基础原理,分库分表代码进一步负载话文章来源地址https://www.toymoban.com/news/detail-738437.html
到了这里,关于SpringBoot_第七章(读写分离)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!