首页 > 其他分享 >实现读写分离SpringBoot+MyBatis+Druid

实现读写分离SpringBoot+MyBatis+Druid

时间:2023-09-10 17:33:58浏览次数:60  
标签:SpringBoot spring Druid datasource org MyBatis import com public

实现读写分离 SpringBoot+MyBatis+Druid 1.读写分离概念理解 读写分离要做的事情就是对于一条SQL该选择哪个数据库去执行,至于谁来做选择数据库这件事儿,无非两个,要么中间件帮我们做,要么程序自己做。因此,一般来讲,读写分离有两种实现方式。第一种是依靠中间件(比如:MyCat),也就是说应用程序连接到中间件,中间件帮我们做SQL分离;第二种是应用程序自己去做分离。这里我们选择程序自己来做,主要是利用Spring提供的路由数据源,以及AOP。然而,应用程序层面去做读写分离最大的弱点(不足之处)在于无法动态增加数据库节点,因为数据源配置都是写在配置中的,新增数据库意味着新加一个数据源,必然改配置,并重启应用。当然,好处就是相对简单。

读写分离涉及到数据库主从同步

数据库主从同步:https://www.cnblogs.com/cjsblog/p/9706370.html

  1. AbstractRoutingDataSource 基于特定的查找key路由到特定的数据源。它内部维护了一组目标数据源,并且做了路由key与目标数据源之间的映射,提供基于key查找数据源的方法。

开始吧! 工程结构

1.引入maven依赖 4.0.0 org.springframework.boot spring-boot-starter-parent 2.4.0 com.example multidatasource 0.0.1-SNAPSHOT multidatasource 读写分离多数据源

<properties>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.2</version>
    </dependency>
     <dependency>
          <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-configuration-processor</artifactId>
         <optional>true</optional>
     </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.10</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </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>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

2.数据源配置 spring.datasource.master.url=jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC spring.datasource.master.username=root spring.datasource.master.password= spring.datasource.master.type=com.alibaba.druid.pool.DruidDataSource

spring.datasource.slave1.url=jdbc:mysql://localhost:3306/db02?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC spring.datasource.slave1.username=root spring.datasource.slave1.password= spring.datasource.slave1.type=com.alibaba.druid.pool.DruidDataSource

spring.datasource.slave2.url=jdbc:mysql://localhost:3306/db03?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC spring.datasource.slave2.username=root spring.datasource.slave2.password= spring.datasource.slave2.type=com.alibaba.druid.pool.DruidDataSource

#Spring Boot 默认是不注入这些属性值的,需要自己绑定 #druid 数据源专有配置 #配置初始化大小、最小、最大 spring.datasource.initialSize= 5 spring.datasource.minIdle= 5 spring.datasource.maxActive= 20 #配置获取连接等待超时的时间 spring.datasource.maxWait= 60000 #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 spring.datasource.timeBetweenEvictionRunsMillis= 60000 #配置一个连接在池中最小生存的时间,单位是毫秒 spring.datasource.minEvictableIdleTimeMillis= 300000 #用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。

如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。

spring.datasource.validationQuery=SELECT 1 FROM DUAL spring.datasource.testWhileIdle=true spring.datasource.testOnBorrow=false spring.datasource.testOnReturn=false

#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入 #如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority #则导入 log4j 依赖即可,Maven 地址: https://mvnrepository.com/artifact/log4j/log4j spring.datasource.filters=stat,wall,log4j #是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 pring.datasource.poolPreparedStatements= true spring.datasource.maxPoolPreparedStatementPerConnectionSize= 20 spring.datasource.useGlobalDataSourceStat= true

配置监控统计拦截的filters

spring.datasource.stat-view-servlet.url-pattern=/druid/* spring.datasource.stat-view-servlet.reset-enable= false spring.datasource.druid.stat-view-servlet.login-username=admin

spring.datasource.druid.stat-view-servlet.login-password=123 spring.datasource.druid.stat-view-servlet.enabled=true

#设置ip黑名单和白名单 #spring.datasource.druid.stat-view-servlet.allow=127.0.0.1 #spring.datasource.druid.stat-view-servlet.deny=

#过滤所有请求 spring.datasource.url-pattern= /* #排除哪些请求 spring.datasource.web-stat-filter.exclusions= ".js,.gif,.jpg,.bmp,.png,.css,.ico,/druid/"

3.加载数据库配置DataSourceConfig package com.example.multidatasource.config;

import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import com.example.multidatasource.bean.MyRoutingDataSource; import com.example.multidatasource.type.DBTypeEnum; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import sun.rmi.runtime.Log;

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

/**

  • @create: 2020-11-16 00:28 **/ @Configuration @Slf4j public class DataSourceConfig {
    /*
  • HikariDataSource改为druid
  • */ @Bean @ConfigurationProperties(prefix = "spring.datasource.master") public DataSource masterDataSource(){ return DruidDataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.slave1") public DataSource slave1DataSource(){ return DruidDataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.slave2") public DataSource slave2DataSource(){ return DruidDataSourceBuilder.create().build(); } @Bean public DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("slave1DataSource") DataSource slave1DataSource, @Qualifier("slave2DataSource") DataSource slave2DataSource) { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DBTypeEnum.MASTER, masterDataSource); targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource); targetDataSources.put(DBTypeEnum.SLAVE2, slave2DataSource); log.info(targetDataSources.toString()); MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource(); myRoutingDataSource.setDefaultTargetDataSource(masterDataSource); myRoutingDataSource.setTargetDataSources(targetDataSources); return myRoutingDataSource; }

/* SpringBoot 默认使用 HikariDataSource @Bean @ConfigurationProperties(prefix = "spring.datasource.master") public DataSource masterDataSource(){ log.info(DataSourceBuilder.create().build().toString()); return DataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.slave1") public DataSource slave1DataSource(){ return DataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.slave2") public DataSource slave2DataSource(){ return DataSourceBuilder.create().build(); }*/

/* spring.datasource.master.jdbc-url= jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC spring.datasource.master.username= root spring.datasource.master.password= spring.datasource.master.driver-class-name= com.mysql.jdbc.Driver spring.datasource.slave1.jdbc-url= jdbc:mysql://localhost:3306/db02?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC spring.datasource.slave1.username= root spring.datasource.slave1.password= spring.datasource.slave1.driver-class-name= com.mysql.jdbc.Driver spring.datasource.slave2.jdbc-url= jdbc:mysql://localhost:3306/db03?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC spring.datasource.slave2.username= root spring.datasource.slave2.password= spring.datasource.slave2.driver-class-name= com.mysql.jdbc.Driver*/

}

4.MyBatis配置 package com.example.multidatasource.config;

import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.annotation.Resource; import javax.sql.DataSource;

/**

  • @create: 2020-11-16 01:08 **/ @EnableTransactionManagement @Configuration public class MybatisConfig { @Resource(name = "myRoutingDataSource") private DataSource myRoutingDataSource;
    /由于Spring容器中现在有4个数据源,所以我们需要为事务管理器和MyBatis手动指定一个明确的数据源。/ @Bean public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
 sqlSessionFactoryBean.setDataSource(myRoutingDataSource);
 //我采取的是注解式sql,如果加上扫描,但包下无mapper.xml会报错
 //sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));

 return sqlSessionFactoryBean.getObject();

} @Bean public PlatformTransactionManager platformTransactionManager() { return new DataSourceTransactionManager(myRoutingDataSource); } }

5.数据源名枚举区分 public enum DBTypeEnum { MASTER,SLAVE1,SLAVE2 } 6. 线程上下文工具 contextHolder 是线程变量,因为每个请求是一个线程,所以通过这样来区分使用哪个库 determineCurrentLookupKey是重写的AbstractRoutingDataSource的方法, 主要是确定当前应该使用哪个数据源的key,因为AbstractRoutingDataSource 中保存的多个数据源是通过Map的方式保存的

package com.example.multidatasource.bean;

import com.example.multidatasource.type.DBTypeEnum; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component;

import java.util.concurrent.atomic.AtomicInteger;

/**

  • @create: 2020-11-16 01:00 **/ @Slf4j @Component public class DBContextHolder {
    /*
  • contextHolder 是线程变量,因为每个请求是一个线程,所以通过这样来区分使用哪个库 determineCurrentLookupKey是重写的AbstractRoutingDataSource的方法, 主要是确定当前应该使用哪个数据源的key,因为AbstractRoutingDataSource 中保存的多个数据源是通过Map的方式保存的
  • */ private static final ThreadLocal contextHolder=new ThreadLocal<>();

private static final AtomicInteger counter =new AtomicInteger(-1);

//设置当前线程所用数据库类型 public static void set(DBTypeEnum dbType){ contextHolder.set(dbType); }

//获取当前线程所用数据类型 public static DBTypeEnum get() { return contextHolder.get(); } public static void master() { set(DBTypeEnum.MASTER); log.info("切换到master"); }

public static void slave() { // 轮询 int index = counter.getAndIncrement() % 2; if (counter.get() > 9999) { counter.set(-1); } if (index == 0) { set(DBTypeEnum.SLAVE1); log.info("切换到slave1"); }else { set(DBTypeEnum.SLAVE2); log.info("切换到slave2"); } }

}

7.获取路由Key package com.example.multidatasource.bean;

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

/**

  • @program: learn
  • @description:获取路由key **/ public class MyRoutingDataSource extends AbstractRoutingDataSource { @Override @Nullable protected Object determineCurrentLookupKey() { return DBContextHolder.get(); } }

8.默认情况下,所有的查询都走从库,插入/修改/删除走主库。我们使用AOP面向切面通过方法名来区分操作类型(CRUD) package com.example.multidatasource.aop;

import com.example.multidatasource.bean.DBContextHolder; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component;

/**

  • @create: 2020-11-16 01:18 **/ @Aspect @Component public class DataSourceAspect {
    @Pointcut("!@annotation(com.example.multidatasource.annotation.Master) " + "&& (execution(* com.example.multidatasource.service...select(..)) " + "|| execution(* com.example.multidatasource.service...get(..)))") public void readPointcut(){
    } @Pointcut("@annotation(com.example.multidatasource.annotation.Master) " + "|| execution(* com.example.multidatasource.service...insert(..)) " + "|| execution(* com.example.multidatasource.service...add(..)) " + "|| execution(* com.example.multidatasource.service...update(..)) " + "|| execution(* com.example.multidatasource.service...edit(..)) " + "|| execution(* com.example.multidatasource.service...delete(..)) " + "|| execution(* com.example.multidatasource.service...remove(..))") public void writePointcut(){
    } @Before("readPointcut()") public void read() { DBContextHolder.slave(); }
    @Before("writePointcut()") public void write() { DBContextHolder.master(); }

}

8.1 有一般情况就有特殊情况,特殊情况是某些情况下我们需要强制读主库,针对这种情况,我们定义一个主键,用该注解标注的就读主库

/特殊情况是某些情况下我们需要强制读主库,针对这种情况,我们定义一个主键,用该注解标注的就读主库/ public @interface Master { } 9.实体类+DAO+Service package com.example.multidatasource.entity;

import lombok.Data;

@Data public class User { private String id; private String name;

public User(String id, String name) {
    this.id = id;
    this.name = name;
}

}

package com.example.multidatasource.dao;

import com.example.multidatasource.entity.User; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.springframework.stereotype.Repository;

import java.util.List;

@Mapper @Repository public interface UserDao {

@Select("select * from user")
public List<User> selectAllUser();

@Insert("insert into user(id,name) values(#{user.id},#{user.name})")
public int  insertUser(@Param("user") User user);

}

package com.example.multidatasource.service;

import com.example.multidatasource.dao.UserDao; import com.example.multidatasource.entity.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;

import java.util.List;

@Service public class UserService {

@Autowired
private UserDao userDao;


public List<User> selectAllUser(){
   return userDao.selectAllUser();
}

public int  insertUser(User user){
    return userDao.insertUser(user);
}

}

10.测试类 package com.example.multidatasource;

import com.example.multidatasource.entity.User; import com.example.multidatasource.service.UserService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest;

import java.util.List; import java.util.Random; import java.util.UUID;

@SpringBootTest class MultidatasourceApplicationTests {

@Autowired
UserService userService;
@Test
void contextLoads() {
}
@Test
public void testWrite() {

    userService.insertUser(new User(UUID.randomUUID().toString(),"XXX"));
}

@Test
public void testRead() {
    for (int i = 0; i < 4; i++) {
        List<User> users = userService.selectAllUser();
        System.out.println(users.toString());
    }
}

}

标签:SpringBoot,spring,Druid,datasource,org,MyBatis,import,com,public
From: https://blog.51cto.com/u_3641166/7427085

相关文章

  • 在springboot项目种引入element组件
    1、保证vue的版本在3以上2、Win+R--打开命令行窗口(cmd)输入下面的命令,打开图形化界面:vueui3、打开我们创建的vue项目选择路径即可自主导入项目;4、安装element-ui的插件依赖5、查看项目中是否存在ok!......
  • 数据库连接池Druid使用方法
    数据库连接池Druid使用方法一、Druid连接池使用代码示例importcom.alibaba.druid.pool.DruidAbstractDataSource;importcom.alibaba.druid.pool.DruidDataSource;importcom.alibaba.druid.pool.DruidDataSourceFactory;importorg.junit.Test;importjavax.sql.DataSource;......
  • 实现数据库连接池druid的工具类
    一、数据库连接迟druid工具类importcom.alibaba.druid.pool.DruidDataSourceFactory;importorg.apache.commons.beanutils.PropertyUtils;importjavax.sql.DataSource;importjava.io.IOException;importjava.sql.*;importjava.util.ArrayList;importjava.util.List;......
  • mybatis核心配置文件以及mapper文件的配置
    config文件<?xmlversion="1.0"encoding="UTF-8"?><!DOCTYPEconfigurationPUBLIC"-//mybatis.org//DTDConfig3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration>......
  • Mybatis 学习
    1.第一个程序1.1配置数据库表中包含id、name、password1.2配置pom.xmlmysql-connector5.几有问题,用8.几mybatisjunit<build<resources中的directory、includes、filetering中为false。否则在junit的测试中,xml文件会被过滤1.3准备POJO层数据类有id、name、password,......
  • 基于SpringBoot的高校党员信息管理系统的设计与实现-计算机毕业设计源码+LW文档
    摘要:中国的高校线上党建在国内有着非常好的使用前景,所以决定开发基于SpringBoot的高校党员信息管理系统。本系统能够满足党员的日常学习的需要,以及适应现代化党员管理的需求。本系统开发设计思想是实现在线管理的数字化。达到帮助高校进行网上管理,使党员管理工作更加高效的目的。......
  • SpringBoot基本知识
    SpringBoot基本知识一、简介1、springBoot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,SpringBoot致力于在蓬勃发展的快速应用开发领域(ra......
  • MyBatis实现In查询(XTHS 实测)
    一. SQL语法实现In查询SQL语句实现In查询SELECT*FROMuser_infoWHEREuser_namein('xixi','haha');二. MyBatis实现In查询错误范例:如果在MyBatis中也使用类似SQL语法来实现In查询,像如下示例,肯定会报错,因为MyBatis不支持这样的写法。//Dao层List<UserInf......
  • Springboot项目中pom.xml配置文件无法解析下载oracl数据库解决办法(Cannot resolve com
    网上说是因Oracle的版权问题,导致maven下载不下来ojdbc各个版本的jar包。就会报错Cannotresolvecom.oracle:ojdbc6:11.2.0.1.0 经过一番百度,找到了一个适用的解决方法,如下操作即可:1.在终端或客户端机器上找到oracle安装驱动目录:例如:E:\myorcl\product\11.2.0\dbhome_1\j......
  • Springboot集成OceanBase4.x
    概述    在Springboot项目中使用Oceanbase4.2版本数据库。pomPS:可在maven仓库中搜索oceanbase,第一个就是。<dependency> <groupId>com.oceanbase</groupId> <artifactId>oceanbase-client</artifactId> <version>2.4.4</version></dependency>......