Spring Boot 2.5集成Elasticsearch(亲测)
您说得对!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.javasrc/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_indexsettings:shards: 3replicas: 1# 服务器配置 server:port: 8080servlet:context-path: /# 日志配置 logging:level:com.example.esdemo: DEBUGorg.springframework.data.elasticsearch: DEBUGorg.elasticsearch: WARNpattern: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 {@Idprivate 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 {@Autowiredprivate UserEsRepository userEsRepository;@Autowiredprivate 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 {@Autowiredprivate 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接口 ====================/*** 添加用户*/@PostMappingpublic 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()));}}/*** 查询所有用户*/@GetMappingpublic 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()));}}/*** 更新用户*/@PutMappingpublic 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 {@Autowiredprivate UserEsService userEsService;@Overridepublic 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.javasrc/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 {@Autowiredprivate UserEsService userEsService;@Overridepublic 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()
- 自动设置正确的时间戳添加了查询测试 - 在初始化后自动验证数据可查询
增加了错误处理 - 即使部分失败也能继续测试
这样修改后,日期转换问题应该完全解决,所有查询操作都能正常工作。