Java实战系列(1):SpringBoot+ShardingSphere实现多数据源切换

2023-11-07

主要组件版本信息:

SpringBoot:2.2.8.RELEASE

MyBatis Plus:3.3.2

ShardingSphere:4.0.0-RC2

需求说明

在企业开发中,如果业务数据分布在不同的数据源,那么我们就希望在访问业务数据的时候,能够根据业务需求,动态地切换数据源,ShardingSphere是一款不错的数据库中间件,利用它,可以很方便地实现我们想要的功能,下面,我们从零开始介绍,项目搭建及多数据源切换实现。

技术选型

Java 8 + MySql 5.7+ SpringBoot + Lombok + Mybatis Plus + ShardingSphere

开发工具:IntelliJ IDEA + Navicat

SpringBoot项目搭建

打开IDEA,新建一个SpringBoot 项目,如下图示:

创建项目

创建项目2填写项目元数据
填写完项目元数据,点击Next继续下一步,
引入组件,确定版本号指定项目名和路径由以上步骤可以看到,用IDEA搭建SpringBoots项目非常方便。

项目创建完成后,我们来看下整体目录结构,如下图示:
目录结构

我们调整下pom.xml,改成如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.dgd</groupId>
    <artifactId>multi-datasource</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <name>multi-datasource</name>
    <description>多数据源切换</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <springboot.version>2.2.8.RELEASE</springboot.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>${springboot.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${springboot.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <version>${springboot.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

spring-boot-starter是SpringBoot项目的核心,必须要引入;spring-boot-starter-web提供了web相关功能,而spring-boot-starter-test是SpringBoot的测试组件,后续我们写单元测试会用到它。

下面我们来写个HelloWorld接口,验证一下项目搭建是否没问题。

代码如下:

package com.dgd.multidatasource.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author : DaiGD
 * @createtime :  2020年06月13日 14:17
 * @description : HelloWorld 控制器
 */
@RestController
public class HelloWorldController
{
    @GetMapping("/hello/{userName}")
    public String helloWorld(@PathVariable String userName)
    {
        return "Hello:" + userName;
    }
}

新建包并取命为:com.dgd.multidatasource.controller;新建类并取名为:HelloWorldController,在类上添加注解@RestController,该注解将帮助我们创建REST风格的web服务,具体讲解参看此;写一个方法名为:helloWorld,方法上添加注解GetMapping,表明该方法只接收GET请求,入参上添加注解@PathVariable,它将帮我们读取到请求路径上定义的userName参数。此时我们的项目如下图示:

Hello World

接下来我们把项目启动,回到MultiDatasourceApplication类,点击绿色小图标,选择Run选项,启动项目,如图示:
启动项目1
启动项目2

看到控制台输出如下日志,表明项目启动没问题:

启动项目3

接着,我们在浏览器地址栏上输入http://localhost:8080/hello/Dannis,看到网页上出现Hello:Dannis,表明SpringBoot项目成功搭建完成。

数据初始化

现在我们来创建两个数据源,真实场景的多数据源,数据库所在的服务器一般是不相同的,如果是为了模拟真实环境,我们可以在自己电脑上搭建两个虚拟机,分别搭建数据库,或者利用Docker来创建两个数据库,或者买两个云服务器,分别在上面搭建两个数据库,为了简单起见,也可以是在同一个MySql服务上创建两个不同的库,我们就按最后一种情况来,假设已在本地上安装好MySql服务环境,接下来,我们用下面的脚本命令来初始化我们的测试数据:

# 创建第一个数据源
DROP DATABASE IF EXISTS `ds_01`;
CREATE DATABASE `ds_01`;

# 创建用户表并初始化数据
DROP TABLE IF EXISTS `ds_01`.`user`;
CREATE TABLE `ds_01`.`user` (
`id` BIGINT(11) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
`user_name` VARCHAR(16) NOT NULL COMMENT '用户名'
);
INSERT INTO `ds_01`.`user` (`user_name`) VALUES
('Dannis'),
('小飞飞');

# 创建第二个数据源
DROP DATABASE IF EXISTS `ds_02`;
CREATE DATABASE `ds_02`;

# 创建订单表并初始化数据
DROP TABLE IF EXISTS `ds_02`.`order`;
CREATE TABLE `ds_02`.`order` (
`id` BIGINT(11) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '订单ID',
`user_id` BIGINT(11) NOT NULL COMMENT '用户ID',
`address` VARCHAR(32) NOT NULL COMMENT '收货地址'
);
INSERT INTO `ds_02`.`order` (`user_id`,`address`) VALUES
(1,'北京市朝阳区'),
(2,'广州市海珠区');

SQL脚本执行完毕,点击localhost鼠标右键选择刷新,然后可看到出现两个数据库ds_01ds_02,打开查看一下,发现数据已正常写入,如下图所示:

初始化数据

利用Mybatis Plus来访问数据

Mybatis Plus是ORM框架MyBatis的增强版,具体介绍可查看官网

这里我们选用它来简化对数据库的操作,同时,我们也引入Lombok插件来简化Java对象相关方法的编码(IDEA需提前安装好Lombok插件并添加相关配置,具体步骤可自行百度),在pom.xml添加如下代码:

配置版本号:

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <springboot.version>2.2.8.RELEASE</springboot.version>
        <lombok.version>1.18.4</lombok.version>
        <mysql-connector-java.version>5.1.42</mysql-connector-java.version>
        <mybatis-plus.version>3.3.2</mybatis-plus.version>
    </properties>

引入依赖:

  <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql-connector-java.version}</version>
        </dependency>
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <!-- lombok 依赖 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>

新增包并命名为com.dgd.multidatasource.model.mybatis.entity,

新建User类,代码如下:

package com.dgd.multidatasource.model.mybatis.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;

/**
 * @author : DaiGD
 * @createtime :  2020年06月13日 15:33
 * @description : 用户表
 */
@Data
@TableName("`user`")
public class User implements Serializable
{
    private Long id;
    
    private String userName;
}

新建Order类,代码如下:

package com.dgd.multidatasource.model.mybatis.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;

/**
 * @author : DaiGD
 * @createtime :  2020年06月13日 15:35
 * @description : 订单表
 */
@Data
@TableName("`order`")
public class Order implements Serializable
{
    private Long id;

    private Long userId;

    private String address;
}

新增包并命名为com.dgd.multidatasource.model.mybatis.mapper,

新建UserMapper类,代码如下:

package com.dgd.multidatasource.model.mybatis.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dgd.multidatasource.model.mybatis.entity.User;
import org.apache.ibatis.annotations.Mapper;

/**
 * @author : DaiGD
 * @createtime :  2020年06月13日 15:42
 * @description : 用户表映射接口
 */
@Mapper
public interface UserMapper extends BaseMapper<User>
{
}

新建OrderMapper类,代码如下:

package com.dgd.multidatasource.model.mybatis.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dgd.multidatasource.model.mybatis.entity.Order;
import org.apache.ibatis.annotations.Mapper;

/**
 * @author : DaiGD
 * @createtime :  2020年06月13日 15:43
 * @description : 订单表映射接口
 */
@Mapper
public interface OrderMapper extends BaseMapper<Order>
{
}

在配置类application.yml上添加如下配置:

# DataSource Config
spring:
  datasource:
    # 指定驱动类
    driver-class-name: com.mysql.jdbc.Driver
    # 数据库地址
    url: jdbc:mysql://localhost:3306/ds_01?serverTimezone=Asia/Shanghai&useSSL=false
    # 数据库用户名
    username: root
    # 数据库用户密码
    password: root

MultiDatasourceApplication类上指定Mapper扫描路径,如下:

@MapperScan("com.dgd.multidatasource.model.mybatis.mapper")

写个单元测试来验证下MyBatis Plus是否能正常访问ds_01上的数据,代码如下:

package com.dgd.multidatasource.model.mybatis;

import com.dgd.multidatasource.model.mybatis.entity.User;
import com.dgd.multidatasource.model.mybatis.mapper.UserMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

/**
 * @author : DaiGD
 * @createtime :  2020年06月13日 15:39
 * @description : MybatisPlus 功能测试
 */
@SpringBootTest
public class MybatisPlusTest
{
    @Autowired
    UserMapper userMapper;
    @Test
    void userTest()
    {
        User user = userMapper.selectById(2L);
        Assertions.assertNotNull(user);
        Assertions.assertEquals("小飞飞", user.getUserName(), "用户名不正确");
        System.out.println("查询结果:" + user);
    }
}

运行测试用例:
测试用例
控制台输出如下结果,表明Mybatis Plus已能正常使用。
测试用例成功通过

利用ShardingSphere实现多数据源切换

上面我们通过Mybatis Plus已能正常访问ds_01上的数据,但是如果想要同时访问ds_02上的订单数据,就要借助ShardingSphere中间件了,下面来引入相关依赖,如下:

指定版本号:

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <springboot.version>2.2.8.RELEASE</springboot.version>
        <lombok.version>1.18.4</lombok.version>
        <mysql-connector-java.version>5.1.42</mysql-connector-java.version>
        <mybatis-plus.version>3.3.2</mybatis-plus.version>
        <sharding-sphere.version>4.0.0-RC2</sharding-sphere.version>
   </properties>

引入依赖:

 <!-- shardingSphere 依赖 -->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>${sharding-sphere.version}</version>
        </dependency>

接着我们把application.yml文件里内容改成如下所示:

spring:
    shardingsphere:
        props:
            sql:
                show:
                    true
        datasource:
            names: ds1,ds2
            ds1:
                type: com.zaxxer.hikari.HikariDataSource
                driverClassName: com.mysql.jdbc.Driver
                jdbc-url: jdbc:mysql://localhost:3306/ds_01?serverTimezone=Asia/Shanghai&useSSL=false
                username: root
                password: root
            ds2:
                type: com.zaxxer.hikari.HikariDataSource
                driverClassName: com.mysql.jdbc.Driver
                jdbc-url: jdbc:mysql://localhost:3306/ds_02?serverTimezone=Asia/Shanghai&useSSL=false
                username: root
                password: root
        sharding:
            defaultDatabaseStrategy:
                hint:
                    algorithmClassName: com.dgd.multidatasource.shardingsphere.MyDatasourceRoutingAlgorithm
            tables:
                user:
                    actualDataNodes: ds1.user
                order:
                    actualDataNodes: ds2.order
            defaultTableStrategy:
                none:
                    any: ""      
        

我们对上面用到的参数做下说明:
spring:shardingsphere:props:sql:show:是否开启SQL显示,默认是false,开发过程我们把它设成true以方便查看SQL执行过程。
spring:shardingsphere:datasource:names:指定数据源名字,多个数据源之间以逗号分隔,下面就是对声明的数据源ds1ds2进行相关属性配置,不再赘述。
spring:shardingsphere:sharding:defaultDatabaseStrategy:hint:algorithmClassName:声明默认数据库分片策略使用Hint策略,指定Hint分片算法类名称,该类需实现HintShardingAlgorithm接口并提供无参数的构造器。
spring:shardingsphere:sharding:tables:数据分片规则配置,userorder是我们声明的逻辑表名称,actualDataNodes指定实际的数据节点,由数据源名 + 逻辑表名组成,以小数点分隔。
spring:shardingsphere:sharding:defaultTableStrategy:none:因为我们只是用到分库功能,并不需要进行分表,因此,指定默认的分表策略为noneany是我们给该策略取的名字,可以为任意字符串,其值为空。

更多参数配置项说明可参看官网

从上面的配置内容可知,除了要配置数据源外,还有配置分片策略,由于我们希望的是想让它访问哪个数据源就访问哪个数据源,即强制路由,而ShardingSphereHint分片策略正好可以满足我们的这个需求。

以下关于Hint的简单介绍摘自官网

ShardingSphere使用ThreadLocal管理分片键值进行Hint强制路由。可以通过编程的方式向HintManager中添加分片值,该分片值仅在当前线程内生效。

Hint方式主要使用场景:

  • 分片字段不存在SQL中、数据库表结构中,而存在于外部业务逻辑。
  • 强制在主库进行某些数据操作。

更多分片策略可参考ShardingSphere官网

下面我们来开始写分片策略的实现类,首先定义两个数据源常量,如下:

package com.dgd.multidatasource.shardingsphere;

/**
 * @author : DaiGD
 * @createtime :  2020年06月13日 16:46
 * @description : 数据源枚举
 */
public enum DatasourceType
{
    /**
     * 用户数据源
     */
    DATASOURCE_USER,
    /**
     * 订单数据源
     */
    DATASOURCE_ORDER
}

数据库分片策略代码实现:

package com.dgd.multidatasource.shardingsphere;

import org.apache.shardingsphere.api.sharding.hint.HintShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.hint.HintShardingValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.HashSet;

/**
 * @author : DaiGD
 * @createtime :  2020年06月13日 16:42
 * @description : 数据库分片策略
 */
public class MyDatasourceRoutingAlgorithm implements HintShardingAlgorithm<String>
{
    private static final Logger LOGGER = LoggerFactory.getLogger(MyDatasourceRoutingAlgorithm.class);

    /**
     * 用户数据源
     */
    private static final String DS_USER = "ds1";

    /**
     * 订单数据源
     */
    private static final String DS_ORDER = "ds2";

    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, HintShardingValue<String> shardingValue)
    {
        Collection<String> result = new HashSet<>();
        for(String value : shardingValue.getValues())
        {
            if(DatasourceType.DATASOURCE_USER.toString().equals(value))
            {
                if(availableTargetNames.contains(DS_USER))
                {
                    result.add(DS_USER);
                }
            }
            else
            {
                if(availableTargetNames.contains(DS_ORDER))
                {
                    result.add(DS_ORDER);
                }
            }
        }
        LOGGER.info("availableTargetNames:{},shardingValue:{},返回的数据源:{}",
                new Object[] { availableTargetNames, shardingValue, result });

        return result;
    }
}

好了,写个测试用例测试一下,新建包名为com.dgd.multidatasource.shardingsphere,测试类名为DatasourceRoutingTest,具体测试代码如下:

package com.dgd.multidatasource.shardingsphere;

import com.dgd.multidatasource.model.mybatis.entity.Order;
import com.dgd.multidatasource.model.mybatis.entity.User;
import com.dgd.multidatasource.model.mybatis.mapper.OrderMapper;
import com.dgd.multidatasource.model.mybatis.mapper.UserMapper;
import org.apache.shardingsphere.api.hint.HintManager;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

/**
 * @author : DaiGD
 * @createtime :  2020年06月13日 17:05
 * @description : 数据源切换功能验证
 */
@SpringBootTest
public class DatasourceRoutingTest
{
    @Autowired
    UserMapper userMapper;
    
    @Autowired
    OrderMapper orderMapper;

    @Test
    void test()
    {
        HintManager hintManager = HintManager.getInstance();
        // 分库不分表情况下,强制路由至某一个分库时,可使用hintManager.setDatabaseShardingValue方式添加分片
        // 通过此方式添加分片键值后,将跳过SQL解析和改写阶段,从而提高整体执行效率。
        // 详情参考:
        // https://shardingsphere.apache.org/document/legacy/4.x/document/cn/manual/sharding-jdbc/usage/hint/
        hintManager.setDatabaseShardingValue(DatasourceType.DATASOURCE_USER.toString());
        // 访问用户数据源
        User user = userMapper.selectById(2L);
        Assertions.assertNotNull(user);
        Assertions.assertEquals("小飞飞", user.getUserName(), "用户名不正确");
        System.out.println("用户查询结果:" + user);
        hintManager.close();

        hintManager.setDatabaseShardingValue(DatasourceType.DATASOURCE_ORDER.toString());
        // 访问订单数据源
        Order order = orderMapper.selectById(1L);
        Assertions.assertNotNull(order);
        Assertions.assertEquals("北京市朝阳区", order.getAddress(), "地址不正确");
        System.out.println("订单查询结果:" + order);
        hintManager.close();
    }
}

测试结果显示如下图所示,说明数据源已能成功切换:

数据源切换成功测试用例

最后,为了能在web端访问我们的项目,加上Controller等相关代码,具体代码如下:

创建com.dgd.multidatasource.service包,新建两个类,分别为UserServiceOrderService,代码分别为:

package com.dgd.multidatasource.service;

import com.dgd.multidatasource.model.mybatis.entity.User;
import com.dgd.multidatasource.model.mybatis.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author : DaiGD
 * @createtime :  2020年06月13日 19:14
 * @description : 用户服务方法
 */
@Service
public class UserService
{
    @Autowired
    private UserMapper userMapper;

    public User queryById(long id)
    {
        return userMapper.selectById(id);
    }
}
package com.dgd.multidatasource.service;

import com.dgd.multidatasource.model.mybatis.entity.Order;
import com.dgd.multidatasource.model.mybatis.mapper.OrderMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author : DaiGD
 * @createtime :  2020年06月13日 19:15
 * @description : 订单服务方法
 */
@Service
public class OrderService
{
    @Autowired
    private OrderMapper orderMapper;

    public Order queryById(long id)
    {
        return orderMapper.selectById(id);
    }
}

在原来的controller包下添加一个类,名为BusinessController,代码如下:

package com.dgd.multidatasource.controller;

import com.dgd.multidatasource.model.mybatis.entity.Order;
import com.dgd.multidatasource.model.mybatis.entity.User;
import com.dgd.multidatasource.service.OrderService;
import com.dgd.multidatasource.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author : DaiGD
 * @createtime :  2020年06月13日 19:17
 * @description : 业务功能控制器
 */
@RestController
public class BusinessController
{
    @Autowired
    private UserService userService;

    @Autowired
    private OrderService orderService;

    @GetMapping("/user/{id}")
    public User queryByUserId(@PathVariable Long id)
    {
        return userService.queryById(id);
    }

    @GetMapping("/order/{id}")
    public Order queryByOrderId(@PathVariable Long id)
    {
        return orderService.queryById(id);
    }
}

之后启动项目,在浏览上分别输入:http://localhost:8080/user/1http://localhost:8080/order/2,可以看到浏览器分别响应:

{"id":1,"userName":"Dannis"}
{"id":2,"userId":2,"address":"广州市海珠区"}

说明数据源切换在web层也正常。

防坑记录

  • 对于分表策略,如果声明类型为none,如果不指定指定策略的名称和值,如下所示:
    分表策略未指定启动测试用例会提示如下异常:
    分表异常解决方法:
    any:""的注释去掉即可。
    参考

  • 因为我们的订单表名声明为了order,如果在Order类上的@TableName直接写成如下所示(注意,order没有加上反引号):

@Data
@TableName("order")
public class Order implements Serializable
{
    private Long id;

    private Long userId;

    private String address;
}

启动测试用例会提示如下异常:
表声明异常显然,SQL语句解析时出现了错误,它把我们的order当成了MySql内置关键字了,加上反引号区分开来即可,如下:

@Data
@TableName("`order`")
public class Order implements Serializable
{
    private Long id;

    private Long userId;

    private String address;
}

项目完整代码地址

项目完整代码:
码云GitHub

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java实战系列(1):SpringBoot+ShardingSphere实现多数据源切换 的相关文章

随机推荐

  • FTP暴力破解登陆漏洞复现(Windows Server 2003)

    FTP暴力破解漏洞复现 0x01 漏洞简述 FTP弱口令 一般指使用FTP的用户的密码长度太短 复杂度不够 仅包含数字 或仅包含字母等 容易被黑客攻击 发生恶意文件上传或更严重的入侵行为 知道FTP弱口令 可以通过该漏洞获取主机文件系统信息
  • 规则引擎相关开源项目总结

    表达式引擎 Java Groovy https groovy lang org JEXL https commons apache org proper commons jexl mvel https github com mvel mve
  • 【华为OD机试】停车场车辆统计【2023 B卷

    华为OD机试 真题 点这里 华为OD机试 真题考点分类 点这里 题目描述 特定大小的停车场 数组cars 表示 其中1表示有车 0表示没车 车辆大小不一 小车占一个车位 长度1 货车占两个车位 长度2 卡车占三个车位 长度3 统计停车场最少
  • Windows下安装CUDA并配置cuDNN教程

    Windows下安装CUDA并配置cuDNN教程 一 查看电脑的显卡驱动版本 1 键盘点击Windows R或者在搜索栏输入cmd 进入命令提示符 2 在命令提示符下 输入nvidia smi 查看显卡驱动版本 本电脑显卡驱动版本为 456
  • React中引入百度地图API 避坑

    BMap is not defined 原因 React脚手架中全局变量都要用window去访问 解决 BMap改成window BMap访问 或者将BMap从window中解构出来 BMap前加了window 页面还是一片空白 原因 没有
  • QT中文输入法状态下获取键盘输入事件

    QT在中文输入法下 获取键盘的输入消息时 始终获取不到 但是在英文输入状态下是可以获取的 具体看代码 bool mytest eventFilter QObject obj QEvent ev if QEvent KeyPress ev g
  • 诚之和:自嗨锅能颠覆海底捞吗?

    为了避免独自外出就食的尴尬 实现独乐乐的满足感 催生了 一人食经济 2015年 自热火锅横空出世 一经面世就迅速在自热方便类食品中占据一席之地 特别受到年轻人的青睐 九成以上的消费者年龄在40岁以下 自热火锅以其新潮 便利等优势备受消费者欢
  • python实现螺旋矩阵

    import numpy 使用递归解决 def helixMatrix matrix x cur y cur number n if n 0 print matrix return 0 if n 1 matrix x cur y cur n
  • Python 程序设计-系统登录页面设计

    Python 程序设计 系统登录页面设计 目录 Python 程序设计 系统登录页面设计 1 需求分析 2 总体设计 3 详细设计 4 程序运行结果测试与分析 5 完整源代码 1 需求分析 系统要有管理员登录 普通用户登录和退出三个选项可供
  • Modbus协议简易入门教程

    背景 介绍modbus rtu之前 我们可以了解到 Modbus是施耐德电气公司 于1979年发明的 是全球第一个真正用于工业现场的总线协议 四十多年过去了 现在仍然被广泛使用在各个工业控制领域 除了这个协议很稳定的原因之外 1 免费 2
  • 【机会约束、鲁棒优化】机会约束和鲁棒优化研究优化【ccDCOPF】研究(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码实现 1 概述 随机规划的三个分支分别为期望值模型 机会约
  • 用一个变量控制线程里面的资源问题,线程可见性问题

    线程资源不可见原因 1 CPU高速缓存造成极短时间内数据的不可见 2 指令重排导致不可见 不可见测试 由上图可知 当我们改变eatFlag为true时 并没有输出我们预想的数据 例如 线程中资源正在运行 解决办法 1 线程休眠 sleep
  • Shell脚本基础

    1 变量 匹配规则 懒惰模式最短匹配 从头开始匹配 最短删除 root liudongyi var1 l love you but you love he root liudongyi var2 var1 ve root liudongyi
  • System Error Codes

    From http msdn microsoft com en us library windows desktop ms681382 28v vs 85 29 aspx Applies to desktop apps only Note
  • 如何开通TikTok广告账户?

    如何开通TikTok广告账户 大家好 我是项柚 一个专注于讨论TikTok玩法的跨境电商自媒体人 每天不断输出干货给需要的朋友 希望大家能少走弯路 TikTok Ads是TikTok广告投放的管理后台 汇集多款海外流量产品 覆盖 150 多
  • 数据库单表数据过亿_最受欢迎的三大数据库,你用过吗?

    随着市场的多元化 需求场景多样化 数据库也层出不穷 来适应不同的业务场景 今天小编就给大家总结一下目前下面来总结下目前最受欢迎的三大数据库 快来看看你有没有用过吧 1 MySQL MySQL是一种关系型数据库管理系统 关系数据库将数据保存在
  • C#和.NET FrameWork概述

    NET FrameWork是什么 NET FrameWork是由微软开发的一种面相对象的环境框架 特点如下 多平台 可在各种计算机 服务器 手机上运行 标准化通讯协议 如XML HTTP JSON等 安全性 CLR检查并确保参数及数据对象的
  • imx6之camera mipi

    1 IPU imx6qp有2个IPU 每个IPU有2个CSI 这个CSI并不是MIPI CSI 是内部的功能模块 static struct mipi csi2 platform data mipi csi2 pdata chans 4 m
  • js计算大额数据当中丢失精度以及消除科学计数法解决方案—decimal.js的使用

    目录 decimal js使用场景 decimal js介绍 decimal js使用 1 安装依赖 2 引入并使用 decimal js函数封装 decimal js使用场景 js有精度问题 对于一些金额的计算就总是与偶莫名其妙的问题 d
  • Java实战系列(1):SpringBoot+ShardingSphere实现多数据源切换

    主要组件版本信息 SpringBoot 2 2 8 RELEASE MyBatis Plus 3 3 2 ShardingSphere 4 0 0 RC2 需求说明 在企业开发中 如果业务数据分布在不同的数据源 那么我们就希望在访问业务数据