SpringBoot与Druid整合,实现电商主从数据库同步系统

通过引入主从数据库同步系统,可以显著提升电商平台的性能和稳定性,同时保证数据的一致性和安全性。Druid连接池也提供了强大的监控和安全防护功能,使得整个系统更加健壮和可靠。

我们为什么选择Druid?

  • 高效的连接管理:Druid 提供了高效的物理连接复用机制,能够快速获取和释放数据库连接,减少连接建立和关闭的开销。

  • 异步初始化:支持异步初始化连接池,提高应用启动速度。

  • 详细的统计信息:Druid 内置了丰富的统计功能,可以收集 SQL 执行情况、慢查询记录等详细数据,帮助我们更好地进行性能调优。

  • 防火墙规则:支持配置防火墙规则,限制哪些 IP 地址可以访问数据库,增强安全性。

  • SQL 防注入:提供 WallFilter 插件,防止 SQL 注入攻击,保护数据库不受恶意 SQL 的影响。

  • 多数据源支持:Druid 支持配置多个数据源,非常适合实现读写分离等高级场景。在我们的项目中,主从数据库的配置正是利用了这一特性。

哪些公司在使用Druid?

  • 阿里巴巴 :Druid 是由阿里巴巴开源的一个项目,最初是为了满足其内部庞大的业务需求而开发。目前用于阿里巴巴旗下的淘宝、天猫等电商平台的数据库连接管理。

  • 京东 : 在京东的电商平台中,Druid 用于处理高并发的读写请求,提升系统性能和稳定性。

  • 美团点评 : 在美团点评的各个业务线中,Druid 用于提高数据库的响应速度和吞吐量,特别是在高并发场景下的表现。

  • 小米 : 在小米的电商平台和其他业务系统中,Druid 用于高效地管理数据库连接,确保系统的稳定性和性能。

  • 去哪儿网 : 在去哪儿网的业务系统中,Druid 用于处理大量的用户请求,提高数据库的并发处理能力。

  • 携程旅行网 : 在携程的电商平台中,Druid 用于管理和优化数据库连接,支持高并发的读写操作。

  • 知乎 : 在知乎的系统中,Druid 用于管理和优化数据库连接,支持高并发的读写操作,确保系统的稳定性和性能。

  • 拼多多 : 在拼多多的系统中,Druid 用于管理和优化数据库连接,支持高并发的读写操作,确保系统的稳定性和性能。

代码实操

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

    <!-- MyBatis Plus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.2</version>
    </dependency>

    <!-- Druid -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.2.8</version>
    </dependency>

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

application.yml

server:
  port:8080

spring:
datasource:
    type:com.alibaba.druid.pool.DruidDataSource
    druid:
      master:
        url:jdbc:mysql://localhost:3306/master_db?useSSL=false&serverTimezone=UTC
        username:root
        password:root
      slave:
        url:jdbc:mysql://localhost:3307/slave_db?useSSL=false&serverTimezone=UTC
        username:root
        password:root

mybatis-plus:
configuration:
    log-impl:org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations:classpath*:mybatis-mapper.xml

动态数据源上下文持有者

package com.example.demo.config;

publicclass DynamicDataSourceContextHolder {
    privatestaticfinal ThreadLocal<String> contextHolder = new ThreadLocal<>();

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

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

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

数据源配置

package com.example.demo.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
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
publicclass DataSourceConfig {

    @Value("${spring.datasource.druid.master.url}")
    private String masterUrl;

    @Value("${spring.datasource.druid.master.username}")
    private String masterUsername;

    @Value("${spring.datasource.druid.master.password}")
    private String masterPassword;

    @Value("${spring.datasource.druid.slave.url}")
    private String slaveUrl;

    @Value("${spring.datasource.druid.slave.username}")
    private String slaveUsername;

    @Value("${spring.datasource.druid.slave.password}")
    private String slavePassword;

    @Bean
    public DataSource dynamicDataSource() {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", masterDataSource());
        targetDataSources.put("slave", slaveDataSource());

        AbstractRoutingDataSource abstractRoutingDataSource = new AbstractRoutingDataSource() {
            @Override
            protected Object determineCurrentLookupKey() {
                return DynamicDataSourceContextHolder.getDataSourceType();
            }
        };
        abstractRoutingDataSource.setDefaultTargetDataSource(masterDataSource());
        abstractRoutingDataSource.setTargetDataSources(targetDataSources);

        return abstractRoutingDataSource;
    }

    @Bean
    public DataSource masterDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(masterUrl);
        dataSource.setUsername(masterUsername);
        dataSource.setPassword(masterPassword);
        return dataSource;
    }

    @Bean
    public DataSource slaveDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(slaveUrl);
        dataSource.setUsername(slaveUsername);
        dataSource.setPassword(slavePassword);
        return dataSource;
    }
}

实体类

package com.example.demo.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

@TableName("product")
publicclass Product {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Double price;

    // getters and setters
}

Mapper接口

package com.example.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.Product;

public interface ProductMapper extends BaseMapper<Product> {
}

mybatis-mapper.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.demo.mapper.ProductMapper">

    <select id="selectById" resultType="com.example.demo.entity.Product">
        SELECT * FROM product WHERE id = #{id}
    </select>

    <insert id="insert" parameterType="com.example.demo.entity.Product">
        INSERT INTO product (name, price) VALUES (#{name}, #{price})
    </insert>

</mapper>

Service接口

package com.example.demo.service;

import com.example.demo.entity.Product;

public interface ProductService {
    Product getProductById(Long id);
    boolean saveProduct(Product product);
}

Service

package com.example.demo.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.config.DynamicDataSourceContextHolder;
import com.example.demo.entity.Product;
import com.example.demo.mapper.ProductMapper;
import com.example.demo.service.ProductService;
import org.springframework.stereotype.Service;

@Service
publicclass ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {

    @Override
    public Product getProductById(Long id) {
        DynamicDataSourceContextHolder.setDataSourceType("slave");
        try {
            return getById(id);
        } finally {
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }

    @Override
    public boolean saveProduct(Product product) {
        DynamicDataSourceContextHolder.setDataSourceType("master");
        try {
            return save(product);
        } finally {
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }
}

Controller

package com.example.demo.controller;

import com.example.demo.entity.Product;
import com.example.demo.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/products")
publicclass ProductController {

    @Autowired
    private ProductService productService;

    @GetMapping("/{id}")
    public Product getProduct(@PathVariable Long id) {
        return productService.getProductById(id);
    }

    @PostMapping("/")
    public boolean addProduct(@RequestBody Product product) {
        return productService.saveProduct(product);
    }
}

Application

package com.example.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.example.demo.mapper")
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

测试

添加产品

curl -X POST http://localhost:8080/products/ \
     -H "Content-Type: application/json" \
     -d '{"name": "Sample Product", "price": 99.99}'

Respons

true

获取产品

curl -X GET http://localhost:8080/products/1

Respons

{"id":1,"name":"Sample Product","price":99.99}

关注我,送Java福利

/**
 * 这段代码只有Java开发者才能看得懂!
 * 关注我微信公众号之后,
 * 发送:"666",
 * 即可获得一本由Java大神一手面试经验诚意出品
 * 《Java开发者面试百宝书》Pdf电子书
 * 福利截止日期为2025年02月28日止
 * 手快有手慢没!!!
*/
System.out.println("请关注我的微信公众号:");
System.out.println("Java知识日历");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值