深入理解Spring Boot结合MyBatis调用MySQL,并实现主从复制读写分离

MyBatis作为一款灵活的持久层框架,与Spring Boot结合使用,可以为开发者提供高效、简洁的数据库访问层。本文将详细讲解如何在Spring Boot项目中结合MyBatis调用MySQL,并实现主从复制的读写分离。我们将以电商交易系统为案例,通过实际代码示例展示如何进行配置和实现。

第1章 Spring Boot项目中集成MyBatis
1.1 Maven依赖配置

在Spring Boot项目中集成MyBatis,首先需要在pom.xml文件中引入相关的Maven依赖。以下是最基本的依赖配置:

xml复制代码<dependencies>
    <!-- Spring Boot Starter MyBatis -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.4</version>
    </dependency>

    <!-- MySQL Driver -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- Spring Boot Starter Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

上述依赖确保了Spring Boot能够自动配置MyBatis和MySQL驱动,从而简化了数据库访问的配置工作。

1.2 MyBatis配置

在Spring Boot中,MyBatis的配置通常通过application.propertiesapplication.yml文件进行。以下是application.properties文件中的基本配置示例:

# 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/ecommerce?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=yourpassword
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# MyBatis配置
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.example.ecommerce.model
  • spring.datasource.url:指定数据库连接URL。
  • spring.datasource.username:数据库用户名。
  • spring.datasource.password:数据库密码。
  • spring.datasource.driver-class-name:JDBC驱动类名。
  • mybatis.mapper-locations:指定MyBatis的XML映射文件路径。
  • mybatis.type-aliases-package:指定MyBatis的别名包路径,用于简化实体类的配置。
1.3 创建实体类与Mapper接口

首先,我们需要定义实体类来映射数据库表,例如定义一个Order类来映射订单表:

package com.example.ecommerce.model;

import java.io.Serializable;

public class Order implements Serializable {
    private Long id;
    private String orderNumber;
    private Double totalAmount;

    // getters and setters
}

接下来,定义一个Mapper接口,该接口用于声明数据库操作方法:

package com.example.ecommerce.mapper;

import com.example.ecommerce.model.Order;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface OrderMapper {
    
    @Select("SELECT * FROM orders")
    List<Order> findAllOrders();

    void insertOrder(Order order);
}

在MyBatis中,可以使用注解或XML文件定义SQL语句。上述示例中,findAllOrders方法使用注解定义了一个简单的查询语句。

1.4 配置XML映射文件

如果希望使用XML文件来管理SQL语句,可以在resources/mapper目录下创建一个OrderMapper.xml文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.ecommerce.mapper.OrderMapper">

    <resultMap id="OrderResultMap" type="com.example.ecommerce.model.Order">
        <id column="id" property="id"/>
        <result column="order_number" property="orderNumber"/>
        <result column="total_amount" property="totalAmount"/>
    </resultMap>

    <select id="findAllOrders" resultMap="OrderResultMap">
        SELECT * FROM orders
    </select>

    <insert id="insertOrder">
        INSERT INTO orders (order_number, total_amount) 
        VALUES (#{orderNumber}, #{totalAmount})
    </insert>

</mapper>
1.5 Service层与Controller层

在Service层,我们可以通过依赖注入的方式调用OrderMapper进行业务逻辑处理:

package com.example.ecommerce.service;

import com.example.ecommerce.mapper.OrderMapper;
import com.example.ecommerce.model.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    public List<Order> getAllOrders() {
        return orderMapper.findAllOrders();
    }

    public void createOrder(Order order) {
        orderMapper.insertOrder(order);
    }
}

在控制层,我们定义一个OrderController来处理HTTP请求:

package com.example.ecommerce.controller;

import com.example.ecommerce.model.Order;
import com.example.ecommerce.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/orders")
public class OrderController {

    @Autowired
    private OrderService orderService;

    @GetMapping
    public List<Order> getAllOrders() {
        return orderService.getAllOrders();
    }

    @PostMapping
    public void createOrder(@RequestBody Order order) {
        orderService.createOrder(order);
    }
}
第2章 实现主从复制和读写分离
2.1 主从复制概述

主从复制是一种常见的数据库架构,其中一个数据库实例作为主库,负责处理所有的写操作;而一个或多个从库负责复制主库的数据,并处理读操作。这种架构的主要优点在于可以分担数据库的读写压力,提升系统的性能和可靠性。

2.2 主从复制的配置

首先,我们需要在MySQL中配置主从复制。假设我们有两台MySQL服务器,分别作为主库和从库:

  • 主库(Master):IP地址为192.168.0.1
  • 从库(Slave):IP地址为192.168.0.2

在主库上执行以下步骤:

  1. 修改my.cnf配置文件:
[mysqld]
log-bin=mysql-bin
server-id=1
  1. 重启MySQL服务,并创建一个用于复制的用户:
CREATE USER 'replication'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO 'replication'@'%';
FLUSH PRIVILEGES;
  1. 查看主库状态,获取二进制日志文件名和位置:
SHOW MASTER STATUS;

在从库上执行以下步骤:

  1. 修改my.cnf配置文件:
[mysqld]
server-id=2
  1. 重启MySQL服务,并配置复制:
CHANGE MASTER TO 
MASTER_HOST='192.168.0.1', 
MASTER_USER='replication', 
MASTER_PASSWORD='password', 
MASTER_LOG_FILE='mysql-bin.000001', 
MASTER_LOG_POS=120;
START SLAVE;
  1. 查看从库状态,确保复制正常进行:
SHOW SLAVE STATUS\G;
2.3 在Spring Boot中实现读写分离

在Spring Boot中实现读写分离,可以通过自定义DataSource并基于AbstractRoutingDataSource类进行动态数据源路由。以下是一个示例配置:

package com.example.ecommerce.config;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

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

@Configuration
public class DataSourceConfig {

    @Bean(name = "masterDataSource")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create()
            .url("jdbc:mysql://192.168.0.1:3306/ecommerce?useSSL=false&serverTimezone=UTC")
            .username("root")
            .password("yourpassword")
            .build();
    }

    @Bean(name = "slaveDataSource")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create()
            .url("jdbc:mysql://192.168.0.2:3306/ecommerce?useSSL=false&serverTimezone=UTC")
            .username("root")
            .password("yourpassword")
            .build();
    }

        @Bean
    public DataSource routingDataSource(
            @Qualifier("masterDataSource") DataSource masterDataSource,
            @Qualifier("slaveDataSource") DataSource slaveDataSource) {

        AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource() {
            @Override
            protected Object determineCurrentLookupKey() {
                // 这里可以根据实际情况选择数据源
                return DataSourceContextHolder.getDataSourceType();
            }
        };

        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put(DataSourceType.MASTER, masterDataSource);
        dataSourceMap.put(DataSourceType.SLAVE, slaveDataSource);

        routingDataSource.setTargetDataSources(dataSourceMap);
        routingDataSource.setDefaultTargetDataSource(masterDataSource);

        return routingDataSource;
    }
}

在上述配置中,routingDataSource是一个路由数据源,能够根据当前的上下文选择使用主库或从库。DataSourceContextHolder用于存储和切换当前线程使用的数据源类型。

2.4 创建数据源上下文管理类

为了实现读写分离,我们需要创建一个管理类DataSourceContextHolder,用于动态切换数据源:

package com.example.ecommerce.config;

public class DataSourceContextHolder {

    private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<>();

    public static void setDataSourceType(DataSourceType dataSourceType) {
        contextHolder.set(dataSourceType);
    }

    public static DataSourceType getDataSourceType() {
        return contextHolder.get();
    }

    public static void clearDataSourceType() {
        contextHolder.remove();
    }
}

这里的DataSourceType是一个枚举类,用于区分主库和从库:

package com.example.ecommerce.config;

public enum DataSourceType {
    MASTER, SLAVE;
}
2.5 创建数据源切换的AOP切面

为了自动切换数据源,可以创建一个AOP切面,通过注解来控制具体的读写操作应该使用的数据库:

package com.example.ecommerce.aspect;

import com.example.ecommerce.config.DataSourceContextHolder;
import com.example.ecommerce.config.DataSourceType;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class DataSourceAspect {

    @Before("@annotation(com.example.ecommerce.annotation.Master)")
    public void setMasterDataSource() {
        DataSourceContextHolder.setDataSourceType(DataSourceType.MASTER);
    }

    @Before("@annotation(com.example.ecommerce.annotation.Slave)")
    public void setSlaveDataSource() {
        DataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE);
    }
}

在这个切面类中,我们通过@Before注解拦截带有@Master@Slave注解的方法,分别将数据源设置为主库或从库。

2.6 定义自定义注解

接下来,我们需要定义用于标记读写操作的方法的自定义注解@Master@Slave

package com.example.ecommerce.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Master {
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Slave {
}

通过以上配置,我们已经完成了基于MyBatis和Spring Boot的主从复制读写分离的实现。

第3章 主从复制与读写分离的优势与劣势
3.1 优势
  1. 性能提升:通过将读写操作分离到不同的数据库实例中,可以显著提高数据库的并发处理能力和响应速度,特别是在读操作占多数的场景下,从库可以分担大量的读取请求,从而减轻主库的压力。
  2. 数据冗余:主从复制的从库不仅可以用于读操作,还可以作为主库的备份,一旦主库发生故障,从库可以迅速提升为主库,从而提高系统的可用性。
  3. 扩展性强:通过增加从库的数量,可以轻松地扩展系统的读取能力,而不需要对应用程序进行大的调整,具有很好的水平扩展性。
3.2 劣势
  1. 数据一致性:由于主从复制过程中存在延迟,从库中的数据并不是实时更新的,这可能导致读取到的不是最新的数据。因此,系统设计时需要考虑数据一致性的问题。
  2. 系统复杂度增加:实现主从复制和读写分离需要配置多个数据源和路由逻辑,增加了系统的复杂性,维护和监控工作也相对较为繁琐。
  3. 开发复杂度增加:在开发过程中,开发者需要明确哪些操作是读操作,哪些是写操作,并且要使用相应的注解或逻辑来保证操作的正确性,增加了开发的复杂度。
第4章 代码示例与完整案例

在本章中,我们将结合前面的配置和代码示例,展示一个完整的电商交易系统中如何使用Spring Boot和MyBatis实现主从复制和读写分离。

4.1 配置文件

首先,回顾一下application.properties中的配置:

# 主库数据源配置
spring.datasource.master.url=jdbc:mysql://192.168.0.1:3306/ecommerce?useSSL=false&serverTimezone=UTC
spring.datasource.master.username=root
spring.datasource.master.password=yourpassword
spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver

# 从库数据源配置
spring.datasource.slave.url=jdbc:mysql://192.168.0.2:3306/ecommerce?useSSL=false&serverTimezone=UTC
spring.datasource.slave.username=root
spring.datasource.slave.password=yourpassword
spring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver

# MyBatis配置
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.example.ecommerce.model
4.2 服务层代码

在服务层,我们使用自定义注解@Master@Slave来标识数据库操作的类型:

package com.example.ecommerce.service;

import com.example.ecommerce.annotation.Master;
import com.example.ecommerce.annotation.Slave;
import com.example.ecommerce.mapper.OrderMapper;
import com.example.ecommerce.model.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Slave
    public List<Order> getAllOrders() {
        return orderMapper.findAllOrders();
    }

    @Master
    public void createOrder(Order order) {
        orderMapper.insertOrder(order);
    }
}

在这个示例中,getAllOrders方法使用了从库进行读取操作,而createOrder方法则使用主库进行写入操作。

4.3 测试与验证

为了验证读写分离的效果,可以在主库和从库上分别观察数据的变化情况。通常,我们可以使用以下步骤进行验证:

  1. 在系统中创建一个新的订单,通过调用createOrder方法。
  2. 观察主库,确保数据已经成功写入。
  3. 调用getAllOrders方法,通过从库读取数据,并确认从库中的数据与主库一致。
第5章 结论

通过本文的详细介绍,我们已经学会了如何在Spring Boot项目中结合MyBatis来调用MySQL,并实现主从复制和读写分离。虽然实现读写分离的过程增加了系统的复杂性,但对于提高系统性能和可扩展性有着显著的优势。在实际应用中,开发者需要根据具体的业务场景来权衡数据一致性和系统性能之间的关系,选择合适的架构方案。

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐