常见中间件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、@Accessors
是lombok
注解,@Data
自动生成实体类的Getter、Setter、无参构造、有参构造
等,@EqualsAndHashCode
生成自动生成 equals
和 hashCode
方法,@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));}
}
分页返回结果:
注意点:
- mongoDB在save或者saveAll时,如果id已经存在,则会对改id数据进行覆盖
- 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
使用
- 下载地址:https://github.com/360EntSecGroup-Skylar/ElasticHD/releases
- 执行:直接双击
ElasticHD.exe
。//或./ElasticHD -p 127.0.0.1:980 - 启动访问:http://localhost:9800
这个 Dashboard
的UI设计非常酷炫:
输入es连接,点connect登录:
如果有账号密码,使用
http://username:password@host:port
例如:
http://elastic:elastic@http://127.0.0.1:9200
数据搜索直观易使用:
索引列表看得比较清楚:
这个 SQL查询语句
转 ES
的Json查询格式
的小工具挺厉害的:
✌️ 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 espace">com.db.test.entity.dto.StoreProductEsDTO;
import espace">lombok.extern.slf4j.Slf4j;
import espace">org.junit.jupiter.api.Test;
import espace">org.springframework.boot.test.context.SpringBootTest;
import espace">org.springframework.data.domain.PageRequest;
import espace">org.springframework.data.domain.Pageable;
import espace">org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import espace">org.springframework.data.elasticsearch.core.SearchHits;
import espace">org.springframework.data.elasticsearch.core.query.*;import espace">javax.annotation.Resource;
import espace">java.util.Arrays;
import espace">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<espace">org.springframework.data.elasticsearch.core.SearchHit<StoreProductEsDTO>> searchHitList = searchHits.getSearchHits();for (espace">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. 推荐系统(基于用户行为的实时推荐) |
创作不易,不妨点赞、收藏、关注支持一下,各位的支持就是我创作的最大动力❤️