SSM框架(4):实现主从数据库读写分离

一、主从读写分离的概念

1、什么是主从读写分离?

  一般而言,应用APP对数据库都是“读多写少”,意味着数据库的读取压力比较大。为了解决这个问题,有一个方案就是说采用数据库集群,其中一台是主库,专门负责写入数据,我们称之为“写库”;其它都是从库,负责读取查询数据,我们称之为“读库”;

2、主从读写分离,对数据库的要求,有以下几点:

  • 读库和写库的数据一致;
  • 写数据必须写到写库;
  • 读数据必须到读库

二、实现方案

1、在应用层增加逻辑,实现主从读写分离

  在编写业务逻辑时,根据查询、修改的业务类型,主动地设置去访问不同的数据源。

  (1)优点:

  • 多数据源切换方便,由程序自动完成;
  • 不需要引入中间件;
  • 理论上支持任何数据库;

  (2)缺点:

  • 由程序员完成,运维参与不到;
  • 不能做到动态增加数据源;

2、使用第三方中间件,由中间件来控制读写分离

  使用第三方中间件控制的方式,对应用程序而言,相当于访问一个数据源。

  (1)优点:

  • 源程序不需要做任何改动就可以实现读写分离;
  • 动态添加数据源不需要重启程序;

  (2)缺点:

  • 程序依赖于中间件,会导致切换数据库变得困难;
  • 由中间件做了中转代理,性能有所下降;

注:以上内容转载自网络
原文标题:使用java Spring实现读写分离( MySQL实现主从复制)
原文地址:https://blog.csdn.net/liu976180578/article/details/77684583

三、项目实战

         推荐阅读:主从读写实现:方法名前缀方式、数据库策略方式、一主多从方式

实现原理:

  在开始编码前,统一service层方法名的命名规则,比如,查询类型的方法统一前缀为"query、search、find、get",而其他前缀的方法统一认定为是修改操作。每次接收到请求时,在执行service层的方法前,通过切面的方式获取要执行方法的名称,判读方法名称的前缀是否为"query、search、find、get",如果是,则设置数据源为从库;如果不是,则设置数据源为主库。从而实现主从读写分离。  

步骤一:首先,在项目中实现多数据源访问的功能。

    实现方式,可参考以下资源:

         【原创】SSM框架(2):多数据源支持,动态配置依赖的数据源

步骤二:定义切面DataSourceAspect,植入到service层方法之中

package com.newbie.util.dbReadWriteSeparation;

import com.newbie.util.dbMultipleRouting.DynamicDataSourceHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 *  定义数据源的AOP切面,通过该Service的方法名判断是应该走读库还是写库
 */
@Component
@Aspect
public class DataSourceAspect {
    /**
     * 定义切面植入的增强功能:在service对象的方法执行前,进行切入
     * @param joinpoint : 切面对象
     */
    @Before("execution(* *..service.*.*(..))")
    public void before(JoinPoint joinpoint){
        String methodName = joinpoint.getSignature().getName();
        //根据方法名称的前缀,判断是否是查询方法
        //如果是查询方法,则使用从库;否则,使用主库
        if(isSlaveMethod(methodName)){
            //标记为从库
            DynamicDataSourceHolder.setDataSourceKey("slave1");
        }else {
            //标记为主库
            DynamicDataSourceHolder.setDataSourceKey("master");
        }
    }

    /**
     * 根据方法名称的前缀,判断是否是查询方法
     * @param methodName : 方法名称
     * @return true :是查询方法<br/>
     *          false : 不是查询方法
     */
    public boolean isSlaveMethod(String methodName){
        if(methodName == null){
            return false;
        }
        //遍历方式,判断方法的前缀
        List<String> methodPrefix = new ArrayList<String>();
        methodPrefix.add("query");
        methodPrefix.add("search");
        methodPrefix.add("find");
        methodPrefix.add("get");
        Iterator<String> iterator = methodPrefix.iterator();
        while (iterator.hasNext()){
            String prefixName = iterator.next();
            if(methodName.startsWith(prefixName)){
                return true;
            }
        }
        return false;
    }
}

步骤三、编写service层的业务逻辑

package com.newbie.service.impl;

import com.newbie.dao.UserMapper;
import com.newbie.domain.User;
import com.newbie.service.IReadWriteSeparationService;
import com.newbie.util.dbMultipleRouting.DynamicDataSourceHolder;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Iterator;
import java.util.List;
/**
 * 主从读写分离的实现
 */
@Service
public class ReadWriteSeparationService implements IReadWriteSeparationService {
    @Resource
    private UserMapper userMapper;

    /**
     * 向主库增加一个用户
     */
    public String insertOneUser() {
        String message = "插入完成";
        message += "<p>当前线程数据源:key = "+ DynamicDataSourceHolder.getDataSourceKey()+"</p>";
        User user = new User();
        String id = String.valueOf(System.currentTimeMillis());
        user.setId(id);
        user.setUsername("我是新插入的用户");
        userMapper.insertSelective(user);
        return message;
    }
    /**
     * 使用非query方法,会被认为是修改类操作,查询主库数据
     * @return
     */
    public String notQueryAllUser() {
        String message = "<p>查询成功<p>";
        message += "<p>当前线程数据源:key = "+ DynamicDataSourceHolder.getDataSourceKey()+"</p>";
        //查询数据库中的所有用户信息
        List<User> users = userMapper.selectByExample(null);
        Iterator<User> iterator = users.iterator();
        while (iterator.hasNext()){
            User user = iterator.next();
            message += "<p>用户ID : "+user.getId()+" , 用户名 : "+user.getUsername()+" , 数据来源:"+user.getRemark()+"</p>";
        }
        System.out.println("================== Service.notQueryAllUser() : 当前线程数据源 = "+DynamicDataSourceHolder.getDataSourceKey());
        System.out.println("================== Service.notQueryAllUser() : 返回信息 = "+message);
        return message;
    }
    /**
     * 使用query方法,查询从库数据
     * @return
     */
    public String queryAllUser() {
        String message = "<p>查询成功<p>";
        message += "<p>当前线程数据源:key = "+ DynamicDataSourceHolder.getDataSourceKey()+"</p>";
        //查询数据库中的所有用户信息
        List<User> users = userMapper.selectByExample(null);
        Iterator<User> iterator = users.iterator();
        while (iterator.hasNext()){
            User user = iterator.next();
            message += "<p>用户ID : "+user.getId()+" , 用户名 : "+user.getUsername()+" , 数据来源:"+user.getRemark()+"</p>";
        }
        System.out.println("================== Service.notQueryAllUser() : 当前线程数据源 = "+DynamicDataSourceHolder.getDataSourceKey());
        System.out.println("================== Service.queryAllUser() : 返回信息 = "+message);
        return message;
    }
}

步骤五、编写控制层Controller 和 测试视图

package com.newbie.controller;

import com.newbie.service.IReadWriteSeparationService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.annotation.Resource;

/**
 * 测试:主从读写分离的实现
 */
@Controller
public class ReadWriteSeparationController {
    @Resource
    IReadWriteSeparationService readWriteSeparationService;
    /**
     * 向主库增加一个用户
     */
    @RequestMapping("/insertOneUser")
    public String insertOneUser(Model model) {
        String message = readWriteSeparationService.insertOneUser();
        model.addAttribute("message", message);
        return "showInfo";
    }
    /**
     * 使用非query方法,查询主库数据
     */
    @RequestMapping("/notQueryAllUser")
    public String notQueryAllUser(Model model) {
        String message = readWriteSeparationService.notQueryAllUser();
        model.addAttribute("message", message);
        return "showInfo";
    }
    /**
     * 使用query方法,查询从库数据
     */
    @RequestMapping("/queryAllUser")
    public String queryAllUser(Model model) {
        String message = readWriteSeparationService.queryAllUser();
        model.addAttribute("message", message);
        return "showInfo";
    }

}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title></title>
</head>
<body>
<h2>练习三:实现主从读写分离</h2>
<a href="insertOneUser">插入主库:向主库增加一个用户</a><br/><br/>
<a href="queryAllUser">查询从库:使用query方法,查询从库数据</a><br/><br/>
<a href="notQueryAllUser">查询主库(非正常操作):使用非query方法,查询主库数据</a><br/><br/>
</body>
</html>

步骤六、执行程序,观察结果

原文地址:https://www.cnblogs.com/newbie27/p/10843823.html