常見中間件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 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. 推薦系統(基于用戶行為的實時推薦) |
創作不易,不妨點贊、收藏、關注支持一下,各位的支持就是我創作的最大動力??