solr使用四(实际业务中结合Spring Data Solr使用,含大量实际示例代码)

Spring Data Solr 更多例子 详见 Spring Data Solr官方文档

一、引入pom文件依赖、修改配置文件

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-solr</artifactId>
            <version>4.1.7.RELEASE</version>
        </dependency>

在application.yml中配置solr的地址

spring:
  data:
    solr:
      host: http://localhost:8983/solr

 

二、使用@SolrDocument注解标注solr中的core对应的实体类

/**
 * @author jacksparrow414
 * @date 2020-05-04
 * @description: TODO
 */
@Builder
@Data
@SolrDocument(collection = "customData")
public class SolrSpringUserEntity {
    @Id
    private String userId;
    @Indexed
    private String name;
    @Indexed
    private Long age;

}

说明:

@SolrDocument中的collection代表solr中core的名字,还有一个solrCoreName,不过官方已经废弃了,建议都使用collection

@Id代表customData中的uniqueKey

@Index代表field中的indexed 和 stored等其他选项,前两个默认为true,具体参数可看下Index注解里的内容就知道了

@Data 和@Builder是Lombok的注解,更多关于Lombok的使用,可参见Lombok官方文档

三、继承官方接口(CrudRepository、PageAndSortingRepository),完成基本的CRUD和分页操作,还可以使用@Query注解,进行自定义查询

/**
 * @author jacksparrow414
 * @date 2020-05-03
 * @description: TODO
 */

public interface SolrSpringService extends CrudRepository<SolrSpringUserEntity,String>,
        PagingAndSortingRepository<SolrSpringUserEntity,String> {

    @Query(value = "userId:*",fields = {"userId","name"})
    List<SolrSpringUserEntity> customQueryOne();

    @Query(value = "name:* AND age:*",filters = {"name:我的 And age:[* TO 19]"})
    List<SolrSpringUserEntity> customQueryTwo();

    @Query(value = "*:*",filters = {"name:我的","age:[* TO 19]"})
    List<SolrSpringUserEntity> customQueryThree();
}

说明:

官方提供了基本的save、delete和分页操作,如果要根据实际业务场景进行灵活查询,则可以使用@Query注解或者SolrTemplate进行灵活组装查询参数

四、使用SolrTemplate进行查询

package com.example.mybatis.demomybatis.solr;

import com.example.mybatis.demomybatis.entity.SolrSpringUserEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.data.solr.core.SolrTemplate;
import org.springframework.data.solr.core.query.SimpleQuery;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

/**
 * @author jacksparrow414
 * @date 2020-05-04
 * @description: TODO
 */
@Service
public class SolrCustomServiceImpl implements SolrCustomService{

    @Autowired
    SolrTemplate solrTemplate;
    @Override
    public List<SolrSpringUserEntity> customFindAll() {
        // 设置查询
        SimpleQuery simpleQuery = new SimpleQuery("*:*");
        simpleQuery.addSort(Sort.by("age").ascending());
        // 调用solrTemplate方法
        Page<SolrSpringUserEntity> query = solrTemplate.query("customData", simpleQuery, SolrSpringUserEntity.class);
        return query.stream().collect(Collectors.toList());
    }

    @Override
    public SolrSpringUserEntity customFindByParams() {
        return null;
    }
}

SolrTemplate提供了丰富的操作solr中数据的方法,可以按需使用

五、测试

/**
 * @author jacksparrow414
 * @date 2020-05-04
 * @description: TODO
 */
@SpringBootTest()
public class SolrSpringTests {
    @Autowired
    SolrSpringService solrSpringService;
    @Autowired
    SolrCustomService solrCustomService;

    @Test
    public void testFindById(){
        Optional.ofNullable(solrSpringService.findById("1977362"))
                .ifPresent(u-> System.out.println(u.get()));
    }

    @Test
    public void testDeleteById(){
        solrSpringService.deleteById("897654");
    }

    @Test
    public void testPage(){
        Page<SolrSpringUserEntity> u = solrSpringService
                .findAll(PageRequest.of(1, 2));
        System.out.println(u.getTotalElements());
        System.out.println(u.getTotalPages());
        u.forEach(System.out::println);
    }

    @Test
    public void testCustomQueryOne(){
        Optional.ofNullable(solrSpringService.customQueryOne())
                .filter(CollectionUtil::isNotEmpty)
                .ifPresent(u-> u.forEach(System.out::println));
    }

    @Test
    public void testCustomQueryTwo(){
        Optional.ofNullable(solrSpringService.customQueryTwo())
                .filter(CollectionUtil::isNotEmpty)
                .ifPresent(u->u.forEach(System.out::println));
    }

    @Test
    public void testCustomQueryThree(){
        Optional.ofNullable(solrSpringService.customQueryThree())
                .filter(CollectionUtil::isNotEmpty)
                .ifPresent(u->u.forEach(System.out::println));
    }

    @Test
    public void testCustomFindAll(){
       Optional.ofNullable(solrCustomService.customFindAll())
               .filter(CollectionUtil::isNotEmpty)
               .ifPresent(u->u.forEach(System.out::println));
    }

}

调用官方分页接口测试:

分页测试

5-总条数

3-总页数

当前页的两条数据

六、Spring对于solr事务的支持,加入@Tranasctional即可

实际业务场景可能是:

接收前端的JSON数据->对MySQL数据库操作->操作成功,去同步操作Solr

Spring的事务注解同样对solr有效,这点还是不错的,不用再去手动提交,而且还和数据库操作处于一个事务中,数据库和solr中的数据操作都同时保证了ACID

/**
 * @author jacksparrow414
 * @date 2020-05-04
 * @description: TODO
 */
@RestController
@RequestMapping(value = "solr")
public class SolrController {

    @Autowired
    private SolrSpringService solrSpringService;
    @Autowired
    private UserService userService;

    @Transactional(rollbackFor = Exception.class)
    @RequestMapping(value = "solrTransaction")
    public void testSaveTransactionByAnnotation(){
        SolrSpringUserEntity springUserEntity = SolrSpringUserEntity.builder()
                .userId("238938212")
                .name("testTransaction")
                .age(122L).build();
        UserEntity userEntity = new UserEntity();
        BeanUtils.copyProperties(springUserEntity,userEntity);
        userEntity.setId(123456);
        userEntity.setAge(12);
        // 入MySQL数据库user表
        userService.addUser(userEntity);
        // 将数据同步到solr中
        solrSpringService.save(springUserEntity);
         int wrong = 1/0;
    }
}

当最后1/0报错时,整个事务回滚,数据库和solr中的数据都没有发生变化

七、根据Spring Data 拓展自定义的Repository.实际场景中,我们可能既想使用Spring Data提供的内置方法,又想加一下自己自定义的方法,最好用一个接口就可以实现。官方也提供了这样的机制。详情见官方文档这里

首先先自定义一个接口和对应的实现类。注意实现类的名字一定要以Impl结尾

/**
 * 自定义的一些solr操作.
 * @author jacksparrow414
 * @date 2020/12/12
 */
public interface ArticleSolrService {
    
    /**
     * 批量删除solr中的数据
     * @param ids solr中的ID集合
     * @return
     */
    boolean deleteSolrDataByIds(Collection<String> ids);
    
    /**
     * 根据查询参数查询solr中的数据
     * @return
     */
    List<ArticleSolrEntity> queryDataByParams(QuerySolrParamDto querySolrParamDto);
}

实现类如下:

/**
 * 注意:类名必须以Impl结尾.见<a href="https://docs.spring.io/spring-data/solr/docs/current/reference/html/#repositories.single-repository-behavior">官方文档</a>
 * @author jacksparrow414
 * @date 2020/12/12
 */
@Service
public class ArticleSolrServiceImpl implements ArticleSolrService {
    
    @Autowired
    private SolrTemplate solrTemplate;
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean deleteSolrDataByIds(final Collection<String> ids) {
        Preconditions.checkArgument(CollectionUtil.isNotEmpty(ids), "collection must not be null");
        UpdateResponse response = solrTemplate.deleteByIds("article", ids);
        // 记得提交,否则不会生效
        solrTemplate.commit("article");
        return response.getStatus() == 0;
    }
    
    @Override
    public List<ArticleSolrEntity> queryDataByParams(final QuerySolrParamDto querySolrParamDto) {
        Query simpleQuery = buildSimpleQuery(querySolrParamDto);
        Page<ArticleSolrEntity> articles = solrTemplate.queryForPage("article", simpleQuery, ArticleSolrEntity.class);
        return articles.getContent();
    }
    
    /**
     * 通过具体参数构造Query.
     * 这里为什么没有根据条件拼接具体的queryString?而是直接使用默认的*:*<br/>
     * 根据stackOverFlow上的回答,使用fq效果更好.因为fq可以利用到FilterCache,可以大大提高查询性能
     * <a href="https://stackoverflow.com/questions/11627427/solr-query-q-or-filter-query-fq">stackOverFlow关于此问题的地址</a><br/>
     *
     * 关于Criteria更多使用,可以点进去看下,有很多,这里只使用几种.<br/>
     * 通过PageRequest.of(),方法构造分页时,spring data默认的分是从0开始。而一般我们传的参数都是从1开始,所以这里-1<br/>
     * 通过Sort指定排序规则
     * @param querySolrParamDto 查询参数
     * @return SimpleQuery
     */
    private SimpleQuery buildSimpleQuery(final QuerySolrParamDto querySolrParamDto) {
        Preconditions.checkNotNull(querySolrParamDto.getPageNo(), "pageNo must not be null");
        Preconditions.checkNotNull(querySolrParamDto.getPageSize(), "pageSize must not be null");
        SimpleQuery result = new SimpleQuery("*:*");
        Pageable pageable = PageRequest.of(querySolrParamDto.getPageNo()-1,
            querySolrParamDto.getPageSize(),
            Sort.by(SearchableArticleSolrDefinition.ID_FIELD).ascending());
        result.setPageRequest(pageable);
        if (ObjectUtil.isNotNull(querySolrParamDto.getId())) {
            Criteria criteria = new Criteria(SearchableArticleSolrDefinition.ID_FIELD);
            criteria.is(querySolrParamDto.getId());
            result.addFilterQuery(buildSimpleFilterQuery(criteria));
        }
        if (StrUtil.isNotBlank(querySolrParamDto.getAuthorName())) {
            Criteria criteria = new Criteria(SearchableArticleSolrDefinition.AUTHOR_NAME_FIELD);
            criteria.contains(querySolrParamDto.getAuthorName());
            result.addFilterQuery(buildSimpleFilterQuery(criteria));
        }
        if (CollectionUtil.isNotEmpty(querySolrParamDto.getSubjectId())) {
            Criteria criteria = new Criteria(SearchableArticleSolrDefinition.SUBJECT_ID_FIELD);
            criteria.in(querySolrParamDto.getSubjectId());
            result.addFilterQuery(buildSimpleFilterQuery(criteria));
        }
        // solr中的词组查询,用""包裹起来
        if (StrUtil.isNotBlank(querySolrParamDto.getContent())) {
            StringBuilder phraseQueryString = new StringBuilder(SearchableArticleSolrDefinition.CONTENT_FIELD);
            phraseQueryString.append(":").append("\"").append(querySolrParamDto.getContent()).append("\"");
            Criteria criteria = new SimpleStringCriteria(phraseQueryString.toString());
            result.addFilterQuery(buildSimpleFilterQuery(criteria));
        }
        return result;
    }
    
    /**
     * 通过Criteria构建SimpleFilterQuery.
     * @param criteria 具体的过滤条件
     * @return SimpleFilterQuery
     */
    private FilterQuery buildSimpleFilterQuery(final Criteria criteria) {
        SimpleFilterQuery result = new SimpleFilterQuery();
        result.addCriteria(criteria);
        return result;
    }
}

再定义一个公共接口,继承官方的接口、分页接口和自定义的接口ArticleSolrService

/**
 * 自定义拓展spring data repository.可以定义自己的接口,
 * 然后让该接口继承自定义的接口,直接使用该接口,即可达到又使用官方提供的方法又使用自己的自定义方法<br/>
 * <a href="https://docs.spring.io/spring-data/solr/docs/current/reference/html/#repositories.custom-implementations">官方文档地址</a>
 * @author jacksparrow414
 * @date 2020/12/12
 */
public interface ArticleRepository extends CrudRepository<ArticleSolrEntity, String>,
    PagingAndSortingRepository<ArticleSolrEntity, String>, ArticleSolrService {
}

 接下来就可以使用了。使用示例如下。第一个方法是官方的,后面的方法是我们自定义的。只引入ArticleRepository这一个接口即可

/**
 * @author jacksparrow414
 * @date 2020/12/12
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public final class SolrExtendTest {
    
    @Autowired
    private ArticleRepository articleRepository;
    
    @Test
    public void assertSaveBatchToSolr() {
        ArticleSolrEntity first = new ArticleSolrEntity();
        first.setId(IdUtil.simpleUUID());
        first.setContent("this is first article");
        first.setAuthorName("jack");
        first.setArticleName("jack article");
        first.setDel(0);
        first.setSubjectId(Lists.newArrayList(1, 2));
        ArticleSolrEntity second = new ArticleSolrEntity();
        second.setId(IdUtil.simpleUUID());
        second.setContent("this is second article");
        second.setAuthorName("mark");
        second.setArticleName("mark article");
        second.setDel(0);
        second.setSubjectId(Lists.newArrayList(2, 3, 4));
        articleRepository.saveAll(Lists.newArrayList(first, second));
    }
    
    @Test
    public void assertDeleteBatchByIds() {
        boolean actual = articleRepository.deleteSolrDataByIds(Lists.newArrayList("777777", "23128391283"));
        Assert.assertTrue(actual);
    }
    
    @Test
    public void assertQueryDataByParams() {
        QuerySolrParamDto querySolrParamDto = new QuerySolrParamDto();
//        querySolrParamDto.setId("98f1d57e708c425bb9c7d1e4f4b35150");
        querySolrParamDto.setAuthorName("k");
//        querySolrParamDto.setSubjectId(Lists.newArrayList(2));
        querySolrParamDto.setContent("this is second");
        querySolrParamDto.setPageNo(1);
        querySolrParamDto.setPageSize(5);
        List<ArticleSolrEntity> actual = articleRepository.queryDataByParams(querySolrParamDto);
        Assert.assertTrue(CollectionUtil.isNotEmpty(actual));
    }
}

 温馨提示:

1、在使用SolrTemplate时,可以将对应的日志级别调成debug模式,会看到最终的查询语句。很方便的可以看到自己是不是写对了

logging:
   level:
     org.springframework.data: debug

如下图: 

2、根据Spring官网最新显示。Spring Data Solr准备于2022年停止维护。另一方面,Spring Data对于Elastaic Search的支持倒是一直都有。也侧面说明了一些情况。solr在新时代的搜索方面确实有些不足了。新生代Elastaic Search势头正猛。不过入门ES之前懂一点solr也没有任何损失,学起来还容易些。因为二者的语法都很相似

 

最后注意:如果是公司内部大规模使用,建议建立统一的solr使用规范。使得Solr使用规范化,否则很容易出现有的地方在用SolrClient,有的地方在用Spring Data Solr.用的地方在用手写方式拼接查询,有的地方使用Spring Data Solr的Criterial进行构造查询的情况

所有单元测试和其他代码已放到Github上,如有需要,请自取。如有问题,欢迎留言,交流讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值