SpringBoot+Mybatis+MySQL实现读写分离

在Springboot中使用数据库主从复制实现读写分离,操作数据时操作主表,查询操作使用从表。

我就直接跳过创建SpringBoot应用的步骤了,直接开始操作如何配置读写分离。

1、我的Maven依赖如下,大家可以复制

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

4.0.0

org.springframework.boot

spring-boot-starter-parent

2.3.3.RELEASE

com.example

demo

0.0.1-SNAPSHOT

demo

Demo project for Spring Boot

1.8

org.springframework.boot

spring-boot-starter-aop

org.springframework.boot

spring-boot-starter-jdbc

org.springframework.boot

spring-boot-starter-web

org.mybatis.spring.boot

mybatis-spring-boot-starter

1.3.2

org.apache.commons

commons-lang3

3.8

mysql

mysql-connector-java

runtime

org.springframework.boot

spring-boot-starter-test

test

com.alibaba

fastjson

1.2.46

org.springframework.boot

spring-boot-starter-data-redis

org.springframework.boot

spring-boot-starter-mail

org.springframework.boot

spring-boot-devtools

true

org.springframework.boot

spring-boot-maven-plugin

2、配置数据源,我的application.yml配置如下,大家根据自己的配置进行修改。

#主从数据库配置

spring:

datasource:

master:

jdbc-url: jdbc:mysql://localhost:3306/task_db?serverTimezone=UTC

username: root

password: Caiyi0.0

driver-class-name: com.mysql.jdbc.Driver

slave1:

jdbc-url: jdbc:mysql://172.18.237.231:3307/task_db?serverTimezone=UTC

username: root # 只读账户

password: mysql

driver-class-name: com.mysql.jdbc.Driver

3、然后我们进行多数据源配置,新建一个config目录,创建一个DataSourceConfig.class文件,加上@Configuration注解。

package com.example.demo.config;

import com.example.demo.bean.MyRoutingDataSource;

import com.example.demo.enums.DBTypeEnum;

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 org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

import java.util.HashMap;

import java.util.Map;

@Configuration

public class DataSourceConfig {

@Bean(name = "masterDataSource")

@ConfigurationProperties("spring.datasource.master")

public DataSource masterDataSource(){

return DataSourceBuilder.create().build();

}

@Bean(name = "slave1DataSource")

@ConfigurationProperties("spring.datasource.slave1")

public DataSource slave1DataSource() {

return DataSourceBuilder.create().build();

}

@Bean

@Primary

public DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("slave1DataSource") DataSource slave1DataSource) {

Map targetDataSources = new HashMap<>();

targetDataSources.put(DBTypeEnum.MASTER, masterDataSource);

targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource);

MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource();

myRoutingDataSource.setDefaultTargetDataSource(masterDataSource);

myRoutingDataSource.setTargetDataSources(targetDataSources);

return myRoutingDataSource;

}

}

这里我们配置了3个数据源,一个主数据源,一个从数据源,一个路由数据源。前2个数据源都是为了生成第3个数据源,而且后续我们只用这最后一个路由数据源。

4、然后我们配置一个MyBatis的配置,因为我们现在有3个数据源,所以我们需要为事务管理器和MyBatis手动指定一个明确的数据源。

package com.example.demo.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 javax.annotation.Resource;

import javax.sql.DataSource;

@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);

}

}

5 、设置路由Key/查找数据源

目标数据源就是两个数据源,一个主数据源和从数据源,但是使用的时候要查找数据源。

首先,我们先定义一个枚举类来代表这三个数据源

package com.example.demo.enums;

public enum DBTypeEnum {

MASTER, SLAVE1;

}

接下来,我们通过ThreadLocal将数据源设置到每个线程上下文中

package com.example.demo.bean;

import com.example.demo.enums.DBTypeEnum;

import java.util.concurrent.atomic.AtomicInteger;

public class DBContextHolder {

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);

System.out.println("切换到master");

}

public static void slave() {

set(DBTypeEnum.SLAVE1);

System.out.println("切换到slave1");

}

}

获取路由Key

package com.example.demo.bean;

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

import org.springframework.lang.Nullable;

public class MyRoutingDataSource extends AbstractRoutingDataSource {

@Nullable

@Override

protected Object determineCurrentLookupKey() {

return DBContextHolder.get();

}

}

默认情况下,所有的查询都走从库,插入/修改/删除走主库。我们通过方法名来区分操作类型(CRUD),我们通过过Spring的AOP来进行配置

package com.example.demo.aop;

import com.example.demo.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;

@Aspect

@Component

public class DataSourceAop {

//不用master注解时或者select或者get起头的方法查询从表

@Pointcut("!@annotation(com.example.demo.annotation.Master) " +

"&& (execution(* com.example.demo.service..*.select*(..)) " +

"|| execution(* com.example.demo.service..*.get*(..)))")

public void readPointcut() {

}

//使用master注解时或者增删改数据库时使用主表

@Pointcut("@annotation(com.example.demo.annotation.Master) " +

"|| execution(* com.example.demo.service..*.insert*(..)) " +

"|| execution(* com.example.demo.service..*.add*(..)) " +

"|| execution(* com.example.demo.service..*.update*(..)) " +

"|| execution(* com.example.demo.service..*.edit*(..)) " +

"|| execution(* com.example.demo.service..*.delete*(..)) " +

"|| execution(* com.example.demo.service..*.remove*(..))")

public void writePointcut() {

}

@Before("readPointcut()")

public void read() {

DBContextHolder.slave();

}

@Before("writePointcut()")

public void write() {

DBContextHolder.master();

}

}

如果有特殊情况,例如某些情况下我们需要强制读主库,针对这种情况,我们可以自定义一个注解,然后用这注解标注的方法就读主库。

package com.example.demo.annotation;

public @interface Master {

}

至此,配置完成,下面我们进行测试。

6、测试

假设我们有一个User表。

package com.example.demo.serviceImpl;

import com.example.demo.Dao.TbUserDAO;

import com.example.demo.entity.TbUser;

import com.example.demo.service.UserService;

import org.apache.tomcat.util.http.fileupload.IOUtils;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.cache.annotation.CacheEvict;

import org.springframework.cache.annotation.CachePut;

import org.springframework.cache.annotation.Cacheable;

import org.springframework.stereotype.Service;

import org.springframework.transaction.annotation.Transactional;

import org.springframework.web.multipart.MultipartFile;

import java.io.File;

import java.io.FileOutputStream;

import java.io.IOException;

import java.util.List;

import java.util.UUID;

@Service

public class UserServiceImpl implements UserService {

private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);

@Autowired

TbUserDAO userDAO;

@CachePut(value = "user", key = "#user.id")

@Transactional

@Override

public int insert(TbUser user) {

int i=userDAO.insert(user);

System.out.println(user.getId());

return i;

}

@CacheEvict(value = {"userById"}, key = "#id")

@Override

public int deleteByPrimaryKey(String id) {

Integer userId= new Integer(id);

return userDAO.deleteByPrimaryKey(userId);

}

@Override

public List getList(TbUser user) {

return userDAO.getList(user);

}

@Cacheable(value ={"userById"}, key = "#id")

@Override

public TbUser selectById(String id) {

System.out.println("从数据库查");

Integer userId= new Integer(id);

return userDAO.selectByPrimaryKey(userId);

}

@CacheEvict(value = {"userById"}, key = "#user.id")

@Override

public int updateByPrimaryKey(TbUser user) {

return userDAO.updateByPrimaryKey(user);

}

@Override

public String uploadFile(MultipartFile zipFile) {

String targetFilePath = "H:\\uploadFile";

String fileName = UUID.randomUUID().toString().replace("-", "");

File targetFile = new File(targetFilePath + File.separator + fileName);

FileOutputStream fileOutputStream = null;

try {

fileOutputStream = new FileOutputStream(targetFile);

IOUtils.copy(zipFile.getInputStream(), fileOutputStream);

logger.info("------>>>>>>uploaded a file successfully!<<<<<

} catch (IOException e) {

return "false";

} finally {

try {

fileOutputStream.close();

} catch (IOException e) {

logger.error("", e);

}

}

return "成功";

}

}

Controller代码如下:

package com.example.demo.controller;

import com.alibaba.fastjson.JSONObject;

import com.example.demo.Dao.TbUserDAO;

import com.example.demo.entity.TbUser;

import com.example.demo.service.UserService;

import com.example.demo.utils.APIException;

import com.example.demo.utils.ResultVO;

import com.example.demo.utils.SendMailUtil;

import com.fasterxml.jackson.databind.util.JSONPObject;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.redis.core.RedisTemplate;

import org.springframework.data.redis.serializer.RedisSerializer;

import org.springframework.data.redis.serializer.StringRedisSerializer;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.MethodArgumentNotValidException;

import org.springframework.web.bind.annotation.*;

import org.springframework.web.multipart.MultipartFile;

import java.lang.reflect.Type;

import java.util.List;

@RestController

@RequestMapping("/User")

public class UserController {

@Autowired

UserService userService;

@Autowired

SendMailUtil sendMailUtil;

@Autowired

RedisTemplate redisTemplate;

@RequestMapping(value = "/insertUser",method = RequestMethod.POST )

public int insertUser(@RequestBody String Json){

TbUser jsonpObject=JSONObject.parseObject(Json, (Type) TbUser.class);

return userService.insert(jsonpObject);

}

@RequestMapping(value = "/updateUser",method = RequestMethod.POST )

public int updateUser(@RequestBody String Json){

TbUser jsonpObject=JSONObject.parseObject(Json, (Type) TbUser.class);

return userService.updateByPrimaryKey(jsonpObject);

}

@RequestMapping(value = "/hello",method = RequestMethod.GET)

public String hello(){

return "hello world";

}

@PostMapping("/deleteUser")

public int deleteUserById(@RequestParam String id){

return userService.deleteByPrimaryKey(id);

}

@PostMapping("/selectById")

public TbUser selectUserById(@RequestParam String id){

return userService.selectById(id);

}

@PostMapping("/getList")

public List getList(@RequestBody String json) {

//给qq邮箱发送邮件事例

// sendMailUtil.sendMail("765783376@qq.com","邮件标题","邮件内容");

// try{

// int i=1/0;

// }catch (Exception e){

// throw new APIException(3000,e.getMessage());

// }

TbUser jsonpObject=JSONObject.parseObject(json, (Type) TbUser.class);

return userService.getList(jsonpObject);

}

/**

* 图片上传接口

* @param

* @return

*/

@PostMapping("/upload")

public String Upload(@RequestParam("file") MultipartFile zipFile) {

return userService.uploadFile(zipFile);

}

}

如果项目启动出错,请查看启动类是否配置了@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})

mysql evict_SpringBoot+Mybatis+MySQL实现读写分离相关推荐

  1. java spring mysql配置_java相关:mysql+spring+mybatis实现数据库读写分离的代码配置

    java相关:mysql+spring+mybatis实现数据库读写分离的代码配置 发布于 2020-4-4| 复制链接 分享一篇关于关于mysql+spring+mybatis实现数据库读写分离的代 ...

  2. mysql+spring+mybatis实现数据库读写分离[代码配置] .

    场景:一个读数据源一个读写数据源. 原理:借助spring的[org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource] ...

  3. Mysql主从配置,实现读写分离

    大型网站为了软解大量的并发访问,除了在网站实现分布式负载均衡,远远不够.到了数据业务层.数据访问层,如果还是传统的数据结构,或者只是单单靠一台服务器扛,如此多的数据库连接操作,数据库必然会崩溃,数据丢 ...

  4. 高性能高可用MySQL(主从同步,读写分离,分库分表,去中心化,虚拟IP,心跳机制)

    高性能高可用MySQL(主从同步,读写分离,分库分表,去中心化,虚拟IP,心跳机制) 视频地址:https://www.bilibili.com/video/BV1ry4y1v7Tr?p=8& ...

  5. MySQL主从配置与Mycat读写分离

    MySQL主从配置与Mycat读写分离 一.主数据库配置 编辑配置文件 添加如下内容 server-id=1 binlog-do-db=master_db1 #备份的数据库 log-bin=mysql ...

  6. 黄聪mysql_黄聪:Mysql主从配置,实现读写分离

    大型网站为了软解大量的并发访问,除了在网站实现分布式负载均衡,远远不够.到了数据业务层.数据访问层,如果还是传统的数据结构,或者只是单单靠一台服务器扛,如此多的数据库连接操作,数据库必然会崩溃,数据丢 ...

  7. mybatis实现数据库读写分离

    ps:本文解决mybatis实现数据库读写分离,项目基础是ruoyi-vue 方案一: 自定义一个注解@DataSource, 利用aop切该注解,切了后设置注解@DataSource的值到Threa ...

  8. 基于mysql主从同步的proxy读写分离

    mysql-proxy 简介 MySQL Proxy是一个处于你的client端和MySQL server端之间的简单程序,它可以监测.分析或改变它们的通信.它使用灵活,没有限制,常见的用途包括:负载 ...

  9. MySQL用中间件ProxySQL实现读写分离和主节点故障应用无感应

    昨天做的用proxysql实现的读写分离,但是在实际的应用中这样的结构还很不完整,如果主节点出现故障那么整个拓扑的数据库也无法通过proxysql来调用了,所以还需要增加主节点故障后proxysql能 ...

  10. MySQL高级 - 案例 - 系统性能优化 - 读写分离概述

    性能优化 - 排序 在查询数据时,如果业务需求中需要我们对结果内容进行了排序处理 , 这个时候,我们还需要对排序的字段建立适当的索引, 来提高排序的效率 . 概述 在Mysql主从复制的基础上,可以使 ...

最新文章

  1. 用opencv的traincascade.exe训练行人的HAAR、LBP和HOG特征的xml
  2. java第一阶段知识_坚持:学习Java后台的第一阶段,我学习了那些知识
  3. 揭秘企业级web负载均衡完美架构
  4. 算法4------字符串的字典序最长子序列
  5. 自定义xy组 android,Android自定义view之仿支付宝芝麻信用仪表盘示例
  6. Eclipse查找文件存储路径
  7. 认知觉醒是成长的首因,送3本硬核认知提升书
  8. python文件、存储、压缩
  9. qtextedit 默认文案_Qt设置QTextEdit和QLabel的字体颜色 | 学步园
  10. OpenGL基础2:OpenGL简介
  11. LINUX SHELL脚本多行注释
  12. 2017cad光标大小怎么调_关于调整input里面的输入光标大小
  13. go产生平台相关的0xFFFF
  14. 大数据之实时数据分析之Apache Doris数据库
  15. 本特利监控卡件3500/42M/128229-01后模块
  16. ftp服务器怎么删文件夹,删除ftp服务器文件夹
  17. Mac 如何消除系统更新小红点
  18. 支付宝AR扫福是怎么实现的?
  19. ORACLE实现MySQL中substring_index函数功能
  20. JS函数:具名函数、匿名函数、自执行函数

热门文章

  1. html测试身高体重,【 身高体重测试】_如何测试_注意事项-大众养生网
  2. Linux系统的安装与配置
  3. Spark应用启动报错:Could not locate executable null\bin\winutils.exe in the Hadoop binaries.
  4. OpenCV变脸大法--‘让妖怪现原形‘(附源码)
  5. java语言画图_Java语言实现画图工具
  6. OpenEmbedded 简介
  7. mysql salve从库设置read only 属性
  8. 宝宝出现这些突发状况!你会处理吗?
  9. 控件:可见、隐身、走开
  10. 企业微信群:机器人定时提醒功能数据库配置化