完整教程:Spring Boot 2.5集成Elasticsearch(亲测)
2025-10-03 11:04 tlnshuju 阅读(2) 评论(0) 收藏 举报您说得对!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
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.5.10
com.example
es-demo
1.0.0
1.8
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-elasticsearch
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
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 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 {
// ==================== 基本查询方法 ====================
/**
* 根据用户名查询(精确匹配)
*/
Optional findByUsername(String username);
/**
* 根据邮箱查询(精确匹配)
*/
Optional findByEmail(String email);
/**
* 根据手机号查询(精确匹配)
*/
Optional findByPhone(String phone);
/**
* 根据用户ID查询
*/
Optional findByUserId(Long userId);
// ==================== 范围查询方法 ====================
/**
* 根据年龄范围查询
*/
List findByAgeBetween(Integer minAge, Integer maxAge);
/**
* 查询年龄大于指定值的用户
*/
List findByAgeGreaterThan(Integer age);
/**
* 查询年龄小于指定值的用户
*/
List findByAgeLessThan(Integer age);
// ==================== 模糊查询方法 ====================
/**
* 根据真实姓名模糊查询
*/
List findByRealNameContaining(String realName);
/**
* 根据地址模糊查询
*/
List findByAddressContaining(String address);
/**
* 根据地址模糊查询并分页
*/
Page findByAddressContaining(String address, Pageable pageable);
// ==================== 状态查询方法 ====================
/**
* 根据状态查询用户
*/
List findByStatus(Boolean status);
/**
* 根据状态查询用户并分页
*/
Page findByStatus(Boolean status, Pageable pageable);
// ==================== 组合查询方法 ====================
/**
* 根据用户名和年龄查询
*/
List findByUsernameAndAge(String username, Integer age);
/**
* 根据状态和年龄范围查询
*/
List findByStatusAndAgeBetween(Boolean status, Integer minAge, Integer maxAge);
/**
* 根据真实姓名和状态查询
*/
List findByRealNameContainingAndStatus(String realName, Boolean status);
// ==================== 排序查询方法 ====================
/**
* 根据创建时间倒序查询所有用户
*/
List findAllByOrderByCreateTimeDesc();
/**
* 根据年龄升序查询所有用户
*/
List 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 getIndexInfo() {
Map 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 saveAllUsers(List 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 savedUsers = userEsRepository.saveAll(userEsList);
List 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 findById(String id) {
try {
return userEsRepository.findById(id);
} catch (Exception e) {
log.error("根据ID查询用户失败, ID: {}", id, e);
return Optional.empty();
}
}
/**
* 根据用户ID查询
*/
public Optional findByUserId(Long userId) {
try {
return userEsRepository.findByUserId(userId);
} catch (Exception e) {
log.error("根据用户ID查询失败, UserID: {}", userId, e);
return Optional.empty();
}
}
/**
* 根据用户名查询
*/
public Optional findByUsername(String username) {
try {
return userEsRepository.findByUsername(username);
} catch (Exception e) {
log.error("根据用户名查询失败, 用户名: {}", username, e);
return Optional.empty();
}
}
/**
* 查询所有用户
*/
public List findAll() {
try {
Iterable users = userEsRepository.findAll();
return StreamSupport.stream(users.spliterator(), false)
.collect(Collectors.toList());
} catch (Exception e) {
log.error("查询所有用户失败", e);
return Collections.emptyList();
}
}
/**
* 分页查询所有用户
*/
public Page 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 updates) {
try {
Optional 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) 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 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 ids) {
try {
List 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 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 findByStatus(Boolean status) {
try {
return userEsRepository.findByStatus(status);
} catch (Exception e) {
log.error("根据状态查询用户失败, status: {}", status, e);
return Collections.emptyList();
}
}
/**
* 根据地址模糊查询
*/
public List findByAddress(String address) {
try {
return userEsRepository.findByAddressContaining(address);
} catch (Exception e) {
log.error("根据地址查询用户失败, address: {}", address, e);
return Collections.emptyList();
}
}
/**
* 复杂搜索:多条件组合查询
*/
public List 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
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 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 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> handleException(Exception e) {
log.error("系统异常: ", e);
Map 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> handleRuntimeException(RuntimeException e) {
log.error("运行时异常: ", e);
Map 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> handleIllegalArgumentException(IllegalArgumentException e) {
log.error("参数异常: ", e);
Map 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
%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
UTF-8
${LOG_PATH}/${LOG_FILE}.log
${LOG_PATH}/${LOG_FILE}-%d{yyyy-MM-dd}.%i.log
100MB
30
3GB
%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
UTF-8
完整项目结构回顾
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 testUsers = createTestUsers();
try {
List 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 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 allUsers = userEsService.findAll();
log.info("✅ 查询所有用户测试成功: {} 条记录", allUsers.size());
// 测试4:根据年龄范围查询
List 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 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()
- 自动设置正确的时间戳添加了查询测试 - 在初始化后自动验证数据可查询
增加了错误处理 - 即使部分失败也能继续测试
这样修改后,日期转换问题应该完全解决,所有查询操作都能正常工作。