【存储中间件API】MySQL、Redis、MongoDB、ES常见api操作及性能比较

常见中间件api操作及性能比较

  • ☝️ MySQL crud操作
    • ✌️ maven依赖
    • ✌️ 配置
    • ✌️ 定义实体类
    • ✌️ 常用api
  • ☝️ Redis crud操作
    • ✌️ maven依赖
    • ✌️ 配置
    • ✌️ 常用api
  • ☝️ MongoDB crud操作
    • ✌️ maven依赖
    • ✌️ 配置文件
    • ✌️ 定义实体类
    • ✌️ MongoDB常用api
  • ☝️ ES crud操作 ⭐️⭐️⭐️
    • ✌️ 前期准备
    • ✌️ maven依赖
      • ⭐️ tips
    • ✌️ 配置文件
    • ✌️ 定义实体类
    • ✌️ ES常用api
  • ☝️ 性能比较
    • ✌️ 模拟创建数据接口
    • ✌️ 查询数据接口

本文汇总常见中间件的api操作及性能对比,主要涉及MySQL、Redis、Mongo、Es,这篇文章默认已经安装配置好相应的中间件

关于MongoDB的安装配置可参考文章:《【MongoDB】一问带你深入理解什么是MongDB,MongoDB超超详细保姆级教程》

Es安装配置可参考:《【ELK】window下ELK的安装与部署》

☝️ MySQL crud操作

mysql是目前最常用的关系型数据库,网上有很多资料,这里大致简单过一下主流的Mybatis-Plus用法,不展开细说

✌️ maven依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus</artifactId>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>

<!--添加 Alibaba 数据源-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.20</version>
</dependency>
<!--访问mysql-->
<!--JDBC-->
<!-- MySql 5.5 Connector -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.24</version>
</dependency>

✌️ 配置

ip、port、数据库名称,账户密码换成自己的

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/csdn?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimeZone=GMT+8
    username: root
    password: 123456

✌️ 定义实体类

实体类中,@Data、@EqualsAndHashCode、@Accessorslombok注解,@Data自动生成实体类的Getter、Setter、无参构造、有参构造等,@EqualsAndHashCode生成自动生成 equalshashCode 方法,@Accessors主要作用是支持链式调用,@TableName是MP注解,用于映射表名

@Data
@EqualsAndHashCode
@Accessors(chain = true)
@TableName("t_store_product")
public class StoreProduct implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    private String image;
    private String sliderImage;
    private String storeName;
    private String storeInfo;
    private String keyword;
    private String cateId;
    private String unitName;
    private Integer sort;
    private Boolean isHot;
    private Boolean isBenefit;
    private Boolean isBest;
    private Boolean isNew;
    private Boolean isGood;
    private Integer giveIntegral;
    private Boolean isSub;
    private Integer ficti;
    private Integer tempId;
    private Boolean specType;
    private String activity;
    private String attr;
    private String attrValue;
    private String content;
    private String couponIds;
    private String flatPattern;
}

✌️ 常用api

见文件TestMySQL.java

@Slf4j
@SpringBootTest
public class TestMySQL {

    @Resource
    private StoreProductMapper storeProductMapper;

    /**
     * @param num 生成num条模拟数据
     * @return
     */
    private static List<StoreProduct> getStoreProduct(Integer num) {
        List<StoreProduct> result = new ArrayList<>();
        StoreProduct storeProduct = new StoreProduct();
        for (int i = 0; i < num; i++) {
            storeProduct.setId(999 + i).
                    setImage("https://www.baidu.com/img/bd_logo1.png").
                    setSliderImage("https://www.baidu.com/img/bd_logo1.png")
                    .setStoreName("测试商品" + i)
                    .setStoreInfo("测试商品")
                    .setKeyword("测试商品")
                    .setCateId("1")
                    .setUnitName("件")
                    .setSort(1)
                    .setIsHot(true)
                    .setIsBenefit(true)
                    .setIsBest(true)
                    .setIsNew(true)
                    .setIsGood(true)
                    .setGiveIntegral(1)
                    .setIsSub(true)
                    .setFicti(1)
                    .setTempId(1)
                    .setSpecType(true)
                    .setActivity("{\"test\":\"test\"}")
                    .setAttr("{\"test\":\"test\"}")
                    .setAttrValue("{\"test\":\"test\"}")
                    .setContent("{\"test\":\"test\"}")
                    .setCouponIds("{\"test\":\"test\"}")
                    .setFlatPattern("{\"test\":\"test\"}");
            result.add(storeProduct);
        }
        return result;
    }

    /**
     * 插入单条数据
     */
    @Test
    void test_insert() {
        StoreProduct storeProduct = getStoreProduct(1).get(0);
        storeProductMapper.insert(storeProduct);
    }

    /**
     * 按照id删除
     */
    @Test
    void test_deleteById() {
        storeProductMapper.deleteById(999);
    }

    /**
     * 多个条件删除
     */
    @Test
    void test_deleteByMap() {
        Map<String, Object> columnMap = new HashMap<>();
        // 添加多个条件
        columnMap.put("id", 999);
        columnMap.put("store_name", "测试商品");
        columnMap.put("is_hot", true);
        storeProductMapper.deleteByMap(columnMap);
    }

    /**
     * 构建wrapper语句删除
     */
    @Test
    void test_deleteByWrapper() {
        // 创建 QueryWrapper 对象
        QueryWrapper<StoreProduct> queryWrapper = new QueryWrapper<>();
        // 添加删除条件,例如删除 id 为 "999" 的记录,并且storeName 为 "测试商品" 的记录
        queryWrapper.eq("id", 999);
        storeProductMapper.delete(queryWrapper);
    }

    /**
     * 构建wrapper语句删除
     */
    @Test
    void test_deleteByLambdaWrapper() {
        // 创建 LambdaQueryWrapper 对象
        LambdaQueryWrapper<StoreProduct> queryWrapper = new LambdaQueryWrapper<>();
        // 添加删除条件,例如删除 id 为 "999" 的记录,并且storeName 为 "测试商品" 的记录
        queryWrapper.eq(StoreProduct::getId, 999);
        storeProductMapper.delete(queryWrapper);
    }

    /**
     * 批量删除
     */
    @Test
    void test_deleteBatchIds() {
        storeProductMapper.deleteBatchIds(Arrays.asList(999, 1000));
    }

    /**
     * 更新数据
     */
    @Test
    void test_updateById() {
        StoreProduct storeProduct = getStoreProduct(1).get(0);
        storeProduct.setStoreName("商品名字更新啦~");
        storeProductMapper.updateById(storeProduct);
    }

    /**
     * 构建wrapper语句更新
     */
    @Test
    void test_updateByWrapper() {
        // 创建 UpdateWrapper 对象
        UpdateWrapper<StoreProduct> queryWrapper = new UpdateWrapper<>();
        // 添加更新条件,例如更新 id 为 "999"
        queryWrapper.eq("id", 999);
        queryWrapper.set("store_name", "商品名字再次更新啦~");
        storeProductMapper.update(null, queryWrapper);
    }

    /**
     * 构建LambdaWrapper语句更新
     */
    @Test
    void test_updateByLambdaWrapper() {
        // 创建 UpdateWrapper 对象
        LambdaUpdateWrapper<StoreProduct> queryWrapper = new LambdaUpdateWrapper<>();
        // 添加更新条件,例如更新 id 为 "999"
        queryWrapper.eq(StoreProduct::getId, 999);
        queryWrapper.set(StoreProduct::getStoreName, "商品名字再再次更新啦~");
        storeProductMapper.update(null, queryWrapper);
    }

    /**
     * 通过id查找
     */
    @Test
    void test_selectById() {
        StoreProduct storeProduct = storeProductMapper.selectById(999);
        log.info("查询结果:{}", storeProduct);
    }

    /**
     * 通过id集合查找
     */
    @Test
    void test_selectBatchIds() {
        List<StoreProduct> storeProducts = storeProductMapper.selectBatchIds(Arrays.asList(1, 2));
        for (StoreProduct storeProduct : storeProducts) {
            log.info("查询结果:{}", storeProduct);
        }
    }

    /**
     * 通过map查找
     */
    @Test
    void test_selectByMap() {
        Map<String, Object> columnMap = new HashMap<>();
        // 添加多个条件
        columnMap.put("store_info", "测试商品");
        columnMap.put("is_hot", true);
        List<StoreProduct> storeProducts = storeProductMapper.selectByMap(columnMap);
        for (StoreProduct storeProduct : storeProducts) {
            log.info("查询结果:{}", storeProduct);
        }
    }

    /**
     * 根据条件查一个
     *
     * 注意,如果有多个满足条件的数据,代码会报错:One record is expected, but the query result is multiple records
     */
    @Test
    void test_selectOne() {
        QueryWrapper<StoreProduct> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("id", 999);
        StoreProduct storeProduct = storeProductMapper.selectOne(queryWrapper);
        log.info("查询结果:{}", storeProduct);
    }

    /**
     * 按照条件查询count总数
     */
    @Test
    void test_selectCount() {
        QueryWrapper<StoreProduct> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("store_name", "新款智能手机");
        Long count = storeProductMapper.selectCount(queryWrapper);
        log.info("查询结果有:{} 条", count);
    }

    /**
     * 列表查询
     */
    @Test
    void test_selectList() {
        QueryWrapper<StoreProduct> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("store_name", "新款智能手机");
        List<StoreProduct> storeProducts = storeProductMapper.selectList(queryWrapper);
        for (StoreProduct storeProduct : storeProducts) {
            log.info("查询结果:{}", storeProduct);
        }
    }

    /**
     * 查询结果为map
     */
    @Test
    void test_selectMaps() {
        QueryWrapper<StoreProduct> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("store_info", "测试商品");
        List<Map<String, Object>> maps = storeProductMapper.selectMaps(queryWrapper);
        for (Map<String, Object> map : maps) {
            log.info("查询结果:{}", map);
        }
    }

    /**
     * 分页查询
     */
    @Test
    void test_selectPage() {
        // 创建分页对象,指定当前页码和每页记录数
        LambdaQueryWrapper<StoreProduct> lqw = new LambdaQueryWrapper<>();
        lqw.eq(StoreProduct::getStoreName, "新款智能手机");
        Page<StoreProduct> page = new Page<>(1, 10);
        // 调用 selectPage 方法进行分页查询
        IPage<StoreProduct> resultPage = storeProductMapper.selectPage(page, lqw);
        log.info("当前页码:{},每页记录数:{},总页数:{},总记录数:{}", resultPage.getCurrent(), resultPage.getSize(),resultPage.getPages(), resultPage.getTotal());
        for (StoreProduct storeProduct : resultPage.getRecords()) {
            log.info("查询结果:{}", storeProduct);
        }
    }
}

代码中构建模拟数据方法getStoreProduct()中用到了链式构建,Wrapper构建既能用普通Wrapper,也能用LambdaWrapper,例如查询中的QueryWrapper()或者是LambdaQueryWrapper()test_selectOne如果查询存在多个值,会抛出异常One record is expected, but the query result is multiple records,源码还是非常简单,如下:

在这里插入图片描述

☝️ Redis crud操作

Redis是常见的缓存中间件,接下来看看redis的一些常见操作

✌️ maven依赖

<!-- Spring Boot Redis 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.2.0.RELEASE</version>
</dependency>

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.1.0</version>
</dependency>

✌️ 配置

ip、port、database,密码换成自己的

spring:
  redis:
  host: 127.0.0.1 #地址
  port: 6379 #端口
  password:
  timeout: 30000 # 连接超时时间(毫秒)
  database: 15  #默认数据库
  jedis:
    pool:
      max-active: 200 # 连接池最大连接数(使用负值表示没有限制)
      max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制)
      max-idle: 10 # 连接池中的最大空闲连接
      min-idle: 0 # 连接池中的最小空闲连接
      time-between-eviction-runs: -1 #逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1

✌️ 常用api

TestRedis.java

@Slf4j
@SpringBootTest
public class TestRedis {

    @Resource(name = "stringRedisTemplate")
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 测试设置单个键值对
     */
    @Test
    void testSetValue() {
        stringRedisTemplate.opsForValue().set("testKey", "testValue");
        String value = stringRedisTemplate.opsForValue().get("testKey");
        log.info("设置并获取单个键值对,值为: {}", value);
    }

    /**
     * 测试设置带有过期时间的键值对  10秒过期
     */
    @Test
    void testSetValueWithExpiration() {
        stringRedisTemplate.opsForValue().set("expiringKey", "expiringValue", 10, TimeUnit.SECONDS);
        String value = stringRedisTemplate.opsForValue().get("expiringKey");
        log.info("设置带有过期时间的键值对,值为: {}", value);
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        value = stringRedisTemplate.opsForValue().get("expiringKey");
        log.info("过期时间已到,键值对已过期,值为: {}", value);
    }

    /**
     * 测试获取单个键的值
     */
    @Test
    void testGetValue() {
        stringRedisTemplate.opsForValue().set("existingKey", "existingValue");
        String value = stringRedisTemplate.opsForValue().get("existingKey");
        log.info("获取单个键的值,值为: {}", value);
    }

    /**
     * 测试删除单个键
     */
    @Test
    void testDeleteKey() {
        stringRedisTemplate.opsForValue().set("toDeleteKey", "toDeleteValue");
        Boolean result = stringRedisTemplate.delete("toDeleteKey");
        log.info("删除单个键,结果: {}", result);
    }

    /**
     * 测试批量删除键
     */
    @Test
    void testDeleteKeys() {
        stringRedisTemplate.opsForValue().set("key1", "value1");
        stringRedisTemplate.opsForValue().set("key2", "value2");
        Long deletedCount = stringRedisTemplate.delete(Arrays.asList("key1", "key2"));
        log.info("批量删除键,删除数量: {}", deletedCount);
    }

    /**
     * 测试设置哈希表
     */
    @Test
    void testSetHash() {
        Map<String, String> hash = new HashMap<>();
        hash.put("field1", "value1");
        hash.put("field2", "value2");
        stringRedisTemplate.opsForHash().putAll("testHash", hash);
        Map<Object, Object> result = stringRedisTemplate.opsForHash().entries("testHash");
        log.info("设置哈希表,结果: {}", result);
    }

    /**
     * 测试获取哈希表中的单个字段值
     */
    @Test
    void testGetHashField() {
        stringRedisTemplate.opsForHash().put("testHash", "field1", "value1");
        Object value = stringRedisTemplate.opsForHash().get("testHash", "field1");
        log.info("获取哈希表中的单个字段值,值为: {}", value);
    }

    /**
     * 测试获取哈希表的所有字段和值
     */
    @Test
    void testGetAllHashFields() {
        Map<String, String> hash = new HashMap<>();
        hash.put("field1", "value1");
        hash.put("field2", "value2");
        stringRedisTemplate.opsForHash().putAll("testHash", hash);
        Map<Object, Object> result = stringRedisTemplate.opsForHash().entries("testHash");
        log.info("获取哈希表的所有字段和值,结果: {}", result);
    }

    /**
     * 测试向列表左侧插入元素
     */
    @Test
    void testLeftPushToList() {
        stringRedisTemplate.opsForList().leftPush("testList", "element1");
        stringRedisTemplate.opsForList().leftPush("testList", "element2");
        List<String> list = stringRedisTemplate.opsForList().range("testList", 0, -1);
        log.info("向列表左侧插入元素,列表内容: {}", list);
    }

    /**
     * 测试从列表右侧弹出元素
     */
    @Test
    void testRightPopFromList() {
        stringRedisTemplate.opsForList().leftPush("testList", "element1");
        stringRedisTemplate.opsForList().leftPush("testList", "element2");
        String poppedElement = stringRedisTemplate.opsForList().rightPop("testList");
        log.info("从列表右侧弹出元素,弹出元素: {}", poppedElement);
    }

    /**
     * 测试向集合中添加元素
     */
    @Test
    void testAddToSet() {
        stringRedisTemplate.opsForSet().add("testSet", "element1", "element2");
        Set<String> set = stringRedisTemplate.opsForSet().members("testSet");
        log.info("向集合中添加元素,集合内容: {}", set);
    }

    /**
     * 测试从集合中移除元素
     */
    @Test
    void testRemoveFromSet() {
        stringRedisTemplate.opsForSet().add("testSet", "element1", "element2");
        Long removedCount = stringRedisTemplate.opsForSet().remove("testSet", "element1");
        log.info("从集合中移除元素,移除数量: {}", removedCount);
    }

    /**
     * 测试向有序集合中添加元素
     */
    @Test
    void testAddToZSet() {
        stringRedisTemplate.opsForZSet().add("testZSet", "element1", 1.0);
        stringRedisTemplate.opsForZSet().add("testZSet", "element2", 2.0);
        Set<String> zSet = stringRedisTemplate.opsForZSet().range("testZSet", 0, -1);
        log.info("向有序集合中添加元素,有序集合内容: {}", zSet);
    }

    /**
     * 测试从有序集合中移除元素
     */
    @Test
    void testRemoveFromZSet() {
        stringRedisTemplate.opsForZSet().add("testZSet", "element1", 1.0);
        stringRedisTemplate.opsForZSet().add("testZSet", "element2", 2.0);
        Long removedCount = stringRedisTemplate.opsForZSet().remove("testZSet", "element1");
        log.info("从有序集合中移除元素,移除数量: {}", removedCount);
    }
}

运行结果:

在这里插入图片描述

☝️ MongoDB crud操作

MongoDB是目前常用的高性能的分布式文件存储方案,下面看看他的api实现

✌️ maven依赖

<!-- mongodb连接驱动 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

✌️ 配置文件

我这里图省事,直接在config中写死了mongodb://127.0.0.1:27017/csdn,可配置在yml文件中读取

@Configuration
public class MongoConfig {
    @Bean
    public MongoDatabaseFactory mongoDatabaseFactory() {
        String connectionString = "mongodb://127.0.0.1:27017/csdn";
        return new SimpleMongoClientDatabaseFactory(connectionString);
    }

    @Bean(name = "mongoTemplate")
    public MongoTemplate mongoTemplate() {
        return new MongoTemplate(mongoDatabaseFactory());
    }
}

✌️ 定义实体类

代码中,@Data还是lombok注解,和mysql一样,@Document注解可以理解成映射行

如下图中:
在这里插入图片描述

图中的Mongo的集合(Collection)类比MySQL中的表名,Document类比表中的一行

那为什么一行在navicat中显示有那么多条数据呢?其实Mongo底层是BSON(Binary JSON)二进制存储格式,每个Document下面是一个大的json文件,样例如下:

{
    "_id": "order123",
    "orderDate": "2025-02-18",
    "customer": {
        "customerId": "cust456",
        "name": "John Doe",
        "email": "john.doe@example.com"
    },
    "items": [
        {
            "productId": "prod789",
            "productName": "Smartphone",
            "quantity": 2,
            "price": 500
        },
        {
            "productId": "prod012",
            "productName": "Headphones",
            "quantity": 1,
            "price": 100
        }
    ]
}

@Id注解可以理解成主键,一个对象中只能有一个,可以生动赋值,也可以用默认值,默认值按照ObjectId来取值,包含了时间戳、机器标识、进程 ID 和随机数等信息

在这里插入图片描述

MongoStoreProduct.java

@Data
@Document("storeproductinfo")
public class MongoStoreProduct {

    /**
     * 文档的id使用ObjectId类型来封装,并且贴上@Id注解
     */
    @Id
    @Field("_id")
    @JsonProperty("_id")
    private String id;

    /**
     * 图片
     */
    @Field("image")
    @JsonProperty("image")
    private String image;

    /**
     * 轮播图片
     */
    @Field("sliderImage")
    @JsonProperty("sliderImage")
    private String sliderImage;

    /**
     * 店铺名称
     */
    @Field("storeName")
    @JsonProperty("storeName")
    private String storeName;

    /**
     * 店铺信息
     */
    @Field("storeInfo")
    @JsonProperty("storeInfo")
    private String storeInfo;

    /**
     * 关键词
     */
    @Field("keyword")
    @JsonProperty("keyword")
    private String keyword;

    /**
     * 分类ID
     */
    @Field("cateId")
    @JsonProperty("cateId")
    private String cateId;

    /**
     * 单位名称
     */
    @Field("unitName")
    @JsonProperty("unitName")
    private String unitName;

    /**
     * 排序
     */
    @Field("sort")
    @JsonProperty("sort")
    private Integer sort;

    /**
     * 是否热门
     */
    @Field("isHot")
    @JsonProperty("isHot")
    private Boolean isHot;

    /**
     * 是否有优惠
     */
    @Field("isBenefit")
    @JsonProperty("isBenefit")
    private Boolean isBenefit;

    /**
     * 是否精品
     */
    @Field("isBest")
    @JsonProperty("isBest")
    private Boolean isBest;

    /**
     * 是否新品
     */
    @Field("isNew")
    @JsonProperty("isNew")
    private Boolean isNew;

    /**
     * 是否好评
     */
    @Field("isGood")
    @JsonProperty("isGood")
    private Boolean isGood;

    /**
     * 赠送积分
     */
    @Field("giveIntegral")
    @JsonProperty("giveIntegral")
    private Integer giveIntegral;

    /**
     * 是否子店铺
     */
    @Field("isSub")
    @JsonProperty("isSub")
    private Boolean isSub;

    /**
     * 虚拟销量
     */
    @Field("ficti")
    @JsonProperty("ficti")
    private Integer ficti;

    /**
     * 模板ID
     */
    @Field("tempId")
    @JsonProperty("tempId")
    private Integer tempId;

    /**
     * 规格类型
     */
    @Field("specType")
    @JsonProperty("specType")
    private Boolean specType;

    /**
     * 活动
     */
    @Field("activity")
    @JsonProperty("activity")
    private String activity;

    /**
     * 属性
     */
    @Field("attr")
    @JsonProperty("attr")
    private String attr;

    /**
     * 属性值
     */
    @Field("attrValue")
    @JsonProperty("attrValue")
    private String attrValue;

    /**
     * 内容
     */
    @Field("content")
    @JsonProperty("content")
    private String content;

    /**
     * 优惠券ID列表
     */
    @Field("couponIds")
    @JsonProperty("couponIds")
    private String couponIds;

    /**
     * 平铺模式
     */
    @Field("flatPattern")
    @JsonProperty("flatPattern")
    private String flatPattern;
}

✌️ MongoDB常用api

TestMongoDB.java

@Slf4j
@SpringBootTest
public class TestMongoDB {

    @Resource
    private StoreProductMongoRepository storeProductMongoRepository;

    /**
     * 生成模拟数据
     *
     * @param num 生成的数量
     * @return 模拟数据列表
     */
    private List<MongoStoreProduct> getStoreProduct(Integer num) {
        List<MongoStoreProduct> result = new ArrayList<>();
        for (int i = 0; i < num; i++) {
            MongoStoreProduct mongoStoreProduct = new MongoStoreProduct();
            mongoStoreProduct.setId(String.valueOf(999 + i))
                    .setImage("https://www.baidu.com/img/bd_logo1.png")
                    .setSliderImage("https://www.baidu.com/img/bd_logo1.png")
                    .setStoreName("测试商品" + i)
                    .setStoreInfo("测试商品" + i)
                    .setKeyword("测试商品")
                    .setCateId("1")
                    .setUnitName("件")
                    .setSort(1)
                    .setIsHot(true)
                    .setIsBenefit(true)
                    .setIsBest(true)
                    .setIsNew(true)
                    .setIsGood(true)
                    .setGiveIntegral(1)
                    .setIsSub(true)
                    .setFicti(1)
                    .setTempId(1)
                    .setSpecType(true)
                    .setActivity("{\"test\":\"test\"}")
                    .setAttr("{\"test\":\"test\"}")
                    .setAttrValue("{\"test\":\"test\"}")
                    .setContent("{\"test\":\"test\"}")
                    .setCouponIds("{\"test\":\"test\"}")
                    .setFlatPattern("{\"test\":\"test\"}");
            result.add(mongoStoreProduct);
        }
        return result;
    }

    /**
     * 插入单条数据  id相同时,内容会进行覆盖
     */
    @Test
    void test_insert() {
        MongoStoreProduct mongoStoreProduct = getStoreProduct(1).get(0);
        MongoStoreProduct save = storeProductMongoRepository.save(mongoStoreProduct);
        log.info("插入单条数据,结果: {}", save);
    }

    /**
     * 插入多条数据  id相同时,内容会进行覆盖
     */
    @Test
    void test_insertMultiple() {
        List<MongoStoreProduct> storeProduct = getStoreProduct(3);
        List<MongoStoreProduct> mongoStoreProducts = storeProductMongoRepository.saveAll(storeProduct);
        mongoStoreProducts.forEach(product -> log.info("插入多条数据,结果: {}", product));
    }

    /**
     * 根据 ID 查询单条数据
     */
    @Test
    void test_findById() {
        Optional<MongoStoreProduct> mongoStoreProductOpt = storeProductMongoRepository.findById(String.valueOf(999));
        if (mongoStoreProductOpt.isPresent()) {
            log.info("根据 ID 查询单条数据,结果: {}", mongoStoreProductOpt.get());
        } else {
            log.info("未找到对应 ID 的数据");
        }
    }

    /**
     * 查询所有数据
     */
    @Test
    void test_findAll() {
        Iterable<MongoStoreProduct> mongoStoreProducts = storeProductMongoRepository.findAll();
        mongoStoreProducts.forEach(product -> log.info("查询所有数据,结果: {}", product));
    }

    /**
     * 根据 ID 删除单条数据
     */
    @Test
    void test_deleteById() {
        storeProductMongoRepository.deleteById(String.valueOf(999));
        log.info("根据 ID 删除单条数据,删除完成");
    }

    /**
     * 删除所有数据
     */
    @Test
    void test_deleteAll() {
        storeProductMongoRepository.deleteAll();
        log.info("删除所有数据,删除完成");
    }

    /**
     * 分页查询数据
     */
    @Test
    void test_findAllByPage() {
        PageRequest pageRequest = PageRequest.of(0, 2, Sort.by(Sort.Direction.ASC, "id"));
        Page<MongoStoreProduct> productPage  = storeProductMongoRepository.findAll(pageRequest);
        log.info("当前页码: {}, 每页记录数: {}, 总记录数: {}, 总页数: {}",
                productPage.getNumber(), productPage.getSize(), productPage.getTotalElements(), productPage.getTotalPages());
        // 当前页码: 0, 每页记录数: 2, 总记录数: 23, 总页数: 12
        productPage.getContent().forEach(product -> log.info("分页查询数据,结果: {}", product));
    }
}

分页返回结果:

在这里插入图片描述

注意点:

  1. mongoDB在save或者saveAll时,如果id已经存在,则会对改id数据进行覆盖
  2. storeProductMongoRepository中没有更新相关的接口,可以根据第一点特性进行数据覆盖,代码如下,
@Test
void test_updateByFindAndSave() {
    Optional<StoreProduct> productOptional = storeProductMongoRepository.findById(999L);
    if (productOptional.isPresent()) {
        StoreProduct product = productOptional.get();
        product.setStoreName("更新后的测试商品");
        StoreProduct updatedProduct = storeProductMongoRepository.save(product);
        log.info("更新数据,结果: {}", updatedProduct);
    } else {
        log.info("未找到对应 ID 的数据,无法更新");
    }
}

☝️ ES crud操作 ⭐️⭐️⭐️

✌️ 前期准备

MySQL、Redis、MongoDB可视化工具用Navicat可以解决,ES可以使用ElasticHD,当然也可以使用Postman直接查询结果,先简单介绍一下ElasticHD使用

  1. 下载地址:https://github.com/360EntSecGroup-Skylar/ElasticHD/releases
  2. 执行:直接双击ElasticHD.exe。//或./ElasticHD -p 127.0.0.1:980
  3. 启动访问:http://localhost:9800

这个 Dashboard的UI设计非常酷炫:

在这里插入图片描述
输入es连接,点connect登录:

在这里插入图片描述

如果有账号密码,使用

http://username:password@host:port

例如:

http://elastic:elastic@http://127.0.0.1:9200

数据搜索直观易使用:

在这里插入图片描述

索引列表看得比较清楚:

在这里插入图片描述
这个 SQL查询语句ESJson查询格式的小工具挺厉害的:在这里插入图片描述

✌️ maven依赖

⭐️ tips

es 8.x以上版本,只支持springboot 2.7.x以上,且maven依赖的版本需要和es服务的版本要保持一致,因为我的springBoot版本为2.4.2,我的es选取的是7.13.2版本

在这里插入图片描述

<!-- 引入es -->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-elasticsearch</artifactId>
    <version>4.2.9</version>
    <scope>compile</scope>
    <exclusions>
        <exclusion>
            <groupId>transport</groupId>
            <artifactId>org.elasticsearch.client</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.13.2</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>7.13.2</version>
</dependency>

✌️ 配置文件

application.yml

spring:
  elasticsearch:
    rest:
      uris: 127.0.0.1:9200
      username:
      password:
      read-timeout: 120s
es:
  storeProduct:
    indexName: store_product_info_v2
    pageSize: 500
@Configuration
@ConfigurationProperties(prefix = "spring.elasticsearch.rest")
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {
    @Value("${spring.elasticsearch.rest.uris}")
    private String uris;
    @Value("${spring.elasticsearch.rest.username}")
    private String username;
    @Value("${spring.elasticsearch.rest.password}")
    private String password;

    @Override
    @Bean(name = "elasticsearchClient", destroyMethod = "close")
    public RestHighLevelClient elasticsearchClient() {
        ClientConfiguration configuration = ClientConfiguration.builder()
                .connectedTo(uris)
                .withBasicAuth(username, password)
                .withConnectTimeout(Duration.ofSeconds(60))
                .withSocketTimeout(Duration.ofSeconds(60))
                .withHttpClientConfigurer(httpClientBuilder -> httpClientBuilder
                        .setDefaultIOReactorConfig(IOReactorConfig.custom().setSoKeepAlive(true).build())
                        .setKeepAliveStrategy((httpResponse, httpContext) -> 1000 * 60 * 3)
                )
                .build();
        return RestClients.create(configuration).rest();
    }

    @Override
    @Bean(name = {"elasticsearchRestTemplate"})
    public ElasticsearchRestTemplate elasticsearchOperations(ElasticsearchConverter elasticsearchConverter,
                                                             @Qualifier("elasticsearchClient") RestHighLevelClient elasticsearchClient) {
        return new ElasticsearchRestTemplate(elasticsearchClient, elasticsearchConverter);
    }
}

✌️ 定义实体类

@Document(indexName = "store_product_info_v2")注解绑定是es的索引,@Setting是配置文件的目录,number_of_shards是分片数,number_of_replicas表示分片副本数,max_result_window允许搜索最大值

{
  "index": {
    "number_of_shards": 1,
    "number_of_replicas": 1,
    "max_result_window": 100000
  }
}

StoreProductEsDTO.java

@Data
@Document(indexName = "store_product_info_v2")
//@Document(indexName = "#{@StoreProductServiceImpl.getStoreProductEsIndexName()}")
@Setting(settingPath = "es/StoreProductSettings.json")
public class StoreProductEsDTO {

    /**
     * id
     */
    @Id
    @Field(type = FieldType.Keyword)
    private String id;
    /**
     * 图片
     */
    @Field(value = "image", type = FieldType.Text)
    private String image;
    /**
     * 滑块图片
     */
    @Field(value = "slider_image", type = FieldType.Text)
    private String sliderImage;
    /**
     * 店铺名称
     */
    @Field(value = "store_name", type = FieldType.Text)
    private String storeName;
    /**
     * 店铺信息
     */
    @Field(value = "store_info", type = FieldType.Text)
    private String storeInfo;
    /**
     * 关键词
     */
    @Field(value = "keyword", type = FieldType.Text)
    private String keyword;
    /**
     * 分类 ID
     */
    @Field(value = "cate_id", type = FieldType.Keyword)
    private String cateId;
    /**
     * 单位名称
     */
    @Field(value = "unit_name", type = FieldType.Text)
    private String unitName;
    /**
     * 排序
     */
    @Field(value = "sort", type = FieldType.Integer)
    private Integer sort;
    /**
     * 是否热门
     */
    @Field(value = "is_hot", type = FieldType.Boolean)
    private Boolean isHot;
    /**
     * 是否有优惠
     */
    @Field(value = "is_benefit", type = FieldType.Boolean)
    private Boolean isBenefit;
    /**
     * 是否精品
     */
    @Field(value = "is_best", type = FieldType.Boolean)
    private Boolean isBest;
    /**
     * 是否新品
     */
    @Field(value = "is_new", type = FieldType.Boolean)
    private Boolean isNew;
    /**
     * 是否优质
     */
    @Field(value = "is_good", type = FieldType.Boolean)
    private Boolean isGood;
    /**
     * 赠送积分
     */
    @Field(value = "give_integral", type = FieldType.Integer)
    private Integer giveIntegral;
    /**
     * 是否子项
     */
    @Field(value = "is_sub", type = FieldType.Boolean)
    private Boolean isSub;
    /**
     * 虚拟数据
     */
    @Field(value = "ficti", type = FieldType.Integer)
    private Integer ficti;
    /**
     * 模板 ID
     */
    @Field(value = "temp_id", type = FieldType.Integer)
    private Integer tempId;
    /**
     * 规格类型
     */
    @Field(value = "spec_type", type = FieldType.Boolean)
    private Boolean specType;
    /**
     * 活动
     */
    @Field(value = "activity", type = FieldType.Text)
    private String activity;
    /**
     * 属性
     */
    @Field(value = "attr", type = FieldType.Text)
    private String attr;
    /**
     * 属性值
     */
    @Field(value = "attr_value", type = FieldType.Text)
    private String attrValue;
    /**
     * 内容
     */
    @Field(value = "content", type = FieldType.Text)
    private String content;
    /**
     * 优惠券 ID 列表
     */
    @Field(value = "coupon_ids", type = FieldType.Text)
    private String couponIds;
    /**
     * 平铺模式
     */
    @Field(value = "flat_pattern", type = FieldType.Text)
    private String flatPattern;

}

✌️ ES常用api

TestES.java

import com.db.test.entity.dto.StoreProductEsDTO;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.*;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;

/**
 * @author hanson.huang
 * @version V1.0
 * @ClassName TestES
 * @Description es 测试类
 * @date 2025/2/18 19:41
 **/
@Slf4j
@SpringBootTest
public class TestES {

    @Resource(name = "elasticsearchRestTemplate")
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    /**
     * 生成模拟的 StoreProductEsDTO 对象
     * @return 模拟对象
     */
    private StoreProductEsDTO generateMockProduct(String id) {
        StoreProductEsDTO product = new StoreProductEsDTO();
        product.setId(id);
        product.setImage("https://example.com/image.jpg");
        product.setSliderImage("https://example.com/slider_image.jpg");
        product.setStoreName("测试店铺商品" + id);
        product.setStoreInfo("这是一个测试用的店铺商品信息");
        product.setKeyword("测试商品");
        product.setCateId("1001");
        product.setUnitName("件");
        product.setSort(1);
        product.setIsHot(true);
        product.setIsBenefit(true);
        product.setIsBest(true);
        product.setIsNew(true);
        product.setIsGood(true);
        product.setGiveIntegral(10);
        product.setIsSub(false);
        product.setFicti(1);
        product.setTempId(1);
        product.setSpecType(true);
        product.setActivity("{\"name\":\"测试活动\"}");
        product.setAttr("{\"color\":\"red\"}");
        product.setAttrValue("{\"size\":\"L\"}");
        product.setContent("商品详细内容描述");
        product.setCouponIds("{\"id\":\"C001\"}");
        product.setFlatPattern("{\"mode\":\"平铺\"}");
        return product;
    }

    /**
     * 插入单条文档
     */
    @Test
    void testInsertDocument() {
        StoreProductEsDTO product = generateMockProduct("991");
        StoreProductEsDTO savedProduct = elasticsearchRestTemplate.save(product);
        log.info("插入文档结果: {}", savedProduct);
    }

    /**
     * 批量插入文档
     */
    @Test
    void testBulkInsertDocuments() {
        List<StoreProductEsDTO> products = Arrays.asList(generateMockProduct("992"), generateMockProduct("993"));
        Iterable<StoreProductEsDTO> savedProducts = elasticsearchRestTemplate.save(products);
        savedProducts.forEach(product -> log.info("批量插入文档结果: {}", product));
    }

    /**
     * 根据 ID 删除文档
     */
    @Test
    void testDeleteDocument() {
        String id = "997";
        elasticsearchRestTemplate.delete(id, StoreProductEsDTO.class);
        log.info("删除 ID 为 {} 的文档", id);
    }

    /**
     * 根据 ID 更新文档
     */
    @Test
    void testUpdateDocument() {
        StoreProductEsDTO product = generateMockProduct("994");
        product.setStoreName("更新后的测试店铺商品");
        StoreProductEsDTO updatedProduct = elasticsearchRestTemplate.save(product);
        log.info("更新文档结果: {}", updatedProduct);
    }

    /**
     * 查询单条文档
     */
    @Test
    void testSearchSingleDocument() {
        String id = "992";
        StoreProductEsDTO product = elasticsearchRestTemplate.get(id, StoreProductEsDTO.class);
        if (product != null) {
            log.info("查询到的文档: {}", product);
        } else {
            log.info("未查询到 ID 为 {} 的文档", id);
        }
    }

    /**
     * 查询所有文档
     */
    @Test
    void testSearchAllDocuments() {
        Query query = new CriteriaQuery(new Criteria());
        SearchHits<StoreProductEsDTO> searchHits = elasticsearchRestTemplate.search(query, StoreProductEsDTO.class);
        searchHits.forEach(hit -> log.info("查询到的文档: {}", hit.getContent()));
    }

    /**
     * 分页查询文档
     */
    @Test
    void testSearchDocumentsByPage() {
        int page = 0;
        int size = 10;
        Pageable pageable = PageRequest.of(page, size);
        Query query = new CriteriaQuery(new Criteria()).setPageable(pageable);

        SearchHits<StoreProductEsDTO> searchHits = elasticsearchRestTemplate.search(query, StoreProductEsDTO.class);
        log.info("当前页文档数量: {}", searchHits.getSearchHits().size());

        // 修正遍历部分
        List<org.springframework.data.elasticsearch.core.SearchHit<StoreProductEsDTO>> searchHitList = searchHits.getSearchHits();
        for (org.springframework.data.elasticsearch.core.SearchHit<StoreProductEsDTO> hit : searchHitList) {
            log.info("分页查询到的文档: {}", hit.getContent());
        }
    }

    /**
     * 根据条件查询文档
     */
    @Test
    void testSearchDocumentsByCondition() {
        Criteria criteria = new Criteria("storeName").is("测试店铺商品");
        Query query = new CriteriaQuery(criteria);
        SearchHits<StoreProductEsDTO> searchHits = elasticsearchRestTemplate.search(query, StoreProductEsDTO.class);
        searchHits.forEach(hit -> log.info("根据条件查询到的文档: {}", hit.getContent()));
    }
}

条件查询中,Criteria是模糊匹配,能查出storeName值为测试店铺商品、‘xxx测试店铺商品’、 ‘测试店铺商品xxx’、 'xxx测试店铺商品xxx’等情况

结果如下:

在这里插入图片描述

☝️ 性能比较

先叠个甲,本次比较非常不专业,数量比较小,MySQL也没设置合适索引,所以本次性能比较不具备参考性

✌️ 模拟创建数据接口

controller

@Resource
private StoreProductService storeProductService;

/**
 * 添加数据
 *
 * @param storeProductRequest 需要添加的数据
 * @return
 */
@PostMapping("/addData")
public String addData(@RequestBody StoreProductRequest storeProductRequest) {
    storeProductService.insertData(storeProductRequest);
    return "success";
}

实现类:StoreProductServiceImpl.java

@Slf4j
@Data
@Service
public class StoreProductServiceImpl extends ServiceImpl<StoreProductMapper, StoreProduct> implements StoreProductService {

    @Resource
    private StoreProductMapper storeProductMapper;

    @Resource(name = "stringRedisTemplate")
    private StringRedisTemplate stringRedisTemplate;

    @Resource(name = "elasticsearchRestTemplate")
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Resource
    private StoreProductMongoRepository storeProductMongoRepository;

    private static final String REDIS_KEY = "storeProduct:key";

    @Value("${es.storeProduct.indexName:store_product_info_v2}")
    public String storeProductEsIndexName = "store_product_info_v2";

    @Value("${es.storeProduct.pageSize:500}")
    private int storeProductEsListPageSize = 500;

    @Override
    public void insertData(StoreProductRequest storeProductRequest) {
        // 1.插入mysql
        StoreProduct storeProduct = new StoreProduct();
        BeanUtils.copyProperties(storeProductRequest, storeProduct);
        storeProduct.setActivity(JacksonUtils.jsonEncode(storeProductRequest.getActivity()));
        storeProduct.setAttr(JacksonUtils.jsonEncode(storeProductRequest.getAttr()));
        storeProduct.setAttrValue(JacksonUtils.jsonEncode(storeProductRequest.getAttrValue()));
        storeProduct.setCouponIds(JacksonUtils.jsonEncode(storeProductRequest.getCouponIds()));
        storeProductMapper.insert(storeProduct);
        log.warn("数据已经插入mysql数据库:{}", JacksonUtils.jsonEncode(storeProduct));

        // 2.插入redis
        stringRedisTemplate.opsForValue().set(REDIS_KEY + storeProduct.getId(), JacksonUtils.jsonEncode(storeProduct));
        log.warn("数据已经插入redis数据库:{}", JacksonUtils.jsonEncode(storeProduct));

        // 3.插入mongo
        MongoStoreProduct mongoStoreProduct = new MongoStoreProduct();
        BeanUtils.copyProperties(storeProduct, mongoStoreProduct);
        mongoStoreProduct.setId(storeProduct.getId() + "");
        try {
            storeProductMongoRepository.save(mongoStoreProduct);
            log.warn("数据已经插入mongo数据库:{}", JacksonUtils.jsonEncode(mongoStoreProduct));
        } catch (Exception e) {
            log.error("数据插入mongo数据库失败,失败原因:{}", e);
        }

        // 4.插入es
        StoreProductEsDTO storeProductEsDTO = new StoreProductEsDTO();
        BeanUtils.copyProperties(storeProduct, storeProductEsDTO);
        storeProductEsDTO.setId(storeProduct.getId() + "");
        // 创建客户端
        List<IndexQuery> queries = new ArrayList<>();
        IndexQuery indexQuery = new IndexQuery();
        indexQuery.setId(storeProduct.getId() + "");
        indexQuery.setObject(storeProductEsDTO);
        queries.add(indexQuery);
        try {
            elasticsearchRestTemplate.bulkIndex(queries, StoreProductEsDTO.class);
            log.warn("数据已经插入es数据库:{}", JacksonUtils.jsonEncode(storeProductEsDTO));
        } catch (Exception e) {
            log.error("数据插入es数据库失败,失败原因:{}", e);
        }
    }    
}

接口:

curl --location 'localhost:8081/dbTest/addData' \
--header 'Content-Type: application/json' \
--data '{
  "image": "https://example.com/image.jpg",
  "sliderImage": "https://example.com/slider1.jpg,https://example.com/slider2.jpg",
  "storeName": "新款智能手机",
  "storeInfo": "这是一款高性能智能手机",
  "keyword": "手机,智能手机",
  "cateId": "1,2,3",
  "unitName": "台",
  "sort": 1,
  "isHot": true,
  "isBenefit": false,
  "isBest": true,
  "isNew": true,
  "isGood": false,
  "giveIntegral": 100,
  "isSub": true,
  "ficti": 500,
  "tempId": 1,
  "specType": true,
  "activity": ["1", "2", "3"],
  "attr": [
    {
      "attrName": "颜色",
      "attrValues": "红色,蓝色,绿色"
    },
    {
      "attrName": "尺寸",
      "attrValues": "大号,中号,小号"
    }
  ],
  "attrValue": [
    {
      "productId": 0,
      "stock": 100,
      "suk": "红色-大号",
      "price": 1999.00,
      "image": "https://example.com/red-large.jpg",
      "cost": 1500.00,
      "otPrice": 2199.00,
      "weight": 0.5,
      "volume": 0.1,
      "brokerage": 100.00,
      "brokerageTwo": 50.00,
      "attrValue": "{\"颜色\":\"红色\",\"尺寸\":\"大号\"}",
      "quota": 10,
      "quotaShow": 10,
      "minPrice": 1500.00
    },
    {
      "productId": 0,
      "stock": 150,
      "suk": "蓝色-中号",
      "price": 1899.00,
      "image": "https://example.com/blue-medium.jpg",
      "cost": 1400.00,
      "otPrice": 2099.00,
      "weight": 0.45,
      "volume": 0.09,
      "brokerage": 90.00,
      "brokerageTwo": 45.00,
      "attrValue": "{\"颜色\":\"蓝色\",\"尺寸\":\"中号\"}",
      "quota": 15,
      "quotaShow": 15,
      "minPrice": 1400.00
    }
  ],
  "content": "<p>这是一款高性能智能手机,适合各种场景使用。</p>",
  "couponIds": [1, 2, 3],
  "flatPattern": "https://example.com/flat-pattern.jpg"
}'

调用这个接口后,分别往四个中间件中插入了id为1000的数据

在这里插入图片描述

MySQL:

在这里插入图片描述
Redis:

在这里插入图片描述

MongoDB:

在这里插入图片描述

ES:

在这里插入图片描述

这样数据算创建完成,现在测试分别查出这条数据需要花费时间

✌️ 查询数据接口

我们使用stopwatch()来统计接口耗时,stopwatch()用法可以参考文章《【StopWatch】使用 StopWatch 统计代码中的耗时操作》

代码如下:

/**
 * @return 通过四种方式获取数据
 */
@Override
public Map<String, Object> getData(Integer id) {
    Map<String, Object> result = new HashMap<>();
    // 1.从mysql获取数据
    StopWatch stopWatch = new StopWatch();
    stopWatch.start("mysql查询数据开始");
    StoreProduct storeProduct = storeProductMapper.selectById(id);
    result.put("mysql", storeProduct);
    stopWatch.stop();

    // 2.从redis获取数据
    stopWatch.start("redis查询数据开始");
    String redisData = stringRedisTemplate.opsForValue().get(REDIS_KEY + id);
    result.put("redis", JacksonUtils.jsonDecode(redisData, StoreProduct.class));
    stopWatch.stop();

    // 3.从mongo获取数据
    stopWatch.start("mongo查询数据开始");
    Optional<MongoStoreProduct> optional = storeProductMongoRepository.findById(String.valueOf(id));
    if (optional.isPresent()) {
        MongoStoreProduct mongoStoreProduct = optional.get();
        result.put("mongo", mongoStoreProduct);
    }
    stopWatch.stop();

    // 4.从es获取数据
    stopWatch.start("es查询数据开始");
    StoreProductEsDTO storeProductEsDTO = elasticsearchRestTemplate.get(String.valueOf(id), StoreProductEsDTO.class);
    result.put("es", storeProductEsDTO);
    stopWatch.stop();

    log.error("查询数据耗时:{}", stopWatch.prettyPrint(TimeUnit.MILLISECONDS));

    return result;
}

调用接口:

在这里插入图片描述
统计耗时:

查询数据耗时:StopWatch '': running time = 87 ms
---------------------------------------------
ms         %     Task name
---------------------------------------------
000000033  38%   mysql查询数据开始
000000012  14%   redis查询数据开始
000000024  28%   mongo查询数据开始
000000017  20%   es查询数据开始

虽然结果不具备参考性,可以看出es和redis性能比较好,在大量数据情况下,就查询数据而言,redis > es > mongoDB > mysql

总结一下:

中间件查询效率性能分析底层存储结构优点缺点使用场景
MySQL中等适用于结构化数据,复杂查询性能较好关系型数据库,使用B+树索引1. 支持复杂查询和事务
2. 数据一致性高
3. 成熟的生态系统和工具支持
1. 大数据量时性能下降
2. 水平扩展较复杂
3. 不适合非结构化数据
1. 金融系统(需要强一致性和事务支持)
2. ERP系统(复杂查询和报表)
3. 传统的关系型数据管理(如用户管理、订单管理)
Redis适用于高并发、低延迟的场景内存键值存储,支持多种数据结构1. 极高的读写性能
2. 支持丰富的数据结构
3. 适合缓存和实时数据处理
1. 数据容量受内存限制
2. 持久化可能影响性能
3. 不适合复杂查询
1. 缓存系统(如网页缓存、会话缓存)
2. 实时排行榜(如游戏积分榜)
3. 消息队列(如任务队列)
4. 实时数据处理(如实时推荐系统)
MongoDB中高适用于半结构化数据,读写性能较好文档型数据库,使用BSON格式存储,支持索引1. 灵活的数据模型
2. 水平扩展容易
3. 适合处理大量非结构化数据
1. 复杂查询性能不如关系型数据库
2. 事务支持较弱(虽然MongoDB 4.0+支持多文档事务)
3. 存储空间占用较大
1. 内容管理系统(CMS)
2. 物联网(IoT)数据存储
3. 日志存储和分析
4. 实时大数据处理(如用户行为分析)
Elasticsearch适用于全文搜索和实时分析分布式搜索引擎,使用倒排索引1. 强大的全文搜索能力
2. 实时数据分析
3. 水平扩展容易
1. 写入性能相对较低
2. 配置和维护复杂
3. 数据一致性较弱(最终一致性)
1. 全文搜索引擎(如电商网站的商品搜索)
2. 日志和指标分析(如ELK Stack)
3. 实时数据分析(如监控和报警系统)
4. 推荐系统(基于用户行为的实时推荐)

创作不易,不妨点赞、收藏、关注支持一下,各位的支持就是我创作的最大动力❤️

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/971952.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

解锁D3.js与PlantUML的交互奥秘:探索知识图谱数据可视化新领域

解锁D3.js与PlantUML的交互魔法&#xff1a;数据可视化新征程 在前端开发的广袤天地里&#xff0c;数据可视化一直是一颗璀璨的明珠&#xff0c;吸引着无数开发者探索其奥秘。而当D3.js这一强大的JavaScript库&#xff0c;遇上专注于创建UML图的PlantUML&#xff0c;一场奇妙的…

DeepSeek24小时写作机器人,持续创作高质量文案

内容创作已成为企业、自媒体和创作者的核心竞争力。面对海量的内容需求&#xff0c;人工创作效率低、成本高、质量参差不齐等问题日益凸显。如何在有限时间内产出高质量内容&#xff1f;DeepSeek写作机器人&#xff0c;一款24小时持续创作的智能工具&#xff0c;为企业和个人提…

使用html css js 来实现一个服装行业的企业站源码-静态网站模板

最近在练习 前端基础&#xff0c;html css 和js 为了加强 代码的 熟悉程序&#xff0c;就使用 前端 写了一个个服装行业的企业站。把使用的技术 和 页面效果分享给大家。 应用场景 该制衣服装工厂官网前端静态网站模板主要用于前端练习和编程练习&#xff0c;适合初学者进行 HT…

使用html css js 开发一个 教育机构前端静态网站模板

这个教育机构网站模板是专为前端开发初学者设计的练习项目&#xff0c;适合正在学习前端的学生或自学者使用。网站内容包括首页、课程体系、师资力量、关于我们和联系我们等基础页面&#xff0c;帮助学习者熟悉网页布局、样式设计和交互功能的实现。 静态页面 简单截图 应用…

(蓝桥杯——10. 小郑做志愿者)洛斯里克城志愿者问题详解

题目背景 小郑是一名大学生,她决定通过做志愿者来增加自己的综合分。她的任务是帮助游客解决交通困难的问题。洛斯里克城是一个六朝古都,拥有 N 个区域和古老的地铁系统。地铁线路覆盖了树形结构上的某些路径,游客会询问两个区域是否可以通过某条地铁线路直达,以及有多少条…

React 低代码项目:网络请求与问卷基础实现

&#x1f35e;吐司问卷&#xff1a;网络请求与问卷基础实现 Date: February 10, 2025 Log 技术要点&#xff1a; HTTP协议XMLHttpRequest、fetch、axiosmock.js、postmanWebpack devServer 代理、craco.js 扩展 webpackRestful API 开发要点&#xff1a; 搭建 mock 服务 …

大流量汽(柴)油机泵,抗洪抢险的可靠选择|深圳鼎跃

近年来&#xff0c;全球范围内极端天气频发&#xff0c;洪涝灾害成为危及人民生命财产安全的重要因素。在抗洪抢险行动中&#xff0c;如何迅速、高效地排除积水&#xff0c;保障救援通道和安全区域成为关键。汽柴油机泵凭借其动力强、移动灵活、环境适应性强等特点&#xff0c;…

游戏开发微信小程序--工具箱之父

小程序工具箱之父已更新 Page({data: {score: 0,lives: 3,gameOver: false,playerVisible: true,level: 1,petType: cat,speedBuff: 1,coins: 0,friends: [],achievements: [],currentPetFrame: 0, // 当前宠物动画帧scoreMultiplier: 1, // 得分倍率gameSpeed: 1, // …

一.数据治理理论架构

1、数据治理核心思想&#xff1a; 数据治理理论架构图描绘了一个由顶层设计、管控机制、核心领域和管理系统四个主要部分组成的数据治理框架。它旨在通过系统化的方法&#xff0c;解决数据治理机制缺失引发的业务和技术问题&#xff0c;并最终提升企业的数据管理水平。 数据治…

一键安装教程

有需要的可以私信 亮点&#xff1a; 不再需要安装完去配置环境变量&#xff0c;下载完程序&#xff0c;解压后&#xff0c;右键进行管理员安装&#xff0c;安装完毕自动配置环境变量&#xff0c;即可使用 Maven 安装 右键 以管理员身份运行点击 下一步安装完成后会同步配置环境…

crud项目分析(2)

JWT令牌验证是否登录成功 简单的验证账号密码是否正确(存在) 全局异常处理器 过滤器 因为login下只有这一个网页 唯一一种操作 package com.itheima.filter;import ch.qos.logback.core.util.StringUtil; import com.alibaba.fastjson.JSONObject; import com.itheima.pojo.R…

深入解析iOS视频录制(二):自定义UI的实现

深入解析 iOS 视频录制&#xff08;一&#xff09;&#xff1a;录制管理核心MWRecordingController 类的设计与实现 深入解析iOS视频录制&#xff08;二&#xff09;&#xff1a;自定义UI的实现​​​​​​​ 深入解析 iOS 视频录制&#xff08;三&#xff09;&#xff1a;完…

【Linux系统】生产者消费者模型:基于环形队列(信号量机制)

理论层面 1、环形队列的特性认识 环形队列采用数组模拟&#xff0c;用模运算来模拟环状特性 环形结构起始状态和结束状态都是⼀样的&#xff0c;不好判断为空或者为满&#xff0c;所以可以通过加计数器或者标记位来判断满或者空。另外也可以预留⼀个空的位置&#xff0c;作为…

【笔记】LLM|Ubuntu22服务器极简本地部署DeepSeek+API使用方式

2025/02/18说明&#xff1a;2月18日~2月20日是2024年度博客之星投票时间&#xff0c;走过路过可以帮忙点点投票吗&#xff1f;我想要前一百的实体证书&#xff0c;经过我严密的计算只要再拿到60票就稳了。一人可能会有多票&#xff0c;Thanks♪(&#xff65;ω&#xff65;)&am…

leetcode-414.第三大的数

leetcode-414.第三大的数 code review! 文章目录 leetcode-414.第三大的数一.题目描述二.代码提交 一.题目描述 二.代码提交 class Solution { public:int thirdMax(vector<int>& nums) {set<int> set_v(nums.begin(), nums.end());auto it set_v.rbegin()…

【设计模式】 代理模式(静态代理、动态代理{JDK动态代理、JDK动态代理与CGLIB动态代理的区别})

代理模式 代理模式是一种结构型设计模式&#xff0c;它提供了一种替代访问的方法&#xff0c;即通过代理对象来间接访问目标对象。代理模式可以在不改变原始类代码的情况下&#xff0c;增加额外的功能&#xff0c;如权限控制、日志记录等。 静态代理 静态代理是指创建的或特…

深度学习之图像回归(二)

前言 这篇文章主要是在图像回归&#xff08;一&#xff09;的基础上对该项目进行的优化。&#xff08;一&#xff09;主要是帮助迅速入门 理清一个深度学习项目的逻辑 这篇文章则主要注重在此基础上对于数据预处理和模型训练进行优化前者会通过涉及PCA主成分分析 特征选择 后…

利用分治策略优化快速排序

1. 基本思想 分治快速排序&#xff08;Quick Sort&#xff09;是一种基于分治法的排序算法&#xff0c;采用递归的方式将一个数组分割成小的子数组&#xff0c;并通过交换元素来使得每个子数组元素按照特定顺序排列&#xff0c;最终将整个数组排序。 快速排序的基本步骤&#…

照片模糊怎么变清晰?图生生AI修图-一键清晰放大

当打开手机相册时&#xff0c;那些泛着噪点的合影、细节模糊的风景照、像素化的证件图片&#xff0c;让珍贵时刻蒙上遗憾的面纱。而专业级图像修复工具的门槛&#xff0c;让多数人只能无奈接受这些"不完美的记忆"。AI技术的发展&#xff0c;让普通用户也能轻松拥有专…

Linux 网络与常用操作(适合开发/运维/网络工程师)

目录 OSI 七层协议简介 应用层 传输层 Linux 命令&#xff01;&#xff01;&#xff01; 1. ifconfig 命令 简介 1. 查看网络地址信息 2. 指定开启、或者关闭网卡 3. 修改、设置 IP 地址 4. 修改机器的 MAC 地址信息 5. 永久修改网络设备信息 2. route 路由命令 …