当前位置: 首页 > article >正文

【存储中间件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. 推荐系统(基于用户行为的实时推荐)

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

在这里插入图片描述


http://www.kler.cn/a/556327.html

相关文章:

  • windows怎样查看系统信息(处理器等)
  • 2025最新Python机器视觉实战:基于OpenCV与深度学习的多功能工业视觉检测系统(附完整代码)
  • Maven 的高级调试技巧与常见问题
  • 动态存储斐波那契数列(递归优化)
  • Unity游戏制作中的C#基础(2)变量与数据类型
  • Kettle 实战面试题及参考答案(完整版)
  • 【Java基础-46.3】Java泛型通配符详解:解锁类型安全的灵活编程
  • JavaScript如何创建一个对象?对象字面量和构造函数创建对象有什么区别?
  • 【第三节】C++设计模式(创建型模式)-单例模式
  • 通过监督微调提升多语言大语言模型性能
  • 模电知识点总结(5)
  • docker 和 Quay.io的关系
  • 使用 ^= 对每个字节进行异或操作完成校验和
  • Elasticsearch实战应用:从“搜索小白”到“数据侦探”的进阶之路
  • 5分钟下载excel模板
  • 【deepseek】本地部署+RAG知识库挂载+对话测试
  • 【Film】论文:2024 视频生成可以取代摄影师吗?生成视频的电影语言研究
  • GB28181协议详解
  • RabbitMQ报错:Shutdown Signal channel error; protocol method
  • 浅谈Word2vec算法模型