常见中间件api操作及性能比较
- ☝️ MySQL crud操作
- ✌️ maven依赖
- ✌️ 配置
- ✌️ 定义实体类
- ✌️ 常用api
- ☝️ Redis crud操作
- ✌️ maven依赖
- ✌️ 配置
- ✌️ 常用api
- ☝️ MongoDB crud操作
- ✌️ maven依赖
- ✌️ 配置文件
- ✌️ 定义实体类
- ✌️ MongoDB常用api
- ☝️ ES crud操作 ⭐️⭐️⭐️
- ✌️ 前期准备
- ✌️ maven依赖
- ⭐️ tips
- ✌️ 配置文件
- ✌️ 定义实体类
- ✌️ ES常用api
- ☝️ 性能比较
- ✌️ 模拟创建数据接口
- ✌️ 查询数据接口
本文汇总常见中间件的api操作及性能对比,主要涉及MySQL、Redis、Mongo、Es,这篇文章默认已经安装配置好相应的中间件
关于MongoDB的安装配置可参考文章:《【MongoDB】一问带你深入理解什么是MongDB,MongoDB超超详细保姆级教程》
Es安装配置可参考:《【ELK】window下ELK的安装与部署》
☝️ MySQL crud操作
mysql是目前最常用的关系型数据库,网上有很多资料,这里大致简单过一下主流的Mybatis-Plus用法,不展开细说
✌️ maven依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--添加 Alibaba 数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.20</version>
</dependency>
<!--访问mysql-->
<!--JDBC-->
<!-- MySql 5.5 Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.24</version>
</dependency>
✌️ 配置
ip、port、数据库名称,账户密码换成自己的
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/csdn?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimeZone=GMT+8
username: root
password: 123456
✌️ 定义实体类
实体类中,@Data、@EqualsAndHashCode、@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 {
@Resource
private StoreProductMapper storeProductMapper;
/**
* @param num 生成num条模拟数据
* @return
*/
private static List<StoreProduct> getStoreProduct(Integer num) {
List<StoreProduct> result = new ArrayList<>();
StoreProduct storeProduct = new StoreProduct();
for (int i = 0; i < num; i++) {
storeProduct.setId(999 + i).
setImage("https://www.baidu.com/img/bd_logo1.png").
setSliderImage("https://www.baidu.com/img/bd_logo1.png")
.setStoreName("测试商品" + i)
.setStoreInfo("测试商品")
.setKeyword("测试商品")
.setCateId("1")
.setUnitName("件")
.setSort(1)
.setIsHot(true)
.setIsBenefit(true)
.setIsBest(true)
.setIsNew(true)
.setIsGood(true)
.setGiveIntegral(1)
.setIsSub(true)
.setFicti(1)
.setTempId(1)
.setSpecType(true)
.setActivity("{\"test\":\"test\"}")
.setAttr("{\"test\":\"test\"}")
.setAttrValue("{\"test\":\"test\"}")
.setContent("{\"test\":\"test\"}")
.setCouponIds("{\"test\":\"test\"}")
.setFlatPattern("{\"test\":\"test\"}");
result.add(storeProduct);
}
return result;
}
/**
* 插入单条数据
*/
@Test
void test_insert() {
StoreProduct storeProduct = getStoreProduct(1).get(0);
storeProductMapper.insert(storeProduct);
}
/**
* 按照id删除
*/
@Test
void test_deleteById() {
storeProductMapper.deleteById(999);
}
/**
* 多个条件删除
*/
@Test
void test_deleteByMap() {
Map<String, Object> columnMap = new HashMap<>();
// 添加多个条件
columnMap.put("id", 999);
columnMap.put("store_name", "测试商品");
columnMap.put("is_hot", true);
storeProductMapper.deleteByMap(columnMap);
}
/**
* 构建wrapper语句删除
*/
@Test
void test_deleteByWrapper() {
// 创建 QueryWrapper 对象
QueryWrapper<StoreProduct> queryWrapper = new QueryWrapper<>();
// 添加删除条件,例如删除 id 为 "999" 的记录,并且storeName 为 "测试商品" 的记录
queryWrapper.eq("id", 999);
storeProductMapper.delete(queryWrapper);
}
/**
* 构建wrapper语句删除
*/
@Test
void test_deleteByLambdaWrapper() {
// 创建 LambdaQueryWrapper 对象
LambdaQueryWrapper<StoreProduct> queryWrapper = new LambdaQueryWrapper<>();
// 添加删除条件,例如删除 id 为 "999" 的记录,并且storeName 为 "测试商品" 的记录
queryWrapper.eq(StoreProduct::getId, 999);
storeProductMapper.delete(queryWrapper);
}
/**
* 批量删除
*/
@Test
void test_deleteBatchIds() {
storeProductMapper.deleteBatchIds(Arrays.asList(999, 1000));
}
/**
* 更新数据
*/
@Test
void test_updateById() {
StoreProduct storeProduct = getStoreProduct(1).get(0);
storeProduct.setStoreName("商品名字更新啦~");
storeProductMapper.updateById(storeProduct);
}
/**
* 构建wrapper语句更新
*/
@Test
void test_updateByWrapper() {
// 创建 UpdateWrapper 对象
UpdateWrapper<StoreProduct> queryWrapper = new UpdateWrapper<>();
// 添加更新条件,例如更新 id 为 "999"
queryWrapper.eq("id", 999);
queryWrapper.set("store_name", "商品名字再次更新啦~");
storeProductMapper.update(null, queryWrapper);
}
/**
* 构建LambdaWrapper语句更新
*/
@Test
void test_updateByLambdaWrapper() {
// 创建 UpdateWrapper 对象
LambdaUpdateWrapper<StoreProduct> queryWrapper = new LambdaUpdateWrapper<>();
// 添加更新条件,例如更新 id 为 "999"
queryWrapper.eq(StoreProduct::getId, 999);
queryWrapper.set(StoreProduct::getStoreName, "商品名字再再次更新啦~");
storeProductMapper.update(null, queryWrapper);
}
/**
* 通过id查找
*/
@Test
void test_selectById() {
StoreProduct storeProduct = storeProductMapper.selectById(999);
log.info("查询结果:{}", storeProduct);
}
/**
* 通过id集合查找
*/
@Test
void test_selectBatchIds() {
List<StoreProduct> storeProducts = storeProductMapper.selectBatchIds(Arrays.asList(1, 2));
for (StoreProduct storeProduct : storeProducts) {
log.info("查询结果:{}", storeProduct);
}
}
/**
* 通过map查找
*/
@Test
void test_selectByMap() {
Map<String, Object> columnMap = new HashMap<>();
// 添加多个条件
columnMap.put("store_info", "测试商品");
columnMap.put("is_hot", true);
List<StoreProduct> storeProducts = storeProductMapper.selectByMap(columnMap);
for (StoreProduct storeProduct : storeProducts) {
log.info("查询结果:{}", storeProduct);
}
}
/**
* 根据条件查一个
*
* 注意,如果有多个满足条件的数据,代码会报错:One record is expected, but the query result is multiple records
*/
@Test
void test_selectOne() {
QueryWrapper<StoreProduct> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("id", 999);
StoreProduct storeProduct = storeProductMapper.selectOne(queryWrapper);
log.info("查询结果:{}", storeProduct);
}
/**
* 按照条件查询count总数
*/
@Test
void test_selectCount() {
QueryWrapper<StoreProduct> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("store_name", "新款智能手机");
Long count = storeProductMapper.selectCount(queryWrapper);
log.info("查询结果有:{} 条", count);
}
/**
* 列表查询
*/
@Test
void test_selectList() {
QueryWrapper<StoreProduct> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("store_name", "新款智能手机");
List<StoreProduct> storeProducts = storeProductMapper.selectList(queryWrapper);
for (StoreProduct storeProduct : storeProducts) {
log.info("查询结果:{}", storeProduct);
}
}
/**
* 查询结果为map
*/
@Test
void test_selectMaps() {
QueryWrapper<StoreProduct> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("store_info", "测试商品");
List<Map<String, Object>> maps = storeProductMapper.selectMaps(queryWrapper);
for (Map<String, Object> map : maps) {
log.info("查询结果:{}", map);
}
}
/**
* 分页查询
*/
@Test
void test_selectPage() {
// 创建分页对象,指定当前页码和每页记录数
LambdaQueryWrapper<StoreProduct> lqw = new LambdaQueryWrapper<>();
lqw.eq(StoreProduct::getStoreName, "新款智能手机");
Page<StoreProduct> page = new Page<>(1, 10);
// 调用 selectPage 方法进行分页查询
IPage<StoreProduct> resultPage = storeProductMapper.selectPage(page, lqw);
log.info("当前页码:{},每页记录数:{},总页数:{},总记录数:{}", resultPage.getCurrent(), resultPage.getSize(),resultPage.getPages(), resultPage.getTotal());
for (StoreProduct storeProduct : resultPage.getRecords()) {
log.info("查询结果:{}", storeProduct);
}
}
}
代码中构建模拟数据方法getStoreProduct()
中用到了链式构建,Wrapper构建既能用普通Wrapper,也能用LambdaWrapper,例如查询中的QueryWrapper()
或者是LambdaQueryWrapper()
,test_selectOne
如果查询存在多个值,会抛出异常One record is expected, but the query result is multiple records
,源码还是非常简单,如下:
☝️ Redis crud操作
Redis是常见的缓存中间件,接下来看看redis的一些常见操作
✌️ maven依赖
<!-- Spring Boot Redis 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
✌️ 配置
ip、port、database,密码换成自己的
spring:
redis:
host: 127.0.0.1 #地址
port: 6379 #端口
password:
timeout: 30000 # 连接超时时间(毫秒)
database: 15 #默认数据库
jedis:
pool:
max-active: 200 # 连接池最大连接数(使用负值表示没有限制)
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制)
max-idle: 10 # 连接池中的最大空闲连接
min-idle: 0 # 连接池中的最小空闲连接
time-between-eviction-runs: -1 #逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
✌️ 常用api
TestRedis.java
@Slf4j
@SpringBootTest
public class TestRedis {
@Resource(name = "stringRedisTemplate")
private StringRedisTemplate stringRedisTemplate;
/**
* 测试设置单个键值对
*/
@Test
void testSetValue() {
stringRedisTemplate.opsForValue().set("testKey", "testValue");
String value = stringRedisTemplate.opsForValue().get("testKey");
log.info("设置并获取单个键值对,值为: {}", value);
}
/**
* 测试设置带有过期时间的键值对 10秒过期
*/
@Test
void testSetValueWithExpiration() {
stringRedisTemplate.opsForValue().set("expiringKey", "expiringValue", 10, TimeUnit.SECONDS);
String value = stringRedisTemplate.opsForValue().get("expiringKey");
log.info("设置带有过期时间的键值对,值为: {}", value);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
value = stringRedisTemplate.opsForValue().get("expiringKey");
log.info("过期时间已到,键值对已过期,值为: {}", value);
}
/**
* 测试获取单个键的值
*/
@Test
void testGetValue() {
stringRedisTemplate.opsForValue().set("existingKey", "existingValue");
String value = stringRedisTemplate.opsForValue().get("existingKey");
log.info("获取单个键的值,值为: {}", value);
}
/**
* 测试删除单个键
*/
@Test
void testDeleteKey() {
stringRedisTemplate.opsForValue().set("toDeleteKey", "toDeleteValue");
Boolean result = stringRedisTemplate.delete("toDeleteKey");
log.info("删除单个键,结果: {}", result);
}
/**
* 测试批量删除键
*/
@Test
void testDeleteKeys() {
stringRedisTemplate.opsForValue().set("key1", "value1");
stringRedisTemplate.opsForValue().set("key2", "value2");
Long deletedCount = stringRedisTemplate.delete(Arrays.asList("key1", "key2"));
log.info("批量删除键,删除数量: {}", deletedCount);
}
/**
* 测试设置哈希表
*/
@Test
void testSetHash() {
Map<String, String> hash = new HashMap<>();
hash.put("field1", "value1");
hash.put("field2", "value2");
stringRedisTemplate.opsForHash().putAll("testHash", hash);
Map<Object, Object> result = stringRedisTemplate.opsForHash().entries("testHash");
log.info("设置哈希表,结果: {}", result);
}
/**
* 测试获取哈希表中的单个字段值
*/
@Test
void testGetHashField() {
stringRedisTemplate.opsForHash().put("testHash", "field1", "value1");
Object value = stringRedisTemplate.opsForHash().get("testHash", "field1");
log.info("获取哈希表中的单个字段值,值为: {}", value);
}
/**
* 测试获取哈希表的所有字段和值
*/
@Test
void testGetAllHashFields() {
Map<String, String> hash = new HashMap<>();
hash.put("field1", "value1");
hash.put("field2", "value2");
stringRedisTemplate.opsForHash().putAll("testHash", hash);
Map<Object, Object> result = stringRedisTemplate.opsForHash().entries("testHash");
log.info("获取哈希表的所有字段和值,结果: {}", result);
}
/**
* 测试向列表左侧插入元素
*/
@Test
void testLeftPushToList() {
stringRedisTemplate.opsForList().leftPush("testList", "element1");
stringRedisTemplate.opsForList().leftPush("testList", "element2");
List<String> list = stringRedisTemplate.opsForList().range("testList", 0, -1);
log.info("向列表左侧插入元素,列表内容: {}", list);
}
/**
* 测试从列表右侧弹出元素
*/
@Test
void testRightPopFromList() {
stringRedisTemplate.opsForList().leftPush("testList", "element1");
stringRedisTemplate.opsForList().leftPush("testList", "element2");
String poppedElement = stringRedisTemplate.opsForList().rightPop("testList");
log.info("从列表右侧弹出元素,弹出元素: {}", poppedElement);
}
/**
* 测试向集合中添加元素
*/
@Test
void testAddToSet() {
stringRedisTemplate.opsForSet().add("testSet", "element1", "element2");
Set<String> set = stringRedisTemplate.opsForSet().members("testSet");
log.info("向集合中添加元素,集合内容: {}", set);
}
/**
* 测试从集合中移除元素
*/
@Test
void testRemoveFromSet() {
stringRedisTemplate.opsForSet().add("testSet", "element1", "element2");
Long removedCount = stringRedisTemplate.opsForSet().remove("testSet", "element1");
log.info("从集合中移除元素,移除数量: {}", removedCount);
}
/**
* 测试向有序集合中添加元素
*/
@Test
void testAddToZSet() {
stringRedisTemplate.opsForZSet().add("testZSet", "element1", 1.0);
stringRedisTemplate.opsForZSet().add("testZSet", "element2", 2.0);
Set<String> zSet = stringRedisTemplate.opsForZSet().range("testZSet", 0, -1);
log.info("向有序集合中添加元素,有序集合内容: {}", zSet);
}
/**
* 测试从有序集合中移除元素
*/
@Test
void testRemoveFromZSet() {
stringRedisTemplate.opsForZSet().add("testZSet", "element1", 1.0);
stringRedisTemplate.opsForZSet().add("testZSet", "element2", 2.0);
Long removedCount = stringRedisTemplate.opsForZSet().remove("testZSet", "element1");
log.info("从有序集合中移除元素,移除数量: {}", removedCount);
}
}
运行结果:
☝️ MongoDB crud操作
MongoDB是目前常用的高性能的分布式文件存储方案,下面看看他的api实现
✌️ maven依赖
<!-- mongodb连接驱动 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
✌️ 配置文件
我这里图省事,直接在config中写死了mongodb://127.0.0.1:27017/csdn
,可配置在yml文件中读取
@Configuration
public class MongoConfig {
@Bean
public MongoDatabaseFactory mongoDatabaseFactory() {
String connectionString = "mongodb://127.0.0.1:27017/csdn";
return new SimpleMongoClientDatabaseFactory(connectionString);
}
@Bean(name = "mongoTemplate")
public MongoTemplate mongoTemplate() {
return new MongoTemplate(mongoDatabaseFactory());
}
}
✌️ 定义实体类
代码中,@Data
还是lombok注解,和mysql一样,@Document
注解可以理解成映射行
如下图中:
图中的Mongo的集合(Collection)类比MySQL中的表名,Document类比表中的一行
那为什么一行在navicat中显示有那么多条数据呢?其实Mongo底层是BSON(Binary JSON)二进制存储格式,每个Document下面是一个大的json文件,样例如下:
{
"_id": "order123",
"orderDate": "2025-02-18",
"customer": {
"customerId": "cust456",
"name": "John Doe",
"email": "john.doe@example.com"
},
"items": [
{
"productId": "prod789",
"productName": "Smartphone",
"quantity": 2,
"price": 500
},
{
"productId": "prod012",
"productName": "Headphones",
"quantity": 1,
"price": 100
}
]
}
@Id
注解可以理解成主键,一个对象中只能有一个,可以生动赋值,也可以用默认值,默认值按照ObjectId来取值,包含了时间戳、机器标识、进程 ID 和随机数等信息
MongoStoreProduct.java
@Data
@Document("storeproductinfo")
public class MongoStoreProduct {
/**
* 文档的id使用ObjectId类型来封装,并且贴上@Id注解
*/
@Id
@Field("_id")
@JsonProperty("_id")
private String id;
/**
* 图片
*/
@Field("image")
@JsonProperty("image")
private String image;
/**
* 轮播图片
*/
@Field("sliderImage")
@JsonProperty("sliderImage")
private String sliderImage;
/**
* 店铺名称
*/
@Field("storeName")
@JsonProperty("storeName")
private String storeName;
/**
* 店铺信息
*/
@Field("storeInfo")
@JsonProperty("storeInfo")
private String storeInfo;
/**
* 关键词
*/
@Field("keyword")
@JsonProperty("keyword")
private String keyword;
/**
* 分类ID
*/
@Field("cateId")
@JsonProperty("cateId")
private String cateId;
/**
* 单位名称
*/
@Field("unitName")
@JsonProperty("unitName")
private String unitName;
/**
* 排序
*/
@Field("sort")
@JsonProperty("sort")
private Integer sort;
/**
* 是否热门
*/
@Field("isHot")
@JsonProperty("isHot")
private Boolean isHot;
/**
* 是否有优惠
*/
@Field("isBenefit")
@JsonProperty("isBenefit")
private Boolean isBenefit;
/**
* 是否精品
*/
@Field("isBest")
@JsonProperty("isBest")
private Boolean isBest;
/**
* 是否新品
*/
@Field("isNew")
@JsonProperty("isNew")
private Boolean isNew;
/**
* 是否好评
*/
@Field("isGood")
@JsonProperty("isGood")
private Boolean isGood;
/**
* 赠送积分
*/
@Field("giveIntegral")
@JsonProperty("giveIntegral")
private Integer giveIntegral;
/**
* 是否子店铺
*/
@Field("isSub")
@JsonProperty("isSub")
private Boolean isSub;
/**
* 虚拟销量
*/
@Field("ficti")
@JsonProperty("ficti")
private Integer ficti;
/**
* 模板ID
*/
@Field("tempId")
@JsonProperty("tempId")
private Integer tempId;
/**
* 规格类型
*/
@Field("specType")
@JsonProperty("specType")
private Boolean specType;
/**
* 活动
*/
@Field("activity")
@JsonProperty("activity")
private String activity;
/**
* 属性
*/
@Field("attr")
@JsonProperty("attr")
private String attr;
/**
* 属性值
*/
@Field("attrValue")
@JsonProperty("attrValue")
private String attrValue;
/**
* 内容
*/
@Field("content")
@JsonProperty("content")
private String content;
/**
* 优惠券ID列表
*/
@Field("couponIds")
@JsonProperty("couponIds")
private String couponIds;
/**
* 平铺模式
*/
@Field("flatPattern")
@JsonProperty("flatPattern")
private String flatPattern;
}
✌️ MongoDB常用api
TestMongoDB.java
@Slf4j
@SpringBootTest
public class TestMongoDB {
@Resource
private StoreProductMongoRepository storeProductMongoRepository;
/**
* 生成模拟数据
*
* @param num 生成的数量
* @return 模拟数据列表
*/
private List<MongoStoreProduct> getStoreProduct(Integer num) {
List<MongoStoreProduct> result = new ArrayList<>();
for (int i = 0; i < num; i++) {
MongoStoreProduct mongoStoreProduct = new MongoStoreProduct();
mongoStoreProduct.setId(String.valueOf(999 + i))
.setImage("https://www.baidu.com/img/bd_logo1.png")
.setSliderImage("https://www.baidu.com/img/bd_logo1.png")
.setStoreName("测试商品" + i)
.setStoreInfo("测试商品" + i)
.setKeyword("测试商品")
.setCateId("1")
.setUnitName("件")
.setSort(1)
.setIsHot(true)
.setIsBenefit(true)
.setIsBest(true)
.setIsNew(true)
.setIsGood(true)
.setGiveIntegral(1)
.setIsSub(true)
.setFicti(1)
.setTempId(1)
.setSpecType(true)
.setActivity("{\"test\":\"test\"}")
.setAttr("{\"test\":\"test\"}")
.setAttrValue("{\"test\":\"test\"}")
.setContent("{\"test\":\"test\"}")
.setCouponIds("{\"test\":\"test\"}")
.setFlatPattern("{\"test\":\"test\"}");
result.add(mongoStoreProduct);
}
return result;
}
/**
* 插入单条数据 id相同时,内容会进行覆盖
*/
@Test
void test_insert() {
MongoStoreProduct mongoStoreProduct = getStoreProduct(1).get(0);
MongoStoreProduct save = storeProductMongoRepository.save(mongoStoreProduct);
log.info("插入单条数据,结果: {}", save);
}
/**
* 插入多条数据 id相同时,内容会进行覆盖
*/
@Test
void test_insertMultiple() {
List<MongoStoreProduct> storeProduct = getStoreProduct(3);
List<MongoStoreProduct> mongoStoreProducts = storeProductMongoRepository.saveAll(storeProduct);
mongoStoreProducts.forEach(product -> log.info("插入多条数据,结果: {}", product));
}
/**
* 根据 ID 查询单条数据
*/
@Test
void test_findById() {
Optional<MongoStoreProduct> mongoStoreProductOpt = storeProductMongoRepository.findById(String.valueOf(999));
if (mongoStoreProductOpt.isPresent()) {
log.info("根据 ID 查询单条数据,结果: {}", mongoStoreProductOpt.get());
} else {
log.info("未找到对应 ID 的数据");
}
}
/**
* 查询所有数据
*/
@Test
void test_findAll() {
Iterable<MongoStoreProduct> mongoStoreProducts = storeProductMongoRepository.findAll();
mongoStoreProducts.forEach(product -> log.info("查询所有数据,结果: {}", product));
}
/**
* 根据 ID 删除单条数据
*/
@Test
void test_deleteById() {
storeProductMongoRepository.deleteById(String.valueOf(999));
log.info("根据 ID 删除单条数据,删除完成");
}
/**
* 删除所有数据
*/
@Test
void test_deleteAll() {
storeProductMongoRepository.deleteAll();
log.info("删除所有数据,删除完成");
}
/**
* 分页查询数据
*/
@Test
void test_findAllByPage() {
PageRequest pageRequest = PageRequest.of(0, 2, Sort.by(Sort.Direction.ASC, "id"));
Page<MongoStoreProduct> productPage = storeProductMongoRepository.findAll(pageRequest);
log.info("当前页码: {}, 每页记录数: {}, 总记录数: {}, 总页数: {}",
productPage.getNumber(), productPage.getSize(), productPage.getTotalElements(), productPage.getTotalPages());
// 当前页码: 0, 每页记录数: 2, 总记录数: 23, 总页数: 12
productPage.getContent().forEach(product -> log.info("分页查询数据,结果: {}", product));
}
}
分页返回结果:
注意点:
- 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:9200
username:
password:
read-timeout: 120s
es:
storeProduct:
indexName: store_product_info_v2
pageSize: 500
@Configuration
@ConfigurationProperties(prefix = "spring.elasticsearch.rest")
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {
@Value("${spring.elasticsearch.rest.uris}")
private String uris;
@Value("${spring.elasticsearch.rest.username}")
private String username;
@Value("${spring.elasticsearch.rest.password}")
private String password;
@Override
@Bean(name = "elasticsearchClient", destroyMethod = "close")
public RestHighLevelClient elasticsearchClient() {
ClientConfiguration configuration = ClientConfiguration.builder()
.connectedTo(uris)
.withBasicAuth(username, password)
.withConnectTimeout(Duration.ofSeconds(60))
.withSocketTimeout(Duration.ofSeconds(60))
.withHttpClientConfigurer(httpClientBuilder -> httpClientBuilder
.setDefaultIOReactorConfig(IOReactorConfig.custom().setSoKeepAlive(true).build())
.setKeepAliveStrategy((httpResponse, httpContext) -> 1000 * 60 * 3)
)
.build();
return RestClients.create(configuration).rest();
}
@Override
@Bean(name = {"elasticsearchRestTemplate"})
public ElasticsearchRestTemplate elasticsearchOperations(ElasticsearchConverter elasticsearchConverter,
@Qualifier("elasticsearchClient") RestHighLevelClient elasticsearchClient) {
return new ElasticsearchRestTemplate(elasticsearchClient, elasticsearchConverter);
}
}
✌️ 定义实体类
@Document(indexName = "store_product_info_v2")
注解绑定是es的索引,@Setting
是配置文件的目录,number_of_shards
是分片数,number_of_replicas
表示分片副本数,max_result_window
允许搜索最大值
{
"index": {
"number_of_shards": 1,
"number_of_replicas": 1,
"max_result_window": 100000
}
}
StoreProductEsDTO.java
@Data
@Document(indexName = "store_product_info_v2")
//@Document(indexName = "#{@StoreProductServiceImpl.getStoreProductEsIndexName()}")
@Setting(settingPath = "es/StoreProductSettings.json")
public class StoreProductEsDTO {
/**
* id
*/
@Id
@Field(type = FieldType.Keyword)
private String id;
/**
* 图片
*/
@Field(value = "image", type = FieldType.Text)
private String image;
/**
* 滑块图片
*/
@Field(value = "slider_image", type = FieldType.Text)
private String sliderImage;
/**
* 店铺名称
*/
@Field(value = "store_name", type = FieldType.Text)
private String storeName;
/**
* 店铺信息
*/
@Field(value = "store_info", type = FieldType.Text)
private String storeInfo;
/**
* 关键词
*/
@Field(value = "keyword", type = FieldType.Text)
private String keyword;
/**
* 分类 ID
*/
@Field(value = "cate_id", type = FieldType.Keyword)
private String cateId;
/**
* 单位名称
*/
@Field(value = "unit_name", type = FieldType.Text)
private String unitName;
/**
* 排序
*/
@Field(value = "sort", type = FieldType.Integer)
private Integer sort;
/**
* 是否热门
*/
@Field(value = "is_hot", type = FieldType.Boolean)
private Boolean isHot;
/**
* 是否有优惠
*/
@Field(value = "is_benefit", type = FieldType.Boolean)
private Boolean isBenefit;
/**
* 是否精品
*/
@Field(value = "is_best", type = FieldType.Boolean)
private Boolean isBest;
/**
* 是否新品
*/
@Field(value = "is_new", type = FieldType.Boolean)
private Boolean isNew;
/**
* 是否优质
*/
@Field(value = "is_good", type = FieldType.Boolean)
private Boolean isGood;
/**
* 赠送积分
*/
@Field(value = "give_integral", type = FieldType.Integer)
private Integer giveIntegral;
/**
* 是否子项
*/
@Field(value = "is_sub", type = FieldType.Boolean)
private Boolean isSub;
/**
* 虚拟数据
*/
@Field(value = "ficti", type = FieldType.Integer)
private Integer ficti;
/**
* 模板 ID
*/
@Field(value = "temp_id", type = FieldType.Integer)
private Integer tempId;
/**
* 规格类型
*/
@Field(value = "spec_type", type = FieldType.Boolean)
private Boolean specType;
/**
* 活动
*/
@Field(value = "activity", type = FieldType.Text)
private String activity;
/**
* 属性
*/
@Field(value = "attr", type = FieldType.Text)
private String attr;
/**
* 属性值
*/
@Field(value = "attr_value", type = FieldType.Text)
private String attrValue;
/**
* 内容
*/
@Field(value = "content", type = FieldType.Text)
private String content;
/**
* 优惠券 ID 列表
*/
@Field(value = "coupon_ids", type = FieldType.Text)
private String couponIds;
/**
* 平铺模式
*/
@Field(value = "flat_pattern", type = FieldType.Text)
private String flatPattern;
}
✌️ ES常用api
TestES.java
import com.db.test.entity.dto.StoreProductEsDTO;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.*;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
/**
* @author hanson.huang
* @version V1.0
* @ClassName TestES
* @Description es 测试类
* @date 2025/2/18 19:41
**/
@Slf4j
@SpringBootTest
public class TestES {
@Resource(name = "elasticsearchRestTemplate")
private ElasticsearchRestTemplate elasticsearchRestTemplate;
/**
* 生成模拟的 StoreProductEsDTO 对象
* @return 模拟对象
*/
private StoreProductEsDTO generateMockProduct(String id) {
StoreProductEsDTO product = new StoreProductEsDTO();
product.setId(id);
product.setImage("https://example.com/image.jpg");
product.setSliderImage("https://example.com/slider_image.jpg");
product.setStoreName("测试店铺商品" + id);
product.setStoreInfo("这是一个测试用的店铺商品信息");
product.setKeyword("测试商品");
product.setCateId("1001");
product.setUnitName("件");
product.setSort(1);
product.setIsHot(true);
product.setIsBenefit(true);
product.setIsBest(true);
product.setIsNew(true);
product.setIsGood(true);
product.setGiveIntegral(10);
product.setIsSub(false);
product.setFicti(1);
product.setTempId(1);
product.setSpecType(true);
product.setActivity("{\"name\":\"测试活动\"}");
product.setAttr("{\"color\":\"red\"}");
product.setAttrValue("{\"size\":\"L\"}");
product.setContent("商品详细内容描述");
product.setCouponIds("{\"id\":\"C001\"}");
product.setFlatPattern("{\"mode\":\"平铺\"}");
return product;
}
/**
* 插入单条文档
*/
@Test
void testInsertDocument() {
StoreProductEsDTO product = generateMockProduct("991");
StoreProductEsDTO savedProduct = elasticsearchRestTemplate.save(product);
log.info("插入文档结果: {}", savedProduct);
}
/**
* 批量插入文档
*/
@Test
void testBulkInsertDocuments() {
List<StoreProductEsDTO> products = Arrays.asList(generateMockProduct("992"), generateMockProduct("993"));
Iterable<StoreProductEsDTO> savedProducts = elasticsearchRestTemplate.save(products);
savedProducts.forEach(product -> log.info("批量插入文档结果: {}", product));
}
/**
* 根据 ID 删除文档
*/
@Test
void testDeleteDocument() {
String id = "997";
elasticsearchRestTemplate.delete(id, StoreProductEsDTO.class);
log.info("删除 ID 为 {} 的文档", id);
}
/**
* 根据 ID 更新文档
*/
@Test
void testUpdateDocument() {
StoreProductEsDTO product = generateMockProduct("994");
product.setStoreName("更新后的测试店铺商品");
StoreProductEsDTO updatedProduct = elasticsearchRestTemplate.save(product);
log.info("更新文档结果: {}", updatedProduct);
}
/**
* 查询单条文档
*/
@Test
void testSearchSingleDocument() {
String id = "992";
StoreProductEsDTO product = elasticsearchRestTemplate.get(id, StoreProductEsDTO.class);
if (product != null) {
log.info("查询到的文档: {}", product);
} else {
log.info("未查询到 ID 为 {} 的文档", id);
}
}
/**
* 查询所有文档
*/
@Test
void testSearchAllDocuments() {
Query query = new CriteriaQuery(new Criteria());
SearchHits<StoreProductEsDTO> searchHits = elasticsearchRestTemplate.search(query, StoreProductEsDTO.class);
searchHits.forEach(hit -> log.info("查询到的文档: {}", hit.getContent()));
}
/**
* 分页查询文档
*/
@Test
void testSearchDocumentsByPage() {
int page = 0;
int size = 10;
Pageable pageable = PageRequest.of(page, size);
Query query = new CriteriaQuery(new Criteria()).setPageable(pageable);
SearchHits<StoreProductEsDTO> searchHits = elasticsearchRestTemplate.search(query, StoreProductEsDTO.class);
log.info("当前页文档数量: {}", searchHits.getSearchHits().size());
// 修正遍历部分
List<org.springframework.data.elasticsearch.core.SearchHit<StoreProductEsDTO>> searchHitList = searchHits.getSearchHits();
for (org.springframework.data.elasticsearch.core.SearchHit<StoreProductEsDTO> hit : searchHitList) {
log.info("分页查询到的文档: {}", hit.getContent());
}
}
/**
* 根据条件查询文档
*/
@Test
void testSearchDocumentsByCondition() {
Criteria criteria = new Criteria("storeName").is("测试店铺商品");
Query query = new CriteriaQuery(criteria);
SearchHits<StoreProductEsDTO> searchHits = elasticsearchRestTemplate.search(query, StoreProductEsDTO.class);
searchHits.forEach(hit -> log.info("根据条件查询到的文档: {}", hit.getContent()));
}
}
条件查询中,Criteria是模糊匹配,能查出storeName
值为测试店铺商品
、‘xxx测试店铺商品’、 ‘测试店铺商品xxx’、 'xxx测试店铺商品xxx’等情况
结果如下:
☝️ 性能比较
先叠个甲,本次比较非常不专业,数量比较小,MySQL也没设置合适索引,所以本次性能比较不具备参考性
✌️ 模拟创建数据接口
controller
@Resource
private StoreProductService storeProductService;
/**
* 添加数据
*
* @param storeProductRequest 需要添加的数据
* @return
*/
@PostMapping("/addData")
public String addData(@RequestBody StoreProductRequest storeProductRequest) {
storeProductService.insertData(storeProductRequest);
return "success";
}
实现类:StoreProductServiceImpl.java
@Slf4j
@Data
@Service
public class StoreProductServiceImpl extends ServiceImpl<StoreProductMapper, StoreProduct> implements StoreProductService {
@Resource
private StoreProductMapper storeProductMapper;
@Resource(name = "stringRedisTemplate")
private StringRedisTemplate stringRedisTemplate;
@Resource(name = "elasticsearchRestTemplate")
private ElasticsearchRestTemplate elasticsearchRestTemplate;
@Resource
private StoreProductMongoRepository storeProductMongoRepository;
private static final String REDIS_KEY = "storeProduct:key";
@Value("${es.storeProduct.indexName:store_product_info_v2}")
public String storeProductEsIndexName = "store_product_info_v2";
@Value("${es.storeProduct.pageSize:500}")
private int storeProductEsListPageSize = 500;
@Override
public void insertData(StoreProductRequest storeProductRequest) {
// 1.插入mysql
StoreProduct storeProduct = new StoreProduct();
BeanUtils.copyProperties(storeProductRequest, storeProduct);
storeProduct.setActivity(JacksonUtils.jsonEncode(storeProductRequest.getActivity()));
storeProduct.setAttr(JacksonUtils.jsonEncode(storeProductRequest.getAttr()));
storeProduct.setAttrValue(JacksonUtils.jsonEncode(storeProductRequest.getAttrValue()));
storeProduct.setCouponIds(JacksonUtils.jsonEncode(storeProductRequest.getCouponIds()));
storeProductMapper.insert(storeProduct);
log.warn("数据已经插入mysql数据库:{}", JacksonUtils.jsonEncode(storeProduct));
// 2.插入redis
stringRedisTemplate.opsForValue().set(REDIS_KEY + storeProduct.getId(), JacksonUtils.jsonEncode(storeProduct));
log.warn("数据已经插入redis数据库:{}", JacksonUtils.jsonEncode(storeProduct));
// 3.插入mongo
MongoStoreProduct mongoStoreProduct = new MongoStoreProduct();
BeanUtils.copyProperties(storeProduct, mongoStoreProduct);
mongoStoreProduct.setId(storeProduct.getId() + "");
try {
storeProductMongoRepository.save(mongoStoreProduct);
log.warn("数据已经插入mongo数据库:{}", JacksonUtils.jsonEncode(mongoStoreProduct));
} catch (Exception e) {
log.error("数据插入mongo数据库失败,失败原因:{}", e);
}
// 4.插入es
StoreProductEsDTO storeProductEsDTO = new StoreProductEsDTO();
BeanUtils.copyProperties(storeProduct, storeProductEsDTO);
storeProductEsDTO.setId(storeProduct.getId() + "");
// 创建客户端
List<IndexQuery> queries = new ArrayList<>();
IndexQuery indexQuery = new IndexQuery();
indexQuery.setId(storeProduct.getId() + "");
indexQuery.setObject(storeProductEsDTO);
queries.add(indexQuery);
try {
elasticsearchRestTemplate.bulkIndex(queries, StoreProductEsDTO.class);
log.warn("数据已经插入es数据库:{}", JacksonUtils.jsonEncode(storeProductEsDTO));
} catch (Exception e) {
log.error("数据插入es数据库失败,失败原因:{}", e);
}
}
}
接口:
curl --location 'localhost:8081/dbTest/addData' \
--header 'Content-Type: application/json' \
--data '{
"image": "https://example.com/image.jpg",
"sliderImage": "https://example.com/slider1.jpg,https://example.com/slider2.jpg",
"storeName": "新款智能手机",
"storeInfo": "这是一款高性能智能手机",
"keyword": "手机,智能手机",
"cateId": "1,2,3",
"unitName": "台",
"sort": 1,
"isHot": true,
"isBenefit": false,
"isBest": true,
"isNew": true,
"isGood": false,
"giveIntegral": 100,
"isSub": true,
"ficti": 500,
"tempId": 1,
"specType": true,
"activity": ["1", "2", "3"],
"attr": [
{
"attrName": "颜色",
"attrValues": "红色,蓝色,绿色"
},
{
"attrName": "尺寸",
"attrValues": "大号,中号,小号"
}
],
"attrValue": [
{
"productId": 0,
"stock": 100,
"suk": "红色-大号",
"price": 1999.00,
"image": "https://example.com/red-large.jpg",
"cost": 1500.00,
"otPrice": 2199.00,
"weight": 0.5,
"volume": 0.1,
"brokerage": 100.00,
"brokerageTwo": 50.00,
"attrValue": "{\"颜色\":\"红色\",\"尺寸\":\"大号\"}",
"quota": 10,
"quotaShow": 10,
"minPrice": 1500.00
},
{
"productId": 0,
"stock": 150,
"suk": "蓝色-中号",
"price": 1899.00,
"image": "https://example.com/blue-medium.jpg",
"cost": 1400.00,
"otPrice": 2099.00,
"weight": 0.45,
"volume": 0.09,
"brokerage": 90.00,
"brokerageTwo": 45.00,
"attrValue": "{\"颜色\":\"蓝色\",\"尺寸\":\"中号\"}",
"quota": 15,
"quotaShow": 15,
"minPrice": 1400.00
}
],
"content": "<p>这是一款高性能智能手机,适合各种场景使用。</p>",
"couponIds": [1, 2, 3],
"flatPattern": "https://example.com/flat-pattern.jpg"
}'
调用这个接口后,分别往四个中间件中插入了id为1000的数据
MySQL:
Redis:
MongoDB:
ES:
这样数据算创建完成,现在测试分别查出这条数据需要花费时间
✌️ 查询数据接口
我们使用stopwatch()来统计接口耗时,stopwatch()
用法可以参考文章《【StopWatch】使用 StopWatch 统计代码中的耗时操作》
代码如下:
/**
* @return 通过四种方式获取数据
*/
@Override
public Map<String, Object> getData(Integer id) {
Map<String, Object> result = new HashMap<>();
// 1.从mysql获取数据
StopWatch stopWatch = new StopWatch();
stopWatch.start("mysql查询数据开始");
StoreProduct storeProduct = storeProductMapper.selectById(id);
result.put("mysql", storeProduct);
stopWatch.stop();
// 2.从redis获取数据
stopWatch.start("redis查询数据开始");
String redisData = stringRedisTemplate.opsForValue().get(REDIS_KEY + id);
result.put("redis", JacksonUtils.jsonDecode(redisData, StoreProduct.class));
stopWatch.stop();
// 3.从mongo获取数据
stopWatch.start("mongo查询数据开始");
Optional<MongoStoreProduct> optional = storeProductMongoRepository.findById(String.valueOf(id));
if (optional.isPresent()) {
MongoStoreProduct mongoStoreProduct = optional.get();
result.put("mongo", mongoStoreProduct);
}
stopWatch.stop();
// 4.从es获取数据
stopWatch.start("es查询数据开始");
StoreProductEsDTO storeProductEsDTO = elasticsearchRestTemplate.get(String.valueOf(id), StoreProductEsDTO.class);
result.put("es", storeProductEsDTO);
stopWatch.stop();
log.error("查询数据耗时:{}", stopWatch.prettyPrint(TimeUnit.MILLISECONDS));
return result;
}
调用接口:
统计耗时:
查询数据耗时:StopWatch '': running time = 87 ms
---------------------------------------------
ms % Task name
---------------------------------------------
000000033 38% mysql查询数据开始
000000012 14% redis查询数据开始
000000024 28% mongo查询数据开始
000000017 20% es查询数据开始
虽然结果不具备参考性,可以看出es和redis性能比较好,在大量数据情况下,就查询数据而言,redis > es > mongoDB > mysql
总结一下:
中间件 | 查询效率 | 性能分析 | 底层存储结构 | 优点 | 缺点 | 使用场景 |
---|---|---|---|---|---|---|
MySQL | 中等 | 适用于结构化数据,复杂查询性能较好 | 关系型数据库,使用B+树索引 | 1. 支持复杂查询和事务 2. 数据一致性高 3. 成熟的生态系统和工具支持 | 1. 大数据量时性能下降 2. 水平扩展较复杂 3. 不适合非结构化数据 | 1. 金融系统(需要强一致性和事务支持) 2. ERP系统(复杂查询和报表) 3. 传统的关系型数据管理(如用户管理、订单管理) |
Redis | 高 | 适用于高并发、低延迟的场景 | 内存键值存储,支持多种数据结构 | 1. 极高的读写性能 2. 支持丰富的数据结构 3. 适合缓存和实时数据处理 | 1. 数据容量受内存限制 2. 持久化可能影响性能 3. 不适合复杂查询 | 1. 缓存系统(如网页缓存、会话缓存) 2. 实时排行榜(如游戏积分榜) 3. 消息队列(如任务队列) 4. 实时数据处理(如实时推荐系统) |
MongoDB | 中高 | 适用于半结构化数据,读写性能较好 | 文档型数据库,使用BSON格式存储,支持索引 | 1. 灵活的数据模型 2. 水平扩展容易 3. 适合处理大量非结构化数据 | 1. 复杂查询性能不如关系型数据库 2. 事务支持较弱(虽然MongoDB 4.0+支持多文档事务) 3. 存储空间占用较大 | 1. 内容管理系统(CMS) 2. 物联网(IoT)数据存储 3. 日志存储和分析 4. 实时大数据处理(如用户行为分析) |
Elasticsearch | 高 | 适用于全文搜索和实时分析 | 分布式搜索引擎,使用倒排索引 | 1. 强大的全文搜索能力 2. 实时数据分析 3. 水平扩展容易 | 1. 写入性能相对较低 2. 配置和维护复杂 3. 数据一致性较弱(最终一致性) | 1. 全文搜索引擎(如电商网站的商品搜索) 2. 日志和指标分析(如ELK Stack) 3. 实时数据分析(如监控和报警系统) 4. 推荐系统(基于用户行为的实时推荐) |
创作不易,不妨点赞、收藏、关注支持一下,各位的支持就是我创作的最大动力❤️