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

【存储中间件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.DruidDataSourcedriver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/csdn?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimeZone=GMT+8username: rootpassword: 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 {@Resourceprivate 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;}/*** 插入单条数据*/@Testvoid test_insert() {StoreProduct storeProduct = getStoreProduct(1).get(0);storeProductMapper.insert(storeProduct);}/*** 按照id删除*/@Testvoid test_deleteById() {storeProductMapper.deleteById(999);}/*** 多个条件删除*/@Testvoid 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语句删除*/@Testvoid test_deleteByWrapper() {// 创建 QueryWrapper 对象QueryWrapper<StoreProduct> queryWrapper = new QueryWrapper<>();// 添加删除条件,例如删除 id 为 "999" 的记录,并且storeName 为 "测试商品" 的记录queryWrapper.eq("id", 999);storeProductMapper.delete(queryWrapper);}/*** 构建wrapper语句删除*/@Testvoid test_deleteByLambdaWrapper() {// 创建 LambdaQueryWrapper 对象LambdaQueryWrapper<StoreProduct> queryWrapper = new LambdaQueryWrapper<>();// 添加删除条件,例如删除 id 为 "999" 的记录,并且storeName 为 "测试商品" 的记录queryWrapper.eq(StoreProduct::getId, 999);storeProductMapper.delete(queryWrapper);}/*** 批量删除*/@Testvoid test_deleteBatchIds() {storeProductMapper.deleteBatchIds(Arrays.asList(999, 1000));}/*** 更新数据*/@Testvoid test_updateById() {StoreProduct storeProduct = getStoreProduct(1).get(0);storeProduct.setStoreName("商品名字更新啦~");storeProductMapper.updateById(storeProduct);}/*** 构建wrapper语句更新*/@Testvoid test_updateByWrapper() {// 创建 UpdateWrapper 对象UpdateWrapper<StoreProduct> queryWrapper = new UpdateWrapper<>();// 添加更新条件,例如更新 id 为 "999"queryWrapper.eq("id", 999);queryWrapper.set("store_name", "商品名字再次更新啦~");storeProductMapper.update(null, queryWrapper);}/*** 构建LambdaWrapper语句更新*/@Testvoid test_updateByLambdaWrapper() {// 创建 UpdateWrapper 对象LambdaUpdateWrapper<StoreProduct> queryWrapper = new LambdaUpdateWrapper<>();// 添加更新条件,例如更新 id 为 "999"queryWrapper.eq(StoreProduct::getId, 999);queryWrapper.set(StoreProduct::getStoreName, "商品名字再再次更新啦~");storeProductMapper.update(null, queryWrapper);}/*** 通过id查找*/@Testvoid test_selectById() {StoreProduct storeProduct = storeProductMapper.selectById(999);log.info("查询结果:{}", storeProduct);}/*** 通过id集合查找*/@Testvoid test_selectBatchIds() {List<StoreProduct> storeProducts = storeProductMapper.selectBatchIds(Arrays.asList(1, 2));for (StoreProduct storeProduct : storeProducts) {log.info("查询结果:{}", storeProduct);}}/*** 通过map查找*/@Testvoid 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*/@Testvoid test_selectOne() {QueryWrapper<StoreProduct> queryWrapper = new QueryWrapper<>();queryWrapper.eq("id", 999);StoreProduct storeProduct = storeProductMapper.selectOne(queryWrapper);log.info("查询结果:{}", storeProduct);}/*** 按照条件查询count总数*/@Testvoid test_selectCount() {QueryWrapper<StoreProduct> queryWrapper = new QueryWrapper<>();queryWrapper.eq("store_name", "新款智能手机");Long count = storeProductMapper.selectCount(queryWrapper);log.info("查询结果有:{} 条", count);}/*** 列表查询*/@Testvoid 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*/@Testvoid 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);}}/*** 分页查询*/@Testvoid 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;/*** 测试设置单个键值对*/@Testvoid testSetValue() {stringRedisTemplate.opsForValue().set("testKey", "testValue");String value = stringRedisTemplate.opsForValue().get("testKey");log.info("设置并获取单个键值对,值为: {}", value);}/*** 测试设置带有过期时间的键值对  10秒过期*/@Testvoid 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);}/*** 测试获取单个键的值*/@Testvoid testGetValue() {stringRedisTemplate.opsForValue().set("existingKey", "existingValue");String value = stringRedisTemplate.opsForValue().get("existingKey");log.info("获取单个键的值,值为: {}", value);}/*** 测试删除单个键*/@Testvoid testDeleteKey() {stringRedisTemplate.opsForValue().set("toDeleteKey", "toDeleteValue");Boolean result = stringRedisTemplate.delete("toDeleteKey");log.info("删除单个键,结果: {}", result);}/*** 测试批量删除键*/@Testvoid testDeleteKeys() {stringRedisTemplate.opsForValue().set("key1", "value1");stringRedisTemplate.opsForValue().set("key2", "value2");Long deletedCount = stringRedisTemplate.delete(Arrays.asList("key1", "key2"));log.info("批量删除键,删除数量: {}", deletedCount);}/*** 测试设置哈希表*/@Testvoid 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);}/*** 测试获取哈希表中的单个字段值*/@Testvoid testGetHashField() {stringRedisTemplate.opsForHash().put("testHash", "field1", "value1");Object value = stringRedisTemplate.opsForHash().get("testHash", "field1");log.info("获取哈希表中的单个字段值,值为: {}", value);}/*** 测试获取哈希表的所有字段和值*/@Testvoid 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);}/*** 测试向列表左侧插入元素*/@Testvoid testLeftPushToList() {stringRedisTemplate.opsForList().leftPush("testList", "element1");stringRedisTemplate.opsForList().leftPush("testList", "element2");List<String> list = stringRedisTemplate.opsForList().range("testList", 0, -1);log.info("向列表左侧插入元素,列表内容: {}", list);}/*** 测试从列表右侧弹出元素*/@Testvoid testRightPopFromList() {stringRedisTemplate.opsForList().leftPush("testList", "element1");stringRedisTemplate.opsForList().leftPush("testList", "element2");String poppedElement = stringRedisTemplate.opsForList().rightPop("testList");log.info("从列表右侧弹出元素,弹出元素: {}", poppedElement);}/*** 测试向集合中添加元素*/@Testvoid testAddToSet() {stringRedisTemplate.opsForSet().add("testSet", "element1", "element2");Set<String> set = stringRedisTemplate.opsForSet().members("testSet");log.info("向集合中添加元素,集合内容: {}", set);}/*** 测试从集合中移除元素*/@Testvoid testRemoveFromSet() {stringRedisTemplate.opsForSet().add("testSet", "element1", "element2");Long removedCount = stringRedisTemplate.opsForSet().remove("testSet", "element1");log.info("从集合中移除元素,移除数量: {}", removedCount);}/*** 测试向有序集合中添加元素*/@Testvoid 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);}/*** 测试从有序集合中移除元素*/@Testvoid 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 {@Beanpublic 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 {@Resourceprivate 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相同时,内容会进行覆盖*/@Testvoid test_insert() {MongoStoreProduct mongoStoreProduct = getStoreProduct(1).get(0);MongoStoreProduct save = storeProductMongoRepository.save(mongoStoreProduct);log.info("插入单条数据,结果: {}", save);}/*** 插入多条数据  id相同时,内容会进行覆盖*/@Testvoid test_insertMultiple() {List<MongoStoreProduct> storeProduct = getStoreProduct(3);List<MongoStoreProduct> mongoStoreProducts = storeProductMongoRepository.saveAll(storeProduct);mongoStoreProducts.forEach(product -> log.info("插入多条数据,结果: {}", product));}/*** 根据 ID 查询单条数据*/@Testvoid test_findById() {Optional<MongoStoreProduct> mongoStoreProductOpt = storeProductMongoRepository.findById(String.valueOf(999));if (mongoStoreProductOpt.isPresent()) {log.info("根据 ID 查询单条数据,结果: {}", mongoStoreProductOpt.get());} else {log.info("未找到对应 ID 的数据");}}/*** 查询所有数据*/@Testvoid test_findAll() {Iterable<MongoStoreProduct> mongoStoreProducts = storeProductMongoRepository.findAll();mongoStoreProducts.forEach(product -> log.info("查询所有数据,结果: {}", product));}/*** 根据 ID 删除单条数据*/@Testvoid test_deleteById() {storeProductMongoRepository.deleteById(String.valueOf(999));log.info("根据 ID 删除单条数据,删除完成");}/*** 删除所有数据*/@Testvoid test_deleteAll() {storeProductMongoRepository.deleteAll();log.info("删除所有数据,删除完成");}/*** 分页查询数据*/@Testvoid 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, 总页数: 12productPage.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:9200username:password:read-timeout: 120s
es:storeProduct:indexName: store_product_info_v2pageSize: 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;}/*** 插入单条文档*/@Testvoid testInsertDocument() {StoreProductEsDTO product = generateMockProduct("991");StoreProductEsDTO savedProduct = elasticsearchRestTemplate.save(product);log.info("插入文档结果: {}", savedProduct);}/*** 批量插入文档*/@Testvoid testBulkInsertDocuments() {List<StoreProductEsDTO> products = Arrays.asList(generateMockProduct("992"), generateMockProduct("993"));Iterable<StoreProductEsDTO> savedProducts = elasticsearchRestTemplate.save(products);savedProducts.forEach(product -> log.info("批量插入文档结果: {}", product));}/*** 根据 ID 删除文档*/@Testvoid testDeleteDocument() {String id = "997";elasticsearchRestTemplate.delete(id, StoreProductEsDTO.class);log.info("删除 ID 为 {} 的文档", id);}/*** 根据 ID 更新文档*/@Testvoid testUpdateDocument() {StoreProductEsDTO product = generateMockProduct("994");product.setStoreName("更新后的测试店铺商品");StoreProductEsDTO updatedProduct = elasticsearchRestTemplate.save(product);log.info("更新文档结果: {}", updatedProduct);}/*** 查询单条文档*/@Testvoid testSearchSingleDocument() {String id = "992";StoreProductEsDTO product = elasticsearchRestTemplate.get(id, StoreProductEsDTO.class);if (product != null) {log.info("查询到的文档: {}", product);} else {log.info("未查询到 ID 为 {} 的文档", id);}}/*** 查询所有文档*/@Testvoid testSearchAllDocuments() {Query query = new CriteriaQuery(new Criteria());SearchHits<StoreProductEsDTO> searchHits = elasticsearchRestTemplate.search(query, StoreProductEsDTO.class);searchHits.forEach(hit -> log.info("查询到的文档: {}", hit.getContent()));}/*** 分页查询文档*/@Testvoid 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());}}/*** 根据条件查询文档*/@Testvoid 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 {@Resourceprivate StoreProductMapper storeProductMapper;@Resource(name = "stringRedisTemplate")private StringRedisTemplate stringRedisTemplate;@Resource(name = "elasticsearchRestTemplate")private ElasticsearchRestTemplate elasticsearchRestTemplate;@Resourceprivate 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;@Overridepublic void insertData(StoreProductRequest storeProductRequest) {// 1.插入mysqlStoreProduct 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.插入redisstringRedisTemplate.opsForValue().set(REDIS_KEY + storeProduct.getId(), JacksonUtils.jsonEncode(storeProduct));log.warn("数据已经插入redis数据库:{}", JacksonUtils.jsonEncode(storeProduct));// 3.插入mongoMongoStoreProduct 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.插入esStoreProductEsDTO 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. 推荐系统(基于用户行为的实时推荐)

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

在这里插入图片描述

相关文章:

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

常见中间件api操作及性能比较 ☝️ MySQL crud操作✌️ maven依赖✌️ 配置✌️ 定义实体类✌️ 常用api ☝️ Redis crud操作✌️ maven依赖✌️ 配置✌️ 常用api ☝️ MongoDB crud操作✌️ maven依赖✌️ 配置文件✌️ 定义实体类✌️ MongoDB常用api ☝️ ES crud操作 ⭐️…...

UE_C++ —— Gameplay Classes

目录 一&#xff0c;Adding Classes 二&#xff0c;Class Headers Class Declaration Class Specifiers Metadata Specifiers 三&#xff0c;Class Implementation Class Constructor 引擎中每个游戏类都由一个类头文件&#xff08;.h&#xff09;和一个类源文件&#x…...

windows上vscode cmake工程搭建

安装vscode插件&#xff1a; 1.按装fastc&#xff08;主要是安装MinGW\mingw64比较方便&#xff09; 2.安装C&#xff0c;cmake&#xff0c;cmake tools插件 3.准备工作完成之后&#xff0c;按F1&#xff0c;选择cmake:Quick Start就可以创建一个cmake工程。 4.设置Cmake: G…...

软件测试:1、单元测试

1. 单元测试的基本概念 单元&#xff08;Unit&#xff09;&#xff1a;软件系统的基本组成单位&#xff0c;可以是函数、模块、方法或类。 单元测试&#xff08;Unit Testing&#xff09;&#xff1a;对软件单元进行的测试&#xff0c;验证代码的正确性、规范性、安全性和性能…...

如果后台的Long类型的数据返回是null,那么Android客户端的数据bean的kotlin的Long类型的字段接受到数据后是null空指针吗?

如果后台的Long类型的数据返回是null&#xff0c;那么Android客户端的数据bean的kotlin的Long类型的字段接受到数据后是null空指针吗&#xff1f; DeepSeek R1 思考 35 秒 思考过程 好的&#xff0c;用户的问题是关于在Android客户端使用Kotlin处理后台返回的Long类型数据为n…...

Vue3 打造 Windows 桌面个性高效组件工具

软件介绍 Widgets 这款基于 Vue3 构建的开源 Windows 桌面小部件工具超实用。 其多样化组件库涵盖超 20 种&#xff0c;从倒计时、打工进度等实用工具&#xff0c;到抖音热榜等实时资讯组件应有尽有&#xff0c;各组件独立运行&#xff0c;满足多场景需求。 高度自定义布局支持…...

学习笔记-沁恒第四讲-米醋

一&#xff0c; 语音模块&#xff1a;数据包发送 刷卡模块&#xff1a;数据包接收 AS608&#xff1a;数据包发送接收 二&#xff0c;第三讲文件夹改成第四讲&#xff0c;工程也改成第四讲 三&#xff0c;目前在内存里面。保存新值&#xff0c;掉电会丢失 u8 password[6]{1,…...

epoll_event的概念和使用案例

epoll_event 是 Linux 下 epoll I/O 多路复用机制的核心数据结构&#xff0c;用于描述文件描述符&#xff08;File Descriptor, FD&#xff09;上发生的事件及其关联的用户数据。通过 epoll&#xff0c;可以高效地监控多个文件描述符的状态变化&#xff08;如可读、可写、错误等…...

容器和虚拟机选择对比

1. 概述 如果主要需求是学习和测试 Ubuntu 下的命令行工具或服务型应用&#xff0c;推荐使用 Docker Docker 更轻量、更高效&#xff0c;适合快速搭建和销毁环境。 启用 WSL 2&#xff0c;Docker Desktop 是一个非常好的选择。 如果需要完整的桌面环境或进行复杂的系统级开…...

C++17中std::chrono::duration和std::chrono::time_point的舍入函数

文章目录 1. std::chrono::duration的舍入函数1.1 floor1.2 ceil1.3 round 2. std::chrono::time_point的舍入函数2.1 示例 3. 舍入函数的应用场景3.1 时间测量3.2 数据记录3.3 时间同步 4. 总结 在C17中&#xff0c; std::chrono库提供了一组强大的时间处理工具&#xff0c;包…...

基于SpringBoot的线上汽车租赁系统的设计与实现(源码+SQL脚本+LW+部署讲解等)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…...

基于Dancing Links的精确覆盖算法(解决NP难问题)和量子计算机模拟中的Shor算法(涉及数论与量子叠加态模拟)

一、Dancing Links算法实现数独求解(NP难问题) 算法方案 数独可转化为精确覆盖问题,使用Knuth提出的DLX算法实现高效求解。该算法通过双向十字循环链表实现快速回溯,时间复杂度可达O(n^k)(k为常数) #include <iostream> #include <vector> #include <c…...

体育品牌排行榜前十名:MLB·棒球1号位

MLB是一个融合了棒球文化与街头时尚元素的潮流运动品牌。以下是对该品牌的详细介绍&#xff1a; 一、品牌背景 • 全称&#xff1a;MLB全称是Major League Baseball&#xff0c;即美国职业棒球大联盟。不过&#xff0c;作为品牌的MLB并非由美国职业棒球大联盟直接运营&#x…...

Java网络编程封装

系列文章目录 Java知识点 文章目录 系列文章目录&#x1f449;前言&#x1f449;一、封装的目标&#x1f449;二、套接字层封装&#x1f449;壁纸分享&#x1f449;总结 &#x1f449;前言 Java 网络编程封装原理主要围绕着将底层的网络通信细节隐藏起来&#xff0c;提供简洁…...

数字内容体验标杆案例解析

内容概要 在数字化转型浪潮中&#xff0c;数字内容体验正成为企业构建核心竞争力的关键抓手。本文通过拆解金融、零售、文旅等领域的标杆案例&#xff0c;系统分析沉浸式设计与智能交互系统的技术融合路径&#xff0c;揭示头部企业如何通过XR技术、实时数据可视化及场景化内容…...

区块链相关方法-PEST分析

一、定义:一种用于分析企业外部宏观环境的工具。PEST 这四个字母分别代表政治&#xff08;Political&#xff09;、经济&#xff08;Economic&#xff09;、社会&#xff08;Social&#xff09;和技术&#xff08;Technological&#xff09;。这种分析方法帮助企业或组织了解宏…...

Dify安装教程:Linux系统本地化安装部署Dify详细教程

1. 本地部署 Dify 应用开发平台 环境:Ubuntu(24.10) docker-ce docker compose 安装 克隆 Dify 源代码至本地环境: git clone https://github.com/langgenius/dify.git 启动 Dify: cd dify/docker cp .env.example...

git使用-克隆远程项目、分支管理

文章目录 克隆远程项目到本地1. 远程找到需要克隆的项目&#xff0c;复制ssh地址2. idea开启git版本控制&#xff08;如果已经开了&#xff0c;忽略此步骤&#xff09;3. clone远端项目4. 克隆完成 分支管理1. 新建分支2. 切换分支3. 合并分支4. 储存变化 克隆远程项目到本地 …...

QT 引入Quazip和Zlib源码工程到项目中,无需编译成库,跨平台,压缩进度

前言 最近在做项目时遇到一个需求&#xff0c;需要将升级的文件压缩成zip&#xff0c;再进行传输&#xff1b; 通过网络调研&#xff0c;有许多方式可以实现&#xff0c;例如QT私有模块的ZipReader、QZipWriter&#xff1b;或者第三方库zlib或者libzip或者quazip等&#xff1…...

SQLMesh 系列教程8- 详解 seed 模型

在数据分析和建模过程中&#xff0c;外部模型&#xff08;External Models&#xff09;在 SQLMesh 中扮演着重要角色。外部模型允许用户引用外部数据源或现有数据库表&#xff0c;从而实现灵活的数据整合和分析。本文将介绍外部模型的定义、生成方法&#xff08;包括使用 CLI 和…...

oracle apex post接口

日常记录 使用到了apex_json方式接收 、、、1 首先&#xff0c;接口通过body传递过来&#xff0c;成功接收到&#xff0c; 数据格式为 JSON_OBJECT_T l_json : JSON_OBJECT_T.parse(:body); 这里我用参数接收到 然后 里面是包含了 "data" 我用 继续接收到这个 l…...

复制所绑定元素文本的vue自定义指令

最近写了一个复制所绑定元素文本的vue自定义指令&#xff0c;给大家分享一下。 import { ElMessage } from element-plus// data-* 属性名 const dataCopyBtnTextAttribute data-copy-btn-text // 复制按钮的class&#xff0c;结合项目实际进行设置 const copyBtnClass icon…...

若依-@Excel新增注解numberFormat

Excel注解中原本的scale会四舍五入小数&#xff0c;导致进度丢失 想要的效果 显示的时候保留两个小数真正的数值是保留之前的数值 还原过程 若以中有一個專門的工具类&#xff0c;用来处理excel的 找到EXCEL导出方法exportExcel()找到writeSheet,写表格的方法找到填充数据的方法…...

内容中台重构智能服务:人工智能技术驱动精准决策

内容概要 现代企业数字化转型进程中&#xff0c;内容中台与人工智能技术的深度融合正在重构智能服务的基础架构。通过整合自然语言处理、知识图谱构建与深度学习算法三大技术模块&#xff0c;该架构实现了从数据采集到决策输出的全链路智能化。在数据层&#xff0c;系统可对接…...

网络安全:DeepSeek已经在自动的挖掘漏洞

大家好,我是AI拉呱,一个专注于人工智领域与网络安全方面的博主,现任资深算法研究员一职,兼职硕士研究生导师;热爱机器学习和深度学习算法应用,深耕大语言模型微调、量化、私域部署。曾获多次获得AI竞赛大奖,拥有多项发明专利和学术论文。对于AI算法有自己独特见解和经验…...

C#从入门到精通(34)—如何防止winform程序被同时打开多次

前言&#xff1a; 大家好&#xff0c;我是上位机马工&#xff0c;硕士毕业4年年入40万&#xff0c;目前在一家自动化公司担任软件经理&#xff0c;从事C#上位机软件开发8年以上&#xff01;我们在开发上位机软件的过程中&#xff0c;评判一个人软件写的好不好&#xff0c;有一…...

Linux系统使用Docker部署Geoserver并做数据挂载进行地图服务的发布和游览

文章目录 1、前提环境2、拉取geoserver镜像3、创建数据挂载目录4、 运行容器5、 测试使用&#xff08;发布shp数据为服务&#xff09;5.1、创建工作区5.2、添加数据存储5.3、发布图层5.4、服务游览 1、前提环境 部署环境&#xff1a;Linux&#xff0c;Centos7 &#xff0c;Doc…...

STM32-温湿度上传OneNET项目

一、项目需求 使用 ESP8266 连接 OneNET 云平台&#xff0c;并通过 MQTT 协议上传 DHT11 获取的温湿度值。 二、项目框图 三、DHT11工作原理 参考于良许嵌入式手把手教你玩转DHT11&#xff08;原理驱动&#xff09; | 良许嵌入式 3.1 正常工作验证 #​ 上电后&#xff…...

HTML 中的 Canvas 样式设置全解

在 HTML5 中&#xff0c;<canvas> 元素提供了一个强大的绘图接口&#xff0c;允许开发者通过 JavaScript 实现各种图形和动画效果。为了充分利用 <canvas> 的功能&#xff0c;理解其样式设置是至关重要的。本文将详细介绍如何在 HTML 中设置 <canvas> 的各种…...

【Java】File 类

目录 File 类File 类构造方法常见成员方法判断与获取创建与删除获取并遍历 File 类 File 对象表示一个路径&#xff0c;可以是文件的路径&#xff0c;也可以是文件夹的路径 这个路径可以是存在的&#xff0c;也允许是不存在的 绝对路径和相对路径的区别&#xff1a; 绝对路径…...

SAP S4HANA Administration (Mark Mergaerts Bert Vanstechelman)

SAP S4HANA Administration (Mark Mergaerts Bert Vanstechelman)...

特征提取:如何从不同模态中获取有效信息?

在多模态学习中,特征提取是一个至关重要的过程。它是将原始数据(如文本、图像、视频和语音等)转化为机器能够理解和处理的特征的核心步骤。不同于传统的单一模态任务,在多模态学习中,如何有效地从每种模态中提取出有意义的信息并进行融合,直接影响到最终模型的性能和准确…...

2025年-G14-Lc88-278.第一个坏版本 -java版

1.题目描述 第一个坏版本 你是一名产品经理&#xff0c;目前领导一个团队开发新产品。不幸的是&#xff0c;你产品的最新版本未通过质量检查。由于每个版本都是基于前一个版本开发的&#xff0c;所以坏版本之后的所有版本也都是坏的。假设你有 n 个版本 [1, 2, …, n]&#xff…...

计算机网络————(一)HTTP讲解

基础内容分类 从TCP/IP协议栈为依托&#xff0c;由上至下、从应用层到基础设施介绍协议。 1.应用层&#xff1a; HTTP/1.1 Websocket HTTP/2.0 2.应用层的安全基础设施 LTS/SSL 3.传输层 TCP 4.网络层及数据链路层 IP层和以太网 HTTP协议 网络页面形成基本 流程&#xff1a…...

大语言模型架构:从基础到进阶,如何理解和演变

引言 你可能听说过像 ChatGPT 这样的 AI 模型&#xff0c;它们能够理解并生成自然语言文本。这些模型的背后有着复杂的架构和技术&#xff0c;但如果你了解这些架构&#xff0c;就能明白它们是如何工作的。今天&#xff0c;我们将用简单的语言&#xff0c;逐步介绍大语言模型的…...

科普mfc100.dll丢失怎么办?有没有简单的方法修复mfc100.dll文件

当电脑频繁弹窗提示“mfc100.dll丢失”或应用程序突然闪退时&#xff0c;这个看似普通的系统文件已成为影响用户体验的核心痛点。作为微软基础类库&#xff08;MFC&#xff09;的核心组件&#xff0c;mfc100.dll直接关联着Visual Studio 2010开发的大量软件运行命脉。从工业设计…...

什么是虚拟内存?它的作用是什么?

虚拟内存概念 虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存&#xff08;一个连续完整的地址空间&#xff09;。但是实际上&#xff0c;它通常是被分隔成多个物理内存碎片&#xff0c;还有部分暂时存储在外部磁盘存储器上&#xff0c;在…...

SAP任命Simon Davies为亚太区总裁,领导重组后的亚太地区业务

2025年2月19日&#xff0c;SAP宣布任命Simon Davies为新任亚太区总裁&#xff0c;负责领导公司重组后的亚太地区业务。Davies将常驻新加坡&#xff0c;全面负责SAP在亚太地区的战略、运营、人员管理、销售、服务、合作伙伴关系及盈利能力。他的职责范围涵盖韩国、澳大利亚、新西…...

Markdown使用方法文字版解读

[TOC](这里写自定义目录标题) # Markdown编辑器 你好&#xff01; 这是你第一次使用 **Markdown编辑器** 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章&#xff0c;了解一下Markdown的基本语法知识。 ## 新的改变 我们对Markdown编辑器进行了…...

QT移植,交叉编译至泰山派RK3566开发板,.pro文件解析

备注 交叉编译到开发板,会有各种奇奇怪怪的问题, 直接命令行安装 QTCREATOR和 QtBase,就在板子上搞个桌面系统编译,最后把桌面关掉。 配置文件解析 配置文件丢这里,后面有空整理下。 说下大概的注意点, 安装路径(qtcreator远程部署的路径)、 动态库路径和…...

Liunx(CentOS-6-x86_64)系统安装MySql(5.6.50)

一&#xff1a;安装Liunx&#xff08;CentOS-6-x86_64&#xff09; 安装Liunx&#xff08;CentOS-6-x86_64&#xff09; 二&#xff1a;下载MySql&#xff08;5.6.50&#xff09; MySql下载官网 二&#xff1a;安装MySql 2.1 将mysql上传到Liunx 文件地址 /usr/local/ 2…...

使用 deepseek实现 go语言,读取文本文件的功能,要求支持 ascii,utf-8 等多种格式自适应

使用 deepseek实现 go语言&#xff0c;读取文本文件的功能&#xff0c;要求支持 ascii&#xff0c;utf-8 等多种格式自适应我要用 chatgpt&#xff0c;也问过&#xff0c;但是比 deepseek 还是差一个级别&#xff0c;具体如下&#xff1a; package mainimport ("bufio&qu…...

当电脑上有几个python版本Vscode选择特定版本python

查看当前vscode用的python版本命令 Import sys print(sys.version) 修改VSCODE解释器 打开 VSCode。 按下 CtrlShiftP打开命令面板。 输入 Python: Select Interpreter 并选择它。 从弹出的列表中选择你安装的 Python 解释器。如果你有多个 Python 版本&#xff08;例如…...

大一计算机的自学总结:一维差分与等差数列差分

前言 差分和前缀和一样&#xff0c;也是很重要的基础算法。 一、一维差分 1.内容 当给出一个数组&#xff0c;每次操作让数组某个区间上的值全增加&#xff0c;最后要求返回整个数组的结果。若是一次一次去遍历&#xff0c;时间复杂度肯定很难看。差分可以做到在时间复杂度…...

学习dify第一天:整体架构分析

使用marscode AI插件 从分析最火的dify开始学习使用ai提速首先安装插件功能快捷键使用这个工具如何学习项目首先学习dify那就先上官网看文档开始从docker构建脚本学起看下docker-compose.yamldify里边服务的组件现在看api和web模块api项目根目录有Makefile文件,用于构建api和w…...

C语言的内存分配:malloc和free

使用库函数分配和管理内存。在运行时&#xff0c;分配更多的内存给程序使用&#xff0c;主要工具是malloc函数&#xff0c;这个函数接受一个参数&#xff1a;所需要要的内存字节数。malloc函数会找到合适的空闲内存块&#xff0c;这样的内存是匿名的&#xff0c;即malloc分配了…...

为什么 JSON 不能序列化 set

为什么 JSON 不能序列化 set JSON&#xff08;JavaScript Object Notation&#xff09;作为一种广泛使用的数据交换格式&#xff0c;虽然功能强大&#xff0c;但它无法直接序列化 set 类型。本文将从设计原理、实现限制和实际应用角度&#xff0c;探讨这一现象的原因及解决方案…...

XUnity.AutoTranslator-Gemini——调用Google的Gemini API, 实现Unity游戏中日文文本的自动翻译

XunityAutoTranslator-Gemini-API 本项目是一个使用 Flask 框架和 Google Gemini GenAI 模型构建的 Web API 服务&#xff0c;用于将日文unity游戏文本翻译成简体中文。 日文游戏文本AI翻译API (基于Google Gemini) 本项目是一个使用 Flask 框架和 Google Gemini GenAI 模型…...

深刻理解构件生产线

我们可以将构件生产线类比为软件开发中的一种高效、模块化的构建方式。下面&#xff0c;我将结合Java编程的概念来详细讲解构件生产线的含义和实现方式。 一、构件生产线的概念 构件生产线在软件开发中&#xff0c;类似于工厂中的自动化生产线&#xff0c;它通过将复杂的软件…...

nlp 自然语言处理+bert model +问答系统 question answer system(python 完整代码)

pre-trained bert model 预训练好的Bert模型 本地实现问答系统 用这条命令将bert下载到本地: model.save_pretrained("path/to/model") 也有参考这篇文章 https://colab.research.google.com/drive/1uSlWtJdZmLrI3FCNIlUHFxwAJiSu2J0-#scrollTo=AaweLnNXGhTY …...