自定义数据索引:基于MyBatis-Plus实现全文搜索和模糊查询

自定义数据索引:基于MyBatis-Plus实现全文搜索和模糊查询

【免费下载链接】mybatis-plus An powerful enhanced toolkit of MyBatis for simplify development 【免费下载链接】mybatis-plus 项目地址: https://gitcode.com/gh_mirrors/my/mybatis-plus

引言:为什么需要自定义数据索引?

在日常开发中,我们经常面临这样的场景:用户需要在海量数据中快速找到相关信息,传统的精确匹配查询已经无法满足需求。无论是电商平台的商品搜索、内容管理系统的文章检索,还是企业应用的数据查询,高效的全文搜索和模糊查询能力都至关重要。

MyBatis-Plus作为MyBatis的增强工具,提供了强大的条件构造器和灵活的查询能力。本文将深入探讨如何基于MyBatis-Plus构建自定义数据索引,实现高效的全文搜索和模糊查询功能。

一、MyBatis-Plus模糊查询基础

1.1 SqlLike枚举详解

MyBatis-Plus提供了SqlLike枚举来支持不同类型的模糊查询:

public enum SqlLike {
    /**
     * %值 - 左模糊匹配
     */
    LEFT,
    /**
     * 值% - 右模糊匹配  
     */
    RIGHT,
    /**
     * %值% - 全模糊匹配
     */
    DEFAULT
}

1.2 基础模糊查询示例

// 创建查询包装器
QueryWrapper<User> queryWrapper = new QueryWrapper<>();

// 全模糊匹配 - 搜索包含"张"的用户名
queryWrapper.like("username", "张");

// 左模糊匹配 - 搜索以"com"结尾的邮箱
queryWrapper.likeLeft("email", "com");

// 右模糊匹配 - 搜索以"138"开头的手机号  
queryWrapper.likeRight("phone", "138");

// 使用Lambda表达式
LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
lambdaWrapper.like(User::getUsername, "张");

1.3 多字段联合搜索

// 多字段模糊查询
QueryWrapper<Article> wrapper = new QueryWrapper<>();
wrapper.like("title", keyword)
       .or()
       .like("content", keyword)
       .or() 
       .like("tags", keyword);

// 对应的SQL: 
// WHERE title LIKE '%keyword%' OR content LIKE '%keyword%' OR tags LIKE '%keyword%'

二、构建自定义全文搜索索引

2.1 搜索索引表设计

为了实现高效的全文搜索,我们需要设计专门的搜索索引表:

CREATE TABLE search_index (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    entity_type VARCHAR(50) NOT NULL COMMENT '实体类型',
    entity_id BIGINT NOT NULL COMMENT '实体ID',
    search_content TEXT NOT NULL COMMENT '搜索内容',
    weight INT DEFAULT 1 COMMENT '权重',
    created_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_entity_type (entity_type),
    INDEX idx_search_content (search_content(255)),
    INDEX idx_entity (entity_type, entity_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2.2 索引构建策略

@Service
public class SearchIndexService {
    
    @Autowired
    private SearchIndexMapper searchIndexMapper;
    
    /**
     * 构建文章搜索索引
     */
    public void buildArticleIndex(Article article) {
        SearchIndex index = new SearchIndex();
        index.setEntityType("article");
        index.setEntityId(article.getId());
        
        // 组合搜索内容:标题+内容+标签
        String searchContent = article.getTitle() + " " + 
                             article.getContent() + " " +
                             String.join(" ", article.getTags());
        
        index.setSearchContent(searchContent);
        index.setWeight(calculateWeight(article));
        
        searchIndexMapper.insertOrUpdate(index);
    }
    
    /**
     * 计算搜索权重
     */
    private int calculateWeight(Article article) {
        int weight = 1;
        // 根据发布时间、浏览量等计算权重
        if (article.getPublishTime().after(DateUtils.addDays(new Date(), -7))) {
            weight += 2; // 近期文章权重更高
        }
        if (article.getViewCount() > 1000) {
            weight += 1; // 热门文章权重更高
        }
        return weight;
    }
}

2.3 索引更新机制

@Component
public class SearchIndexListener {
    
    @EventListener
    public void handleArticleSave(ArticleSavedEvent event) {
        searchIndexService.buildArticleIndex(event.getArticle());
    }
    
    @EventListener  
    public void handleArticleDelete(ArticleDeletedEvent event) {
        searchIndexService.deleteIndex("article", event.getArticleId());
    }
}

三、高级搜索功能实现

3.1 分词搜索实现

public class WordSegmenter {
    
    private static final Pattern WORD_PATTERN = Pattern.compile("\\w+", Pattern.UNICODE_CHARACTER_CLASS);
    
    /**
     * 简单中文分词
     */
    public static List<String> segmentChinese(String text) {
        List<String> words = new ArrayList<>();
        if (StringUtils.isBlank(text)) {
            return words;
        }
        
        // 简单按字符分割,实际项目中应使用专业分词库
        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);
            if (isChineseCharacter(c)) {
                words.add(String.valueOf(c));
            }
        }
        return words;
    }
    
    /**
     * 英文分词
     */
    public static List<String> segmentEnglish(String text) {
        List<String> words = new ArrayList<>();
        if (StringUtils.isBlank(text)) {
            return words;
        }
        
        Matcher matcher = WORD_PATTERN.matcher(text);
        while (matcher.find()) {
            words.add(matcher.group().toLowerCase());
        }
        return words;
    }
    
    private static boolean isChineseCharacter(char c) {
        Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
        return ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS 
            || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
            || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A;
    }
}

3.2 权重排序搜索

@Service
public class AdvancedSearchService {
    
    @Autowired
    private SearchIndexMapper searchIndexMapper;
    
    /**
     * 高级搜索:支持分词、权重排序
     */
    public Page<SearchResult> advancedSearch(String keyword, Pageable pageable) {
        // 分词处理
        List<String> keywords = WordSegmenter.segmentChinese(keyword);
        keywords.addAll(WordSegmenter.segmentEnglish(keyword));
        
        if (keywords.isEmpty()) {
            return new PageImpl<>(Collections.emptyList());
        }
        
        // 构建查询条件
        QueryWrapper<SearchIndex> wrapper = new QueryWrapper<>();
        for (String word : keywords) {
            wrapper.like("search_content", word);
        }
        
        // 按权重降序排序
        wrapper.orderByDesc("weight");
        
        // 执行查询
        Page<SearchIndex> indexPage = searchIndexMapper.selectPage(
            new Page<>(pageable.getPageNumber(), pageable.getPageSize()), 
            wrapper
        );
        
        // 转换为搜索结果
        return convertToSearchResult(indexPage);
    }
}

3.3 搜索建议功能

@RestController
@RequestMapping("/api/search")
public class SearchController {
    
    @Autowired
    private SearchService searchService;
    
    /**
     * 搜索建议
     */
    @GetMapping("/suggestions")
    public List<String> getSuggestions(@RequestParam String query) {
        if (StringUtils.isBlank(query) || query.length() < 2) {
            return Collections.emptyList();
        }
        
        return searchService.getSuggestions(query.trim(), 10);
    }
    
    /**
     * 热门搜索词
     */
    @GetMapping("/hot-keywords")
    public List<String> getHotKeywords() {
        return searchService.getHotKeywords(20);
    }
}

四、性能优化策略

4.1 数据库索引优化

-- 创建全文索引(MySQL 5.6+)
ALTER TABLE search_index 
ADD FULLTEXT INDEX idx_fulltext_search (search_content)
WITH PARSER ngram;

-- 查询使用全文索引
SELECT * FROM search_index 
WHERE MATCH(search_content) AGAINST('关键词' IN NATURAL LANGUAGE MODE);

4.2 查询性能优化

public class SearchOptimizer {
    
    /**
     * 查询条件优化
     */
    public static QueryWrapper<SearchIndex> optimizeQuery(String keyword) {
        QueryWrapper<SearchIndex> wrapper = new QueryWrapper<>();
        
        // 关键词长度过滤
        if (keyword.length() < 2) {
            return wrapper; // 返回空条件
        }
        
        // 根据关键词长度选择不同的搜索策略
        if (keyword.length() <= 5) {
            // 短关键词使用精确匹配
            wrapper.like("search_content", keyword);
        } else {
            // 长关键词使用分词搜索
            List<String> words = WordSegmenter.segmentChinese(keyword);
            for (String word : words) {
                wrapper.like("search_content", word);
            }
        }
        
        return wrapper;
    }
    
    /**
     * 分页优化
     */
    public static Page<SearchIndex> optimizePagination(Pageable pageable) {
        // 限制最大分页大小
        int pageSize = Math.min(pageable.getPageSize(), 100);
        return new Page<>(pageable.getPageNumber(), pageSize);
    }
}

4.3 缓存策略

@Service
@CacheConfig(cacheNames = "searchCache")
public class CachedSearchService {
    
    @Autowired
    private SearchService searchService;
    
    /**
     * 带缓存的搜索
     */
    @Cacheable(key = "#keyword + ':' + #pageable.pageNumber + ':' + #pageable.pageSize")
    public Page<SearchResult> cachedSearch(String keyword, Pageable pageable) {
        return searchService.search(keyword, pageable);
    }
    
    /**
     * 清除搜索缓存
     */
    @CacheEvict(allEntries = true)
    public void clearCache() {
        // 清除所有搜索缓存
    }
}

五、实战案例:电商商品搜索

5.1 商品搜索索引构建

@Component
public class ProductIndexBuilder {
    
    @Autowired
    private SearchIndexMapper searchIndexMapper;
    
    @Transactional
    public void buildProductIndex(Product product) {
        SearchIndex index = new SearchIndex();
        index.setEntityType("product");
        index.setEntityId(product.getId());
        
        // 构建搜索内容:名称+品牌+分类+属性
        StringBuilder content = new StringBuilder();
        content.append(product.getName()).append(" ");
        content.append(product.getBrand()).append(" ");
        content.append(product.getCategory()).append(" ");
        
        // 添加属性信息
        product.getAttributes().forEach((key, value) -> {
            content.append(key).append(" ").append(value).append(" ");
        });
        
        index.setSearchContent(content.toString());
        index.setWeight(calculateProductWeight(product));
        
        searchIndexMapper.insertOrUpdate(index);
    }
    
    private int calculateProductWeight(Product product) {
        int weight = 1;
        if (product.getSales() > 1000) weight += 3;
        if (product.getRating() >= 4.5) weight += 2;
        if (product.isNewArrival()) weight += 1;
        return weight;
    }
}

5.2 商品搜索服务

@Service
public class ProductSearchService {
    
    @Autowired
    private ProductMapper productMapper;
    
    @Autowired
    private SearchIndexMapper searchIndexMapper;
    
    public Page<Product> searchProducts(String keyword, ProductSearchRequest request) {
        // 搜索索引表
        QueryWrapper<SearchIndex> indexWrapper = new QueryWrapper<>();
        indexWrapper.eq("entity_type", "product")
                   .like("search_content", keyword);
        
        // 按权重排序
        indexWrapper.orderByDesc("weight");
        
        Page<SearchIndex> indexPage = searchIndexMapper.selectPage(
            new Page<>(request.getPage(), request.getSize()),
            indexWrapper
        );
        
        // 获取商品ID列表
        List<Long> productIds = indexPage.getRecords().stream()
            .map(SearchIndex::getEntityId)
            .collect(Collectors.toList());
        
        if (productIds.isEmpty()) {
            return new PageImpl<>(Collections.emptyList());
        }
        
        // 查询商品详细信息
        QueryWrapper<Product> productWrapper = new QueryWrapper<>();
        productWrapper.in("id", productIds);
        
        // 添加其他筛选条件
        if (StringUtils.isNotBlank(request.getCategory())) {
            productWrapper.eq("category", request.getCategory());
        }
        if (request.getMinPrice() != null) {
            productWrapper.ge("price", request.getMinPrice());
        }
        if (request.getMaxPrice() != null) {
            productWrapper.le("price", request.getMaxPrice());
        }
        
        List<Product> products = productMapper.selectList(productWrapper);
        
        // 保持搜索结果的排序
        Map<Long, Product> productMap = products.stream()
            .collect(Collectors.toMap(Product::getId, Function.identity()));
        
        List<Product> sortedProducts = productIds.stream()
            .filter(productMap::containsKey)
            .map(productMap::get)
            .collect(Collectors.toList());
        
        return new PageImpl<>(sortedProducts, PageRequest.of(request.getPage(), request.getSize()), indexPage.getTotal());
    }
}

六、监控与维护

6.1 搜索性能监控

@Aspect
@Component
@Slf4j
public class SearchPerformanceMonitor {
    
    @Around("execution(* com.example.service..*.search*(..))")
    public Object monitorSearchPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        
        try {
            Object result = joinPoint.proceed();
            long duration = System.currentTimeMillis() - startTime;
            
            // 记录搜索性能日志
            logSearchPerformance(methodName, args, duration, true);
            
            return result;
        } catch (Exception e) {
            long duration = System.currentTimeMillis() - startTime;
            logSearchPerformance(methodName, args, duration, false);
            throw e;
        }
    }
    
    private void logSearchPerformance(String methodName, Object[] args, long duration, boolean success) {
        log.info("SearchPerformance - method: {}, duration: {}ms, success: {}, args: {}",
                methodName, duration, success, Arrays.toString(args));
        
        // 可以发送到监控系统
        Metrics.counter("search.performance")
              .tag("method", methodName)
              .tag("success", String.valueOf(success))
              .record(duration);
    }
}

6.2 索引维护任务

@Component
@Slf4j
public class IndexMaintenanceTask {
    
    @Autowired
    private SearchIndexMapper searchIndexMapper;
    
    /**
     * 每天凌晨清理过期索引
     */
    @Scheduled(cron = "0 0 2 * * ?")
    public void cleanupExpiredIndexes() {
        log.info("开始清理过期搜索索引...");
        
        // 删除30天前的索引记录
        LocalDateTime expireTime = LocalDateTime.now().minusDays(30);
        QueryWrapper<SearchIndex> wrapper = new QueryWrapper<>();
        wrapper.lt("updated_time", expireTime);
        
        int deletedCount = searchIndexMapper.delete(wrapper);
        
        log.info("清理完成,共删除{}条过期索引记录", deletedCount);
    }
    
    /**
     * 每周重建权重
     */
    @Scheduled(cron = "0 0 3 * * 1") // 每周一凌晨3点
    public void rebuildWeights() {
        log.info("开始重建搜索索引权重...");
        
        // 根据业务逻辑重新计算权重
        // 这里可以根据销量、评分、时间等因素重新计算权重
        
        log.info("权重重建完成");
    }
}

七、总结与最佳实践

通过本文的探讨,我们了解了如何基于MyBatis-Plus构建强大的全文搜索和模糊查询系统。以下是关键的最佳实践:

7.1 设计原则

  1. 分离关注点:将搜索索引与业务数据分离,避免影响主业务表性能
  2. 适度冗余:在搜索索引中存储必要的冗余信息以提高查询性能
  3. 异步处理:索引构建和更新采用异步方式,避免阻塞主业务流程

7.2 性能优化

  1. 合理使用索引:为搜索字段创建合适的数据库索引
  2. 查询优化:避免全表扫描,使用分页限制结果集大小
  3. 缓存策略:对热门搜索结果进行缓存,减少数据库压力

7.3 可维护性

  1. 监控告警:建立完善的监控体系,及时发现性能问题
  2. 定期维护:设置定时任务进行索引清理和优化
  3. 版本控制:对搜索算法和索引结构进行版本管理

通过遵循这些最佳实践,您可以构建出高效、稳定、可扩展的全文搜索系统,为用户提供卓越的搜索体验。


进一步优化建议

  • 考虑引入专业的搜索引擎如Elasticsearch处理海量数据搜索
  • 实现搜索词推荐和自动补全功能
  • 添加搜索分析功能,了解用户搜索行为模式
  • 支持多语言搜索和国际化需求

MyBatis-Plus的强大功能结合合理的架构设计,能够帮助您构建出满足各种复杂搜索需求的系统。

【免费下载链接】mybatis-plus An powerful enhanced toolkit of MyBatis for simplify development 【免费下载链接】mybatis-plus 项目地址: https://gitcode.com/gh_mirrors/my/mybatis-plus

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值