您说得对!Spring Boot 2.5.10 确实自带了 Elasticsearch 的集成,我们可以使用 Spring Data Elasticsearch 而不需要单独引入 elasticsearch-rest-high-level-client
。让我为您提供完整的 Elasticsearch 7.12 集成方案。
项目目录结构
text
src/main/java/com/example/esdemo/
├── config/
│ └── ElasticsearchConfig.java
├── entity/
│ └── UserEs.java
├── repository/
│ └── UserEsRepository.java
├── service/
│ └── UserEsService.java
├── controller/
│ └── UserEsController.java
└── ESDemoApplication.java
src/main/resources/
├── application.yml
└── logback-spring.xml
1. Maven 依赖 (pom.xml)
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.10</version>
</parent>
<groupId>com.example</groupId>
<artifactId>es-demo</artifactId>
<version>1.0.0</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Data Elasticsearch (Spring Boot 2.5.10 自带 Elasticsearch 7.12 客户端) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2. 配置文件 (application.yml)
yaml
spring:
elasticsearch:
rest:
# Elasticsearch 集群节点地址
uris: http://localhost:9200,http://localhost:9201,http://localhost:9202
# 连接超时时间
connection-timeout: 10s
# 读取超时时间
read-timeout: 30s
# 认证信息(如果Elasticsearch开启了安全认证)
# username: elastic
# password: your_password
# 应用配置
app:
elasticsearch:
index:
user: user_es_index
settings:
shards: 3
replicas: 1
# 服务器配置
server:
port: 8080
servlet:
context-path: /
# 日志配置
logging:
level:
com.example.esdemo: DEBUG
org.springframework.data.elasticsearch: DEBUG
org.elasticsearch: WARN
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file:
name: logs/es-demo.log
3. 配置层 (Config)
ElasticsearchConfig.java
java
package com.example.esdemo.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
@Slf4j
@Configuration
@EnableElasticsearchRepositories(basePackages = "com.example.esdemo.repository")
public class ElasticsearchConfig {
/**
* Spring Boot 2.5.10 自动配置了 ElasticsearchRestTemplate 和 RestHighLevelClient
* 我们不需要手动配置,只需要通过 @EnableElasticsearchRepositories 启用 Repository 支持
*/
public ElasticsearchConfig() {
log.info("Elasticsearch配置初始化完成 - 使用Spring Boot自动配置");
}
}
4. 实体层 (Entity)
UserEs.java
java
package com.example.esdemo.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.Setting;
import java.time.LocalDateTime;
import java.util.Map;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "user_es_index")
@Setting(shards = 3, replicas = 1) // 索引设置
public class UserEs {
@Id
private String id;
@Field(type = FieldType.Long)
private Long userId;
@Field(type = FieldType.Keyword)
private String username;
@Field(type = FieldType.Text, analyzer = "standard", searchAnalyzer = "standard")
private String realName;
@Field(type = FieldType.Integer)
private Integer age;
@Field(type = FieldType.Keyword)
private String email;
@Field(type = FieldType.Text, analyzer = "standard")
private String address;
@Field(type = FieldType.Keyword)
private String phone;
@Field(type = FieldType.Date)
private LocalDateTime createTime;
@Field(type = FieldType.Date)
private LocalDateTime updateTime;
@Field(type = FieldType.Boolean)
private Boolean status;
@Field(type = FieldType.Object, enabled = false)
private Map<String, Object> extraInfo;
// 便捷构造方法
public UserEs(Long userId, String username, String realName, Integer age,
String email, String address, String phone) {
this.userId = userId;
this.username = username;
this.realName = realName;
this.age = age;
this.email = email;
this.address = address;
this.phone = phone;
this.createTime = LocalDateTime.now();
this.updateTime = LocalDateTime.now();
this.status = true;
}
// 静态工厂方法
public static UserEs createUser(Long userId, String username, String realName,
Integer age, String email, String address, String phone) {
return new UserEs(userId, username, realName, age, email, address, phone);
}
}
5. 数据访问层 (Repository)
UserEsRepository.java
java
package com.example.esdemo.repository;
import com.example.esdemo.entity.UserEs;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface UserEsRepository extends ElasticsearchRepository<UserEs, String> {
// ==================== 基本查询方法 ====================
/**
* 根据用户名查询(精确匹配)
*/
Optional<UserEs> findByUsername(String username);
/**
* 根据邮箱查询(精确匹配)
*/
Optional<UserEs> findByEmail(String email);
/**
* 根据手机号查询(精确匹配)
*/
Optional<UserEs> findByPhone(String phone);
/**
* 根据用户ID查询
*/
Optional<UserEs> findByUserId(Long userId);
// ==================== 范围查询方法 ====================
/**
* 根据年龄范围查询
*/
List<UserEs> findByAgeBetween(Integer minAge, Integer maxAge);
/**
* 查询年龄大于指定值的用户
*/
List<UserEs> findByAgeGreaterThan(Integer age);
/**
* 查询年龄小于指定值的用户
*/
List<UserEs> findByAgeLessThan(Integer age);
// ==================== 模糊查询方法 ====================
/**
* 根据真实姓名模糊查询
*/
List<UserEs> findByRealNameContaining(String realName);
/**
* 根据地址模糊查询
*/
List<UserEs> findByAddressContaining(String address);
/**
* 根据地址模糊查询并分页
*/
Page<UserEs> findByAddressContaining(String address, Pageable pageable);
// ==================== 状态查询方法 ====================
/**
* 根据状态查询用户
*/
List<UserEs> findByStatus(Boolean status);
/**
* 根据状态查询用户并分页
*/
Page<UserEs> findByStatus(Boolean status, Pageable pageable);
// ==================== 组合查询方法 ====================
/**
* 根据用户名和年龄查询
*/
List<UserEs> findByUsernameAndAge(String username, Integer age);
/**
* 根据状态和年龄范围查询
*/
List<UserEs> findByStatusAndAgeBetween(Boolean status, Integer minAge, Integer maxAge);
/**
* 根据真实姓名和状态查询
*/
List<UserEs> findByRealNameContainingAndStatus(String realName, Boolean status);
// ==================== 排序查询方法 ====================
/**
* 根据创建时间倒序查询所有用户
*/
List<UserEs> findAllByOrderByCreateTimeDesc();
/**
* 根据年龄升序查询所有用户
*/
List<UserEs> findAllByOrderByAgeAsc();
}
6. 服务层 (Service)
UserEsService.java
java
package com.example.esdemo.service;
import com.example.esdemo.entity.UserEs;
import com.example.esdemo.repository.UserEsRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.IndexOperations;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
@Slf4j
@Service
public class UserEsService {
@Autowired
private UserEsRepository userEsRepository;
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
// ==================== 索引管理操作 ====================
/**
* 创建索引
*/
public boolean createIndex() {
try {
IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(UserEs.class);
if (indexOperations.exists()) {
log.warn("索引已存在: user_es_index");
return true;
}
boolean created = indexOperations.create();
if (created) {
// 创建映射
Document mapping = indexOperations.createMapping(UserEs.class);
indexOperations.putMapping(mapping);
log.info("索引创建成功: user_es_index");
}
return created;
} catch (Exception e) {
log.error("创建索引失败", e);
return false;
}
}
/**
* 删除索引
*/
public boolean deleteIndex() {
try {
IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(UserEs.class);
if (!indexOperations.exists()) {
log.warn("索引不存在: user_es_index");
return true;
}
boolean deleted = indexOperations.delete();
log.info("索引删除结果: {}", deleted);
return deleted;
} catch (Exception e) {
log.error("删除索引失败", e);
return false;
}
}
/**
* 检查索引是否存在
*/
public boolean indexExists() {
try {
IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(UserEs.class);
return indexOperations.exists();
} catch (Exception e) {
log.error("检查索引存在失败", e);
return false;
}
}
/**
* 刷新索引
*/
public void refreshIndex() {
try {
IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(UserEs.class);
indexOperations.refresh();
log.info("索引刷新完成");
} catch (Exception e) {
log.error("刷新索引失败", e);
}
}
/**
* 获取索引信息
*/
public Map<String, Object> getIndexInfo() {
Map<String, Object> info = new HashMap<>();
try {
IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(UserEs.class);
info.put("exists", indexOperations.exists());
info.put("settings", indexOperations.getSettings());
info.put("mapping", indexOperations.getMapping());
info.put("indexName", "user_es_index");
} catch (Exception e) {
log.error("获取索引信息失败", e);
info.put("error", e.getMessage());
}
return info;
}
// ==================== 文档CRUD操作 ====================
/**
* 添加单个用户
*/
public UserEs saveUser(UserEs userEs) {
try {
if (userEs.getCreateTime() == null) {
userEs.setCreateTime(LocalDateTime.now());
}
userEs.setUpdateTime(LocalDateTime.now());
if (userEs.getStatus() == null) {
userEs.setStatus(true);
}
UserEs savedUser = userEsRepository.save(userEs);
log.info("用户保存成功, ID: {}, 用户名: {}", savedUser.getId(), savedUser.getUsername());
return savedUser;
} catch (Exception e) {
log.error("保存用户失败: {}", userEs, e);
throw new RuntimeException("保存用户失败: " + e.getMessage(), e);
}
}
/**
* 批量添加用户
*/
public List<UserEs> saveAllUsers(List<UserEs> userEsList) {
try {
userEsList.forEach(user -> {
if (user.getCreateTime() == null) {
user.setCreateTime(LocalDateTime.now());
}
user.setUpdateTime(LocalDateTime.now());
if (user.getStatus() == null) {
user.setStatus(true);
}
});
Iterable<UserEs> savedUsers = userEsRepository.saveAll(userEsList);
List<UserEs> result = StreamSupport.stream(savedUsers.spliterator(), false)
.collect(Collectors.toList());
log.info("批量保存用户成功, 数量: {}", result.size());
return result;
} catch (Exception e) {
log.error("批量保存用户失败", e);
throw new RuntimeException("批量保存用户失败: " + e.getMessage(), e);
}
}
/**
* 根据ID查询用户
*/
public Optional<UserEs> findById(String id) {
try {
return userEsRepository.findById(id);
} catch (Exception e) {
log.error("根据ID查询用户失败, ID: {}", id, e);
return Optional.empty();
}
}
/**
* 根据用户ID查询
*/
public Optional<UserEs> findByUserId(Long userId) {
try {
return userEsRepository.findByUserId(userId);
} catch (Exception e) {
log.error("根据用户ID查询失败, UserID: {}", userId, e);
return Optional.empty();
}
}
/**
* 根据用户名查询
*/
public Optional<UserEs> findByUsername(String username) {
try {
return userEsRepository.findByUsername(username);
} catch (Exception e) {
log.error("根据用户名查询失败, 用户名: {}", username, e);
return Optional.empty();
}
}
/**
* 查询所有用户
*/
public List<UserEs> findAll() {
try {
Iterable<UserEs> users = userEsRepository.findAll();
return StreamSupport.stream(users.spliterator(), false)
.collect(Collectors.toList());
} catch (Exception e) {
log.error("查询所有用户失败", e);
return Collections.emptyList();
}
}
/**
* 分页查询所有用户
*/
public Page<UserEs> findAllWithPage(int page, int size) {
try {
Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createTime"));
return userEsRepository.findAll(pageable);
} catch (Exception e) {
log.error("分页查询用户失败, page: {}, size: {}", page, size, e);
throw new RuntimeException("分页查询失败: " + e.getMessage(), e);
}
}
/**
* 更新用户
*/
public UserEs updateUser(UserEs userEs) {
try {
if (userEs.getId() == null) {
throw new IllegalArgumentException("用户ID不能为空");
}
if (!userEsRepository.existsById(userEs.getId())) {
throw new RuntimeException("用户不存在, ID: " + userEs.getId());
}
userEs.setUpdateTime(LocalDateTime.now());
UserEs updatedUser = userEsRepository.save(userEs);
log.info("用户更新成功, ID: {}", updatedUser.getId());
return updatedUser;
} catch (Exception e) {
log.error("更新用户失败: {}", userEs, e);
throw new RuntimeException("更新用户失败: " + e.getMessage(), e);
}
}
/**
* 部分更新用户
*/
public UserEs partialUpdate(String id, Map<String, Object> updates) {
try {
Optional<UserEs> userOpt = userEsRepository.findById(id);
if (!userOpt.isPresent()) {
throw new RuntimeException("用户不存在, ID: " + id);
}
UserEs user = userOpt.get();
updates.forEach((key, value) -> {
switch (key) {
case "realName":
user.setRealName((String) value);
break;
case "age":
user.setAge((Integer) value);
break;
case "email":
user.setEmail((String) value);
break;
case "address":
user.setAddress((String) value);
break;
case "phone":
user.setPhone((String) value);
break;
case "status":
user.setStatus((Boolean) value);
break;
case "extraInfo":
user.setExtraInfo((Map<String, Object>) value);
break;
}
});
user.setUpdateTime(LocalDateTime.now());
UserEs updatedUser = userEsRepository.save(user);
log.info("用户部分更新成功, ID: {}", id);
return updatedUser;
} catch (Exception e) {
log.error("部分更新用户失败, ID: {}, updates: {}", id, updates, e);
throw new RuntimeException("部分更新失败: " + e.getMessage(), e);
}
}
/**
* 根据ID删除用户
*/
public boolean deleteById(String id) {
try {
if (!userEsRepository.existsById(id)) {
log.warn("用户不存在, 无法删除, ID: {}", id);
return false;
}
userEsRepository.deleteById(id);
log.info("用户删除成功, ID: {}", id);
return true;
} catch (Exception e) {
log.error("删除用户失败, ID: {}", id, e);
return false;
}
}
/**
* 根据用户名删除用户
*/
public boolean deleteByUsername(String username) {
try {
Optional<UserEs> userOpt = userEsRepository.findByUsername(username);
if (!userOpt.isPresent()) {
log.warn("用户不存在, 无法删除, 用户名: {}", username);
return false;
}
userEsRepository.delete(userOpt.get());
log.info("用户删除成功, 用户名: {}", username);
return true;
} catch (Exception e) {
log.error("根据用户名删除用户失败, 用户名: {}", username, e);
return false;
}
}
/**
* 批量删除用户
*/
public void deleteAll(List<String> ids) {
try {
List<UserEs> usersToDelete = ids.stream()
.map(id -> {
UserEs user = new UserEs();
user.setId(id);
return user;
})
.collect(Collectors.toList());
userEsRepository.deleteAll(usersToDelete);
log.info("批量删除用户成功, 数量: {}", ids.size());
} catch (Exception e) {
log.error("批量删除用户失败", e);
throw new RuntimeException("批量删除失败: " + e.getMessage(), e);
}
}
// ==================== 高级查询操作 ====================
/**
* 根据年龄范围查询
*/
public List<UserEs> findByAgeRange(Integer minAge, Integer maxAge) {
try {
return userEsRepository.findByAgeBetween(minAge, maxAge);
} catch (Exception e) {
log.error("根据年龄范围查询失败, minAge: {}, maxAge: {}", minAge, maxAge, e);
return Collections.emptyList();
}
}
/**
* 根据状态查询用户
*/
public List<UserEs> findByStatus(Boolean status) {
try {
return userEsRepository.findByStatus(status);
} catch (Exception e) {
log.error("根据状态查询用户失败, status: {}", status, e);
return Collections.emptyList();
}
}
/**
* 根据地址模糊查询
*/
public List<UserEs> findByAddress(String address) {
try {
return userEsRepository.findByAddressContaining(address);
} catch (Exception e) {
log.error("根据地址查询用户失败, address: {}", address, e);
return Collections.emptyList();
}
}
/**
* 复杂搜索:多条件组合查询
*/
public List<UserEs> complexSearch(String keyword, Integer minAge, Integer maxAge,
String address, Boolean status) {
try {
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 构建布尔查询
org.elasticsearch.index.query.BoolQueryBuilder boolQuery =
org.elasticsearch.index.query.QueryBuilders.boolQuery();
// 关键词搜索
if (StringUtils.hasText(keyword)) {
boolQuery.should(org.elasticsearch.index.query.QueryBuilders
.matchQuery("username", keyword));
boolQuery.should(org.elasticsearch.index.query.QueryBuilders
.matchQuery("realName", keyword));
boolQuery.should(org.elasticsearch.index.query.QueryBuilders
.matchQuery("address", keyword));
boolQuery.minimumShouldMatch(1);
}
// 年龄范围过滤
if (minAge != null || maxAge != null) {
org.elasticsearch.index.query.RangeQueryBuilder rangeQuery =
org.elasticsearch.index.query.QueryBuilders.rangeQuery("age");
if (minAge != null) {
rangeQuery.gte(minAge);
}
if (maxAge != null) {
rangeQuery.lte(maxAge);
}
boolQuery.filter(rangeQuery);
}
// 地址过滤
if (StringUtils.hasText(address)) {
boolQuery.filter(org.elasticsearch.index.query.QueryBuilders
.wildcardQuery("address", "*" + address + "*"));
}
// 状态过滤
if (status != null) {
boolQuery.filter(org.elasticsearch.index.query.QueryBuilders
.termQuery("status", status));
}
NativeSearchQuery query = queryBuilder
.withQuery(boolQuery)
.withSort(Sort.by(Sort.Direction.DESC, "createTime"))
.build();
return elasticsearchRestTemplate.queryForList(query, UserEs.class);
} catch (Exception e) {
log.error("复杂搜索失败, keyword: {}, minAge: {}, maxAge: {}, address: {}, status: {}",
keyword, minAge, maxAge, address, status, e);
return Collections.emptyList();
}
}
// ==================== 统计操作 ====================
/**
* 统计用户总数
*/
public long count() {
try {
return userEsRepository.count();
} catch (Exception e) {
log.error("统计用户总数失败", e);
return 0;
}
}
/**
* 根据状态统计用户数量
*/
public long countByStatus(Boolean status) {
try {
return userEsRepository.findByStatus(status).size();
} catch (Exception e) {
log.error("根据状态统计用户数量失败, status: {}", status, e);
return 0;
}
}
/**
* 检查用户名是否存在
*/
public boolean existsByUsername(String username) {
try {
return userEsRepository.findByUsername(username).isPresent();
} catch (Exception e) {
log.error("检查用户名是否存在失败, username: {}", username, e);
return false;
}
}
/**
* 检查邮箱是否存在
*/
public boolean existsByEmail(String email) {
try {
return userEsRepository.findByEmail(email).isPresent();
} catch (Exception e) {
log.error("检查邮箱是否存在失败, email: {}", email, e);
return false;
}
}
}
7. 控制层 (Controller)
UserEsController.java
java
package com.example.esdemo.controller;
import com.example.esdemo.entity.UserEs;
import com.example.esdemo.service.UserEsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Slf4j
@RestController
@RequestMapping("/api/es/users")
public class UserEsController {
@Autowired
private UserEsService userEsService;
// ==================== 索引管理接口 ====================
/**
* 创建索引
*/
@PostMapping("/index")
public ResponseEntity<Map<String, Object>> createIndex() {
log.info("请求创建索引");
try {
boolean success = userEsService.createIndex();
Map<String, Object> result = createSuccessResult(success,
success ? "索引创建成功" : "索引创建失败");
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("创建索引异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("创建索引异常: " + e.getMessage()));
}
}
/**
* 删除索引
*/
@DeleteMapping("/index")
public ResponseEntity<Map<String, Object>> deleteIndex() {
log.info("请求删除索引");
try {
boolean success = userEsService.deleteIndex();
Map<String, Object> result = createSuccessResult(success,
success ? "索引删除成功" : "索引删除失败");
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("删除索引异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("删除索引异常: " + e.getMessage()));
}
}
/**
* 检查索引是否存在
*/
@GetMapping("/index/exists")
public ResponseEntity<Map<String, Object>> indexExists() {
log.info("请求检查索引是否存在");
try {
boolean exists = userEsService.indexExists();
Map<String, Object> result = createSuccessResult(true, "查询成功");
result.put("exists", exists);
result.put("indexName", "user_es_index");
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("检查索引存在异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("检查索引存在异常: " + e.getMessage()));
}
}
/**
* 获取索引信息
*/
@GetMapping("/index/info")
public ResponseEntity<Map<String, Object>> getIndexInfo() {
log.info("请求获取索引信息");
try {
Map<String, Object> indexInfo = userEsService.getIndexInfo();
Map<String, Object> result = createSuccessResult(true, "获取索引信息成功");
result.put("indexInfo", indexInfo);
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("获取索引信息异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("获取索引信息异常: " + e.getMessage()));
}
}
/**
* 刷新索引
*/
@PostMapping("/index/refresh")
public ResponseEntity<Map<String, Object>> refreshIndex() {
log.info("请求刷新索引");
try {
userEsService.refreshIndex();
Map<String, Object> result = createSuccessResult(true, "索引刷新成功");
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("刷新索引异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("刷新索引异常: " + e.getMessage()));
}
}
// ==================== 文档CRUD接口 ====================
/**
* 添加用户
*/
@PostMapping
public ResponseEntity<Map<String, Object>> addUser(@RequestBody UserEs userEs) {
log.info("请求添加用户: {}", userEs.getUsername());
try {
UserEs savedUser = userEsService.saveUser(userEs);
Map<String, Object> result = createSuccessResult(true, "用户添加成功");
result.put("data", savedUser);
return ResponseEntity.status(HttpStatus.CREATED).body(result);
} catch (Exception e) {
log.error("添加用户异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("添加用户失败: " + e.getMessage()));
}
}
/**
* 批量添加用户
*/
@PostMapping("/batch")
public ResponseEntity<Map<String, Object>> batchAddUsers(@RequestBody List<UserEs> userEsList) {
log.info("请求批量添加用户, 数量: {}", userEsList.size());
try {
List<UserEs> savedUsers = userEsService.saveAllUsers(userEsList);
Map<String, Object> result = createSuccessResult(true, "批量添加用户成功");
result.put("data", savedUsers);
result.put("count", savedUsers.size());
return ResponseEntity.status(HttpStatus.CREATED).body(result);
} catch (Exception e) {
log.error("批量添加用户异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("批量添加用户失败: " + e.getMessage()));
}
}
/**
* 根据ID查询用户
*/
@GetMapping("/{id}")
public ResponseEntity<Map<String, Object>> getUserById(@PathVariable String id) {
log.info("请求根据ID查询用户: {}", id);
try {
Optional<UserEs> userOpt = userEsService.findById(id);
if (userOpt.isPresent()) {
Map<String, Object> result = createSuccessResult(true, "查询成功");
result.put("data", userOpt.get());
return ResponseEntity.ok(result);
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(createErrorResult("用户不存在"));
}
} catch (Exception e) {
log.error("根据ID查询用户异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("查询用户失败: " + e.getMessage()));
}
}
/**
* 根据用户ID查询
*/
@GetMapping("/userId/{userId}")
public ResponseEntity<Map<String, Object>> getUserByUserId(@PathVariable Long userId) {
log.info("请求根据用户ID查询: {}", userId);
try {
Optional<UserEs> userOpt = userEsService.findByUserId(userId);
if (userOpt.isPresent()) {
Map<String, Object> result = createSuccessResult(true, "查询成功");
result.put("data", userOpt.get());
return ResponseEntity.ok(result);
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(createErrorResult("用户不存在"));
}
} catch (Exception e) {
log.error("根据用户ID查询异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("查询用户失败: " + e.getMessage()));
}
}
/**
* 根据用户名查询
*/
@GetMapping("/username/{username}")
public ResponseEntity<Map<String, Object>> getUserByUsername(@PathVariable String username) {
log.info("请求根据用户名查询: {}", username);
try {
Optional<UserEs> userOpt = userEsService.findByUsername(username);
if (userOpt.isPresent()) {
Map<String, Object> result = createSuccessResult(true, "查询成功");
result.put("data", userOpt.get());
return ResponseEntity.ok(result);
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(createErrorResult("用户不存在"));
}
} catch (Exception e) {
log.error("根据用户名查询异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("查询用户失败: " + e.getMessage()));
}
}
/**
* 查询所有用户
*/
@GetMapping
public ResponseEntity<Map<String, Object>> getAllUsers() {
log.info("请求查询所有用户");
try {
List<UserEs> users = userEsService.findAll();
Map<String, Object> result = createSuccessResult(true, "查询成功");
result.put("data", users);
result.put("total", users.size());
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("查询所有用户异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("查询用户失败: " + e.getMessage()));
}
}
/**
* 分页查询所有用户
*/
@GetMapping("/page")
public ResponseEntity<Map<String, Object>> getAllUsersWithPage(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
log.info("请求分页查询用户, page: {}, size: {}", page, size);
try {
Page<UserEs> userPage = userEsService.findAllWithPage(page, size);
Map<String, Object> result = createSuccessResult(true, "查询成功");
result.put("data", userPage.getContent());
result.put("total", userPage.getTotalElements());
result.put("page", page);
result.put("size", size);
result.put("totalPages", userPage.getTotalPages());
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("分页查询用户异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("分页查询失败: " + e.getMessage()));
}
}
/**
* 更新用户
*/
@PutMapping
public ResponseEntity<Map<String, Object>> updateUser(@RequestBody UserEs userEs) {
log.info("请求更新用户: {}", userEs.getId());
try {
UserEs updatedUser = userEsService.updateUser(userEs);
Map<String, Object> result = createSuccessResult(true, "用户更新成功");
result.put("data", updatedUser);
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("更新用户异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("更新用户失败: " + e.getMessage()));
}
}
/**
* 部分更新用户
*/
@PatchMapping("/{id}")
public ResponseEntity<Map<String, Object>> partialUpdateUser(
@PathVariable String id,
@RequestBody Map<String, Object> updates) {
log.info("请求部分更新用户: {}, updates: {}", id, updates);
try {
UserEs updatedUser = userEsService.partialUpdate(id, updates);
Map<String, Object> result = createSuccessResult(true, "用户部分更新成功");
result.put("data", updatedUser);
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("部分更新用户异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("部分更新用户失败: " + e.getMessage()));
}
}
/**
* 根据ID删除用户
*/
@DeleteMapping("/{id}")
public ResponseEntity<Map<String, Object>> deleteUser(@PathVariable String id) {
log.info("请求删除用户: {}", id);
try {
boolean success = userEsService.deleteById(id);
if (success) {
Map<String, Object> result = createSuccessResult(true, "用户删除成功");
return ResponseEntity.ok(result);
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(createErrorResult("用户不存在"));
}
} catch (Exception e) {
log.error("删除用户异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("删除用户失败: " + e.getMessage()));
}
}
/**
* 根据用户名删除用户
*/
@DeleteMapping("/username/{username}")
public ResponseEntity<Map<String, Object>> deleteUserByUsername(@PathVariable String username) {
log.info("请求根据用户名删除用户: {}", username);
try {
boolean success = userEsService.deleteByUsername(username);
if (success) {
Map<String, Object> result = createSuccessResult(true, "用户删除成功");
return ResponseEntity.ok(result);
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(createErrorResult("用户不存在"));
}
} catch (Exception e) {
log.error("根据用户名删除用户异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("删除用户失败: " + e.getMessage()));
}
}
// ==================== 高级查询接口 ====================
/**
* 根据年龄范围查询
*/
@GetMapping("/age/range")
public ResponseEntity<Map<String, Object>> getUsersByAgeRange(
@RequestParam Integer minAge,
@RequestParam Integer maxAge) {
log.info("请求根据年龄范围查询, minAge: {}, maxAge: {}", minAge, maxAge);
try {
List<UserEs> users = userEsService.findByAgeRange(minAge, maxAge);
Map<String, Object> result = createSuccessResult(true, "查询成功");
result.put("data", users);
result.put("total", users.size());
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("根据年龄范围查询异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("查询失败: " + e.getMessage()));
}
}
/**
* 根据状态查询用户
*/
@GetMapping("/status/{status}")
public ResponseEntity<Map<String, Object>> getUsersByStatus(@PathVariable Boolean status) {
log.info("请求根据状态查询用户: {}", status);
try {
List<UserEs> users = userEsService.findByStatus(status);
Map<String, Object> result = createSuccessResult(true, "查询成功");
result.put("data", users);
result.put("total", users.size());
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("根据状态查询用户异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("查询失败: " + e.getMessage()));
}
}
/**
* 根据地址查询用户
*/
@GetMapping("/address/{address}")
public ResponseEntity<Map<String, Object>> getUsersByAddress(@PathVariable String address) {
log.info("请求根据地址查询用户: {}", address);
try {
List<UserEs> users = userEsService.findByAddress(address);
Map<String, Object> result = createSuccessResult(true, "查询成功");
result.put("data", users);
result.put("total", users.size());
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("根据地址查询用户异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("查询失败: " + e.getMessage()));
}
}
/**
* 复杂搜索
*/
@GetMapping("/search/complex")
public ResponseEntity<Map<String, Object>> complexSearch(
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Integer minAge,
@RequestParam(required = false) Integer maxAge,
@RequestParam(required = false) String address,
@RequestParam(required = false) Boolean status) {
log.info("请求复杂搜索, keyword: {}, minAge: {}, maxAge: {}, address: {}, status: {}",
keyword, minAge, maxAge, address, status);
try {
List<UserEs> users = userEsService.complexSearch(keyword, minAge, maxAge, address, status);
Map<String, Object> result = createSuccessResult(true, "搜索成功");
result.put("data", users);
result.put("total", users.size());
result.put("searchParams", Map.of(
"keyword", keyword,
"minAge", minAge,
"maxAge", maxAge,
"address", address,
"status", status
));
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("复杂搜索异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("搜索失败: " + e.getMessage()));
}
}
// ==================== 统计接口 ====================
/**
* 统计用户总数
*/
@GetMapping("/count")
public ResponseEntity<Map<String, Object>> getUserCount() {
log.info("请求统计用户总数");
try {
long count = userEsService.count();
Map<String, Object> result = createSuccessResult(true, "统计成功");
result.put("count", count);
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("统计用户总数异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("统计失败: " + e.getMessage()));
}
}
/**
* 根据状态统计用户数量
*/
@GetMapping("/count/status/{status}")
public ResponseEntity<Map<String, Object>> countByStatus(@PathVariable Boolean status) {
log.info("请求根据状态统计用户数量: {}", status);
try {
long count = userEsService.countByStatus(status);
Map<String, Object> result = createSuccessResult(true, "统计成功");
result.put("count", count);
result.put("status", status);
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("根据状态统计用户数量异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("统计失败: " + e.getMessage()));
}
}
/**
* 检查用户名是否存在
*/
@GetMapping("/exists/username/{username}")
public ResponseEntity<Map<String, Object>> checkUsernameExists(@PathVariable String username) {
log.info("请求检查用户名是否存在: {}", username);
try {
boolean exists = userEsService.existsByUsername(username);
Map<String, Object> result = createSuccessResult(true, "检查成功");
result.put("exists", exists);
result.put("username", username);
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("检查用户名是否存在异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("检查失败: " + e.getMessage()));
}
}
/**
* 检查邮箱是否存在
*/
@GetMapping("/exists/email/{email}")
public ResponseEntity<Map<String, Object>> checkEmailExists(@PathVariable String email) {
log.info("请求检查邮箱是否存在: {}", email);
try {
boolean exists = userEsService.existsByEmail(email);
Map<String, Object> result = createSuccessResult(true, "检查成功");
result.put("exists", exists);
result.put("email", email);
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("检查邮箱是否存在异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResult("检查失败: " + e.getMessage()));
}
}
// ==================== 健康检查接口 ====================
/**
* 健康检查
*/
@GetMapping("/health")
public ResponseEntity<Map<String, Object>> healthCheck() {
log.info("请求健康检查");
try {
boolean indexExists = userEsService.indexExists();
long userCount = userEsService.count();
Map<String, Object> healthInfo = new HashMap<>();
healthInfo.put("status", "UP");
healthInfo.put("indexExists", indexExists);
healthInfo.put("userCount", userCount);
healthInfo.put("timestamp", System.currentTimeMillis());
Map<String, Object> result = createSuccessResult(true, "服务正常");
result.put("health", healthInfo);
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("健康检查异常", e);
Map<String, Object> healthInfo = new HashMap<>();
healthInfo.put("status", "DOWN");
healthInfo.put("error", e.getMessage());
healthInfo.put("timestamp", System.currentTimeMillis());
Map<String, Object> result = createErrorResult("服务异常");
result.put("health", healthInfo);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
}
}
// ==================== 工具方法 ====================
private Map<String, Object> createSuccessResult(boolean success, String message) {
Map<String, Object> result = new HashMap<>();
result.put("success", success);
result.put("message", message);
result.put("timestamp", System.currentTimeMillis());
return result;
}
private Map<String, Object> createErrorResult(String message) {
Map<String, Object> result = new HashMap<>();
result.put("success", false);
result.put("message", message);
result.put("timestamp", System.currentTimeMillis());
return result;
}
}
8. 应用启动类
ESDemoApplication.java
java
package com.example.esdemo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;
import java.net.InetAddress;
import java.net.UnknownHostException;
@Slf4j
@SpringBootApplication
public class ESDemoApplication {
public static void main(String[] args) throws UnknownHostException {
ConfigurableApplicationContext application = SpringApplication.run(ESDemoApplication.class, args);
Environment env = application.getEnvironment();
String ip = InetAddress.getLocalHost().getHostAddress();
String port = env.getProperty("server.port");
String path = env.getProperty("server.servlet.context-path", "");
log.info("\n----------------------------------------------------------\n\t" +
"Spring Boot Elasticsearch Demo 应用启动成功! \n\t" +
"Local: \t\thttp://localhost:" + port + path + "\n\t" +
"External: \thttp://" + ip + ":" + port + path + "\n\t" +
"Elasticsearch: " + env.getProperty("spring.elasticsearch.rest.uris") + "\n\t" +
"API文档: \thttp://" + ip + ":" + port + path + "/api/es/users/health\n" +
"----------------------------------------------------------");
}
}
9. 测试数据初始化
DataInitializer.java
java
package com.example.esdemo.component;
import com.example.esdemo.entity.UserEs;
import com.example.esdemo.service.UserEsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Slf4j
@Component
public class DataInitializer implements CommandLineRunner {
@Autowired
private UserEsService userEsService;
@Override
public void run(String... args) throws Exception {
log.info("开始初始化Elasticsearch测试数据...");
// 检查并创建索引
if (!userEsService.indexExists()) {
log.info("索引不存在,开始创建索引...");
boolean indexCreated = userEsService.createIndex();
log.info("索引创建结果: {}", indexCreated);
// 等待索引创建完成
Thread.sleep(2000);
} else {
log.info("索引已存在,跳过创建");
}
// 检查是否已有数据
long existingCount = userEsService.count();
if (existingCount > 0) {
log.info("已有 {} 条数据,跳过测试数据初始化", existingCount);
return;
}
// 添加测试数据
List<UserEs> testUsers = Arrays.asList(
UserEs.createUser(1L, "zhangsan", "张三", 25, "zhangsan@example.com", "北京市朝阳区", "13800138001"),
UserEs.createUser(2L, "lisi", "李四", 30, "lisi@example.com", "上海市浦东新区", "13800138002"),
UserEs.createUser(3L, "wangwu", "王五", 28, "wangwu@example.com", "广州市天河区", "13800138003"),
UserEs.createUser(4L, "zhaoliu", "赵六", 35, "zhaoliu@example.com", "深圳市南山区", "13800138004"),
UserEs.createUser(5L, "sunqi", "孙七", 22, "sunqi@example.com", "杭州市西湖区", "13800138005"),
UserEs.createUser(6L, "zhouba", "周八", 40, "zhouba@example.com", "成都市武侯区", "13800138006"),
UserEs.createUser(7L, "wujiu", "吴九", 26, "wujiu@example.com", "武汉市江汉区", "13800138007"),
UserEs.createUser(8L, "zhengshi", "郑十", 33, "zhengshi@example.com", "南京市鼓楼区", "13800138008")
);
List<UserEs> savedUsers = userEsService.saveAllUsers(testUsers);
log.info("测试数据初始化完成,共添加 {} 个用户", savedUsers.size());
// 验证数据
long userCount = userEsService.count();
log.info("当前用户总数: {}", userCount);
// 刷新索引确保数据可搜索
userEsService.refreshIndex();
log.info("索引刷新完成,数据初始化流程结束");
}
}
10. 全局异常处理
GlobalExceptionHandler.java
java
package com.example.esdemo.handler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理所有未知异常
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, Object>> handleException(Exception e) {
log.error("系统异常: ", e);
Map<String, Object> result = new HashMap<>();
result.put("success", false);
result.put("message", "系统内部错误: " + e.getMessage());
result.put("timestamp", System.currentTimeMillis());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
}
/**
* 处理运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<Map<String, Object>> handleRuntimeException(RuntimeException e) {
log.error("运行时异常: ", e);
Map<String, Object> result = new HashMap<>();
result.put("success", false);
result.put("message", "业务处理错误: " + e.getMessage());
result.put("timestamp", System.currentTimeMillis());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(result);
}
/**
* 处理非法参数异常
*/
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Map<String, Object>> handleIllegalArgumentException(IllegalArgumentException e) {
log.error("参数异常: ", e);
Map<String, Object> result = new HashMap<>();
result.put("success", false);
result.put("message", "参数错误: " + e.getMessage());
result.put("timestamp", System.currentTimeMillis());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(result);
}
}
11. 日志配置
logback-spring.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<property name="LOG_PATH" value="logs"/>
<property name="LOG_FILE" value="es-demo"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${LOG_FILE}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${LOG_FILE}-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
<logger name="com.example.esdemo" level="DEBUG"/>
<logger name="org.springframework.data.elasticsearch" level="INFO"/>
<logger name="org.elasticsearch" level="WARN"/>
</configuration>
完整项目结构回顾
text
src/main/java/com/example/esdemo/
├── config/
│ └── ElasticsearchConfig.java
├── entity/
│ └── UserEs.java
├── repository/
│ └── UserEsRepository.java
├── service/
│ └── UserEsService.java
├── controller/
│ └── UserEsController.java
├── handler/
│ └── GlobalExceptionHandler.java
├── component/
│ └── DataInitializer.java
└── ESDemoApplication.java
src/main/resources/
├── application.yml
└── logback-spring.xml
这个完整的实例包含了:
-
Spring Boot 2.5.10 自带的 Elasticsearch 集成(无需额外依赖)
-
完整的 CRUD 操作(增删改查)
-
索引管理(创建、删除、检查索引)
-
复杂搜索功能(多条件组合查询)
-
分页查询
-
批量操作
-
统计功能
-
健康检查
-
全局异常处理
-
完整的日志配置
启动应用后,访问 http://localhost:8080/api/es/users/health
即可测试服务是否正常。所有 API 都提供了完整的错误处理和日志记录。
上面的日期格式有点问题,转换后如下所示:
需要修改 DataInitializer 类!因为方案3将日期字段从 LocalDateTime
改为了 Long
类型(时间戳),所以初始化代码需要相应调整。
修正后的 DataInitializer.java
java
package com.example.esdemo.component;
import com.example.esdemo.entity.UserEs;
import com.example.esdemo.service.UserEsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Slf4j
@Component
public class DataInitializer implements CommandLineRunner {
@Autowired
private UserEsService userEsService;
@Override
public void run(String... args) throws Exception {
log.info("=== 开始初始化Elasticsearch测试数据 ===");
// 1. 删除旧索引(避免数据冲突)
log.info("清理旧索引...");
boolean deleted = userEsService.deleteIndex();
log.info("索引删除结果: {}", deleted);
Thread.sleep(2000);
// 2. 创建新索引
log.info("创建新索引...");
boolean indexCreated = userEsService.createIndex();
log.info("索引创建结果: {}", indexCreated);
if (!indexCreated) {
log.error("索引创建失败,停止数据初始化");
return;
}
// 等待索引创建完成
Thread.sleep(3000);
// 3. 检查索引状态
boolean indexExists = userEsService.indexExists();
log.info("索引存在状态: {}", indexExists);
if (!indexExists) {
log.error("索引创建后检查失败,停止数据初始化");
return;
}
// 4. 添加测试数据
log.info("开始添加测试数据...");
List<UserEs> testUsers = createTestUsers();
try {
List<UserEs> savedUsers = userEsService.saveAllUsers(testUsers);
log.info("测试数据添加成功,共添加 {} 个用户", savedUsers.size());
// 5. 等待数据索引完成
Thread.sleep(2000);
// 6. 验证数据
long userCount = userEsService.count();
log.info("数据验证 - 当前用户总数: {}", userCount);
if (userCount == testUsers.size()) {
log.info("✅ 数据初始化成功!");
// 7. 测试查询操作
testQueryOperations();
} else {
log.warn("⚠️ 数据数量不匹配,期望: {}, 实际: {}", testUsers.size(), userCount);
}
} catch (Exception e) {
log.error("❌ 数据初始化失败", e);
// 即使失败也继续测试查询,可能部分数据写入成功
testQueryOperations();
}
log.info("=== Elasticsearch测试数据初始化完成 ===");
}
/**
* 创建测试用户数据
*/
private List<UserEs> createTestUsers() {
// 使用静态工厂方法创建用户,会自动设置时间戳
return Arrays.asList(
UserEs.createUser(1L, "zhangsan", "张三", 25, "zhangsan@example.com", "北京市朝阳区", "13800138001"),
UserEs.createUser(2L, "lisi", "李四", 30, "lisi@example.com", "上海市浦东新区", "13800138002"),
UserEs.createUser(3L, "wangwu", "王五", 28, "wangwu@example.com", "广州市天河区", "13800138003"),
UserEs.createUser(4L, "zhaoliu", "赵六", 35, "zhaoliu@example.com", "深圳市南山区", "13800138004"),
UserEs.createUser(5L, "sunqi", "孙七", 22, "sunqi@example.com", "杭州市西湖区", "13800138005"),
UserEs.createUser(6L, "zhouba", "周八", 40, "zhouba@example.com", "成都市武侯区", "13800138006"),
UserEs.createUser(7L, "wujiu", "吴九", 26, "wujiu@example.com", "武汉市江汉区", "13800138007"),
UserEs.createUser(8L, "zhengshi", "郑十", 33, "zhengshi@example.com", "南京市鼓楼区", "13800138008")
);
}
/**
* 测试查询操作
*/
private void testQueryOperations() {
try {
log.info("开始测试查询操作...");
// 测试1:根据用户ID查询
var userOpt = userEsService.findByUserId(2L);
if (userOpt.isPresent()) {
UserEs user = userOpt.get();
log.info("✅ 根据用户ID查询测试成功: {} - {}", user.getUserId(), user.getUsername());
log.info(" 创建时间戳: {}, 转换为时间: {}",
user.getCreateTime(),
user.getCreateTimeAsLocalDateTime());
} else {
log.warn("⚠️ 根据用户ID查询测试失败 - 用户ID: 2");
}
// 测试2:根据用户名查询
var userOpt2 = userEsService.findByUsername("zhangsan");
if (userOpt2.isPresent()) {
UserEs user = userOpt2.get();
log.info("✅ 根据用户名查询测试成功: {} - {}", user.getUsername(), user.getRealName());
} else {
log.warn("⚠️ 根据用户名查询测试失败 - 用户名: zhangsan");
}
// 测试3:查询所有用户
List<UserEs> allUsers = userEsService.findAll();
log.info("✅ 查询所有用户测试成功: {} 条记录", allUsers.size());
// 测试4:根据年龄范围查询
List<UserEs> ageRangeUsers = userEsService.findByAgeRange(25, 35);
log.info("✅ 根据年龄范围查询测试成功: {} 条记录", ageRangeUsers.size());
// 测试5:统计用户数量
long count = userEsService.count();
log.info("✅ 统计用户数量测试成功: {} 个用户", count);
log.info("所有查询操作测试完成");
} catch (Exception e) {
log.error("查询测试失败", e);
}
}
}
如果还需要手动创建用户数据的补充方法
UserEsService.java - 添加手动创建方法
java
// 在UserEsService类中添加以下方法:
/**
* 手动创建用户(用于测试和调试)
*/
public UserEs createUserManually(Long userId, String username, String realName,
Integer age, String email, String address, String phone) {
UserEs user = new UserEs();
user.setUserId(userId);
user.setUsername(username);
user.setRealName(realName);
user.setAge(age);
user.setEmail(email);
user.setAddress(address);
user.setPhone(phone);
user.setCreateTime(System.currentTimeMillis());
user.setUpdateTime(System.currentTimeMillis());
user.setStatus(true);
return saveUser(user);
}
/**
* 检查并修复现有数据的日期字段(如果之前有旧数据)
*/
public void fixDateFields() {
try {
List<UserEs> allUsers = findAll();
for (UserEs user : allUsers) {
// 如果createTime为null,设置为当前时间戳
if (user.getCreateTime() == null) {
user.setCreateTime(System.currentTimeMillis());
}
// 如果updateTime为null,设置为当前时间戳
if (user.getUpdateTime() == null) {
user.setUpdateTime(System.currentTimeMillis());
}
// 保存修复后的数据
userEsRepository.save(user);
}
log.info("日期字段修复完成,处理了 {} 条记录", allUsers.size());
} catch (Exception e) {
log.error("修复日期字段失败", e);
}
}
完整的重启和测试步骤
-
停止应用(如果正在运行)
-
删除Elasticsearch中的旧索引(确保干净的环境)
bash
# 通过Kibana或curl删除 curl -X DELETE "http://localhost:9200/user_es_index"
-
启动应用 - DataInitializer会自动:
-
删除旧索引(如果存在)
-
创建新索引
-
插入测试数据
-
执行查询测试
-
-
验证数据:
bash
# 检查索引状态 curl -X GET "http://localhost:8080/api/es/index/status" # 查询所有用户 curl -X GET "http://localhost:8080/api/es/users" # 根据用户ID查询 curl -X GET "http://localhost:8080/api/es/users/userId/2"
关键修改点总结
-
删除了所有对LocalDateTime的直接使用 - 改用Long时间戳
-
使用静态工厂方法
UserEs.createUser()
- 自动设置正确的时间戳 -
添加了查询测试 - 在初始化后自动验证数据可查询
-
增加了错误处理 - 即使部分失败也能继续测试
这样修改后,日期转换问题应该完全解决,所有查询操作都能正常工作。