简介
Spring Data Solr就是为了方便Solr的开发所研制的一个 框架,其底层是对SolrJ(官方 API)的封装。SolrJ的使用是通过它提供的若干接口,而Spring Data Solr 是Spring调用Solr接口的 进一步封装,简化了Solr的使用,可以通过Spring Data Solr提供的对象HttpSolrServer对文档索引的创建、搜索、分组、排序、分页等等进行操控。功能还是较为完善的。
Spring Data Solr使得 SolrJ用起来更简单, 可以把JavaBean通过注解的方式直接从Solr查询出来和写入.就和ORM框架做的工作很相似。这两个包有个版本对应关系,使用的时候要注意:
spring-data-solr | solr-solrj ————————-+—————- 2.1.15 | 5.5.0 3.0.10 | 6.5.0 4.0.0 | 7.4.0 4.2.3 | 8.5.2
Spring Data Solr就是把Solr的应用更好的集成到Spring框架中,它的使用方法就是使用SolrTemplate这个类来对索引库进行CRUD的操作,实际上是由SolrOperations这个接口里面的方法来操作的。
工程应用
方式1:SpringBoot + Spring Data Solr
1. 添加pom.xml依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-solr</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
2. Solr配置
■ 通过Solr URL连接
application.yml
spring:application:name: solr-springdata-example01data:solr:host: http://bigdata-node1:8983/solr/db_syncserver:port: 8081
application.properties
spring.application.name=solr-springdata-example01server.port=8081# solr Configurationspring.data.solr.host=http://bigdata-node1:8983/solr/db_sync
■ 通过ZooKeeper连接
application.yml
spring:application:name: solr-springdata-example01data:solr:zk-host: 192.168.0.101:2181,192.168.0.102:2181,192.168.0.103:2181server:port: 8081
application.properties
spring.application.name=solr-springdata-example01server.port=8083# solr Configurationspring.data.solr.zk-host=192.168.0.101:2181,192.168.0.102:2181,192.168.0.103:2181spring.data.solr.core=db_sync
3. 编写关键类
■ 配置类(SolrConfig.java) ```java import org.apache.solr.client.solrj.impl.CloudSolrClient; import org.apache.solr.common.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.solr.core.SolrTemplate; import java.util.Arrays; import java.util.Optional;
/**
@Description 自定义配置类(SolrTemplate) */ @Configuration public class SolrConfig {
@Value(“${spring.data.solr.zk-host}”) private String zk_host;
@Value(“${spring.data.solr.core}”) private String solrCore;
/**
- 配置SolrTemplate
*/
@Bean
public SolrTemplate solrTemplate() {
if (StringUtils.isEmpty(zk_host)) {
} CloudSolrClient client = new CloudSolrClient.Builder(return null;
).build(); // 设置默认的操作实例 client.setDefaultCollection(solrCore); client.setZkClientTimeout(5000); client.setZkConnectTimeout(5000); SolrTemplate template = new SolrTemplate(client); return template; }Arrays.asList(zk_host.split(",")),Optional.empty()
- 配置SolrTemplate
*/
@Bean
public SolrTemplate solrTemplate() {
if (StringUtils.isEmpty(zk_host)) {
}
**■ 实体类(Article.java)**```javaimport lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import org.apache.solr.client.solrj.beans.Field;import org.springframework.data.solr.core.mapping.SolrDocument;import java.io.Serializable;/*** Description VO-文章类*/@Data@AllArgsConstructor@NoArgsConstructor@SolrDocument(solrCoreName = "db_sync")public class Article implements Serializable {@Field("id")private Integer id;@Field("content_id")private Long contentId;@Field("title")private String title;@Field("content")private String content;@Field("type")private Integer type;@Field("create_at")private Long createAt;@Field("publish_at")private Long publishAt;@Overridepublic String toString() {return "Article{" +"id=" + id +", contentId=" + contentId +", title='" + title + '\'' +", content='" + content + '\'' +", type=" + type +", createAt=" + createAt +", publishAt=" + publishAt +'}';}}
注解说明:spring.application.name=solr-springdata-example01server.port=8083# solr Configurationspring.data.solr.zk-host=192.168.0.101:2181,192.168.0.102:2181,192.168.0.103:2181spring.data.solr.core=db_sync
- @Field:注解标识 。
如果属性与配置文件定义的域名称不一致,需要在注解中指定域名称。
- @Dynamic:声明这是一个动态域。
在代码层,将数据库中某一字段中文字解析成map对象,将给对象封装到实体类中即可完成动态域的配置。
// 示例// 将spec字段中的json字符串转换为map,字段内容{"内存":"64G","网络":"4G"}Map specMap = JSON.parseObject(item.getSpec());// 给带注解的字段赋值item.setSpecMap(specMap);
- @SolrDocument:指定集合的名称。

■ 服务接口(SolrTemplateService.java)
import org.springframework.data.solr.core.query.Criteria;import org.springframework.data.solr.core.query.SimpleFilterQuery;import java.lang.reflect.InvocationTargetException;import java.util.List;import java.util.Map;public interface SolrTemplateService {/*** 将JavaBean对象添加(新增或修改)到Solr中** @param bean VO/DTO/Pojo/Domain对象* @param <T> 泛型(对象类型)* @return*/<T> boolean saveOrUpdate(T bean);/*** 将Map对象添加(新增或修改)到Solr中** @param map Map对象* @param clz JavaBean的Class对象* @param <T> 泛型(对象类型)* @return*/<T> boolean saveOrUpdate(Map map, Class<T> clz);/*** 通过文档ID删除Solr中的文档** @param id 索引ID* @return*/boolean deleteById(String id);/*** 通过查询删除Solr中对应的数据集合** @param query 查询条件*/boolean deleteByQuery(String query);/*** 批量新增或更新记录** @param entities 新增或更新对象列表* @param <T> 泛型* @return*/<T> boolean batchSaveOrUpdate(List<T> entities);/*** 分页查询** @param clz 泛型类对应java.lang.Class* @param offset 开始索引(默认:0)* @param rows 每页显示记录数(默认:10)* @return*/<T> SolrResultInfo<T> queryForPage(Class<T> clz, Long offset, Integer rows);/*** 高亮查询** @param clz 泛型类对应java.lang.Class* @param keywords 关键字* @param hlFieldsList 高亮字段列表* @param <T> 泛型* @return*/<T> SolrResultInfo<T> queryHeightLight(Class<T> clz, String keywords, List<String> hlFieldsList) throws InvocationTargetException, IllegalAccessException;/*** 通过泛型获取Solr中的对象集合** @param clz 泛型类对应java.lang.Class* @param criteria 查询条件* @param hlFieldsList 高亮显示数据域名称,是List<String>集合* @param fqList 过滤条件* @param offset 开始索引(默认:0)* @param rows 每页显示记录数(默认:10)* @return*/<T> SolrResultInfo<T> query(Class<T> clz, Criteria criteria, List<String> hlFieldsList, List<SimpleFilterQuery> fqList, Long offset, Integer rows) throws InvocationTargetException, IllegalAccessException;/*** 根据ID获取记录** @param id 索引ID* @param clz Class对象* @param <T> 泛型* @return*/<T> T getById(String id, Class<T> clz);}
■ 服务接口实现类(SolrTemplateServiceImpl.java)
import com.lonton.bigdata.service.SolrResultInfo;import com.lonton.bigdata.service.SolrTemplateService;import com.lonton.bigdata.util.CommonUtils;import org.apache.commons.beanutils.BeanUtils;import org.apache.solr.client.solrj.response.UpdateResponse;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.data.domain.Page;import org.springframework.data.solr.core.SolrTemplate;import org.springframework.data.solr.core.query.*;import org.springframework.data.solr.core.query.result.HighlightEntry;import org.springframework.data.solr.core.query.result.HighlightPage;import org.springframework.data.solr.core.query.result.ScoredPage;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import java.lang.reflect.InvocationTargetException;import java.util.List;import java.util.Map;import java.util.Optional;@Servicepublic class SolrTemplateServiceImpl implements SolrTemplateService {private static final Logger log = LoggerFactory.getLogger(SolrTemplateServiceImpl.class);@Value("${spring.data.solr.core}")private String solrCore;@Autowiredprivate SolrTemplate solrTemplate;/*** 将JavaBean对象添加(新增或修改)到Solr中** @param bean VO/DTO/Pojo/Domain对象* @param <T> 泛型(对象类型)* @return*/@Override@Transactional(rollbackFor = Exception.class)public <T> boolean saveOrUpdate(T bean) {try {// 将对象存储到索引库中UpdateResponse resp = solrTemplate.saveBean(solrCore, bean);// 提交solrTemplate.commit(solrCore);if (resp.getStatus() == 0) {log.debug("saveOrUpdate successfully!");return true;}} catch (Exception e) {log.error(e.getMessage());e.printStackTrace();}log.debug("saveOrUpdate failure!");return false;}/*** 将Map对象添加(新增或修改)到Solr中** @param map Map对象* @param clz JavaBean的Class对象* @param <T> 泛型(对象类型)* @return*/@Override@Transactional(rollbackFor = Exception.class)public <T> boolean saveOrUpdate(Map map, Class<T> clz) {try {T bean = CommonUtils.mapToBean((Map<String, ?>) map, clz);// 将对象存储到索引库中UpdateResponse resp = solrTemplate.saveBean(solrCore, bean);// 提交solrTemplate.commit(solrCore);if (resp.getStatus() == 0) {log.debug("saveOrUpdate successfully!");return true;}} catch (Exception e) {log.error(e.getMessage());e.printStackTrace();}log.debug("saveOrUpdate failure!");return false;}/*** 通过文档ID删除Solr中的文档** @param id 索引ID* @return*/@Override@Transactional(rollbackFor = Exception.class)public boolean deleteById(String id) {try {UpdateResponse resp = solrTemplate.deleteByIds(solrCore, id);solrTemplate.commit(solrCore);if (resp.getStatus() == 0) {log.debug("delete successfully!");return true;}} catch (Exception e) {log.error(e.getMessage());e.printStackTrace();}log.debug("delete failure!");return false;}/*** 通过查询删除Solr中对应的数据集合** @param query 查询条件*/@Override@Transactional(rollbackFor = Exception.class)public boolean deleteByQuery(String query) {try {UpdateResponse resp = solrTemplate.delete(solrCore, new SimpleQuery(query));solrTemplate.commit(solrCore);if (resp.getStatus() == 0) {log.debug("delete successfully!");return true;}} catch (Exception e) {log.error(e.getMessage());e.printStackTrace();}log.debug("delete failure!");return false;}/*** 批量新增或更新记录** @param entities 新增或更新对象列表* @param <T> 泛型* @return*/@Overridepublic <T> boolean batchSaveOrUpdate(List<T> entities) {try {// 将对象存储到索引库中UpdateResponse resp = solrTemplate.saveBeans(solrCore, entities);// 提交solrTemplate.commit(solrCore);if (resp.getStatus() == 0) {log.debug("batchSaveOrUpdate successfully!");return true;}} catch (Exception e) {log.error(e.getMessage());e.printStackTrace();}log.debug("batchSaveOrUpdate failure!");return false;}/*** 分页查询** @param clz 泛型类对应java.lang.Class* @param offset 开始索引(默认:0)* @param rows 每页显示记录数(默认:10)* @return*/@Overridepublic <T> SolrResultInfo<T> queryForPage(Class<T> clz, Long offset, Integer rows) {// 定义返回自定义数据结构对象SolrResultInfo<T> resultInfo = new SolrResultInfo<T>();Query query = new SimpleQuery("*:*");query.setOffset(offset); // 开始索引(默认 0)query.setRows(rows); // 每页记录数(默认 10)ScoredPage<T> page = solrTemplate.queryForPage(solrCore, query, clz);resultInfo.setTotal(page.getTotalElements());resultInfo.setList(page.getContent());return resultInfo;}/*** 高亮查询** @param clz 泛型类对应java.lang.Class* @param keywords 关键字* @param hlFieldsList 高亮字段列表* @param <T> 泛型* @return*/@Overridepublic <T> SolrResultInfo<T> queryHeightLight(Class<T> clz, String keywords, List<String> hlFieldsList) throws InvocationTargetException, IllegalAccessException {// 定义返回自定义数据结构对象SolrResultInfo<T> resultInfo = new SolrResultInfo<T>();// 封装高亮条件HighlightQuery q = new SimpleHighlightQuery();HighlightOptions highlightOptions = new HighlightOptions();// 高亮显示字段for (String s : hlFieldsList) {highlightOptions.addField(s);}// 高亮样式highlightOptions.setSimplePrefix("<em style='color: red'>");highlightOptions.setSimplePostfix("</em>");q.setHighlightOptions(highlightOptions);Criteria criteria = new Criteria("keywords").is(keywords);q.addCriteria(criteria);HighlightPage<T> pages = solrTemplate.queryForHighlightPage(solrCore, q, clz);// *********************** 设置高亮结果 ***********************for (HighlightEntry<T> h : pages.getHighlighted()) { // 循环高亮入口集合T t = h.getEntity(); // 获取原实体类if (h.getHighlights().size() > 0 && h.getHighlights().get(0).getSnipplets().size() > 0) {for (HighlightEntry.Highlight highlight : h.getHighlights()) {for (String value : highlight.getSnipplets()) {// 替换高亮部分的内容BeanUtils.setProperty(t, highlight.getField().getName(), value);}}}}resultInfo.setTotal(pages.getTotalElements());resultInfo.setList(pages.getContent());return resultInfo;}/*** 通过泛型获取Solr中的对象集合** @param clz 泛型类对应java.lang.Class* @param criteria 查询条件* @param hlFieldsList 高亮显示数据域名称,是List<String>集合* @param fqList 过滤条件* @param offset 开始索引(默认:0)* @param rows 每页显示记录数(默认:10)* @return*/@Overridepublic <T> SolrResultInfo<T> query(Class<T> clz, Criteria criteria, List<String> hlFieldsList, List<SimpleFilterQuery> fqList, Long offset, Integer rows) throws InvocationTargetException, IllegalAccessException {// 定义返回自定义数据结构对象SolrResultInfo<T> resultInfo = new SolrResultInfo<>();// 封装高亮条件HighlightQuery q = new SimpleHighlightQuery();// 过滤条件for (SimpleFilterQuery simpleFilterQuery : fqList) {q.addFilterQuery(simpleFilterQuery);}// 分页查询q.setOffset(offset);q.setRows(rows);// 高亮显示字段HighlightOptions highlightOptions = new HighlightOptions();for (String s : hlFieldsList) {highlightOptions.addField(s);}// 高亮样式highlightOptions.setSimplePrefix("<em style='color: red'>");highlightOptions.setSimplePostfix("</em>");q.setHighlightOptions(highlightOptions);q.addCriteria(criteria);HighlightPage<T> pages = solrTemplate.queryForHighlightPage(solrCore, q, clz);// *********************** 设置高亮结果 ***********************for (HighlightEntry<T> h : pages.getHighlighted()) { // 循环高亮入口集合T t = h.getEntity(); // 获取原实体类if (h.getHighlights().size() > 0 && h.getHighlights().get(0).getSnipplets().size() > 0) {for (HighlightEntry.Highlight highlight : h.getHighlights()) {for (String value : highlight.getSnipplets()) {// 替换高亮部分的内容BeanUtils.setProperty(t, highlight.getField().getName(), value);}}}}resultInfo.setTotal(pages.getTotalElements());resultInfo.setList(pages.getContent());return resultInfo;}private <T> SolrResultInfo<T> queryNoHeightLight(Class<T> clz, String query, Criteria criteria, List<String> hlFieldsList, Long offset, Integer rows) {// 定义返回自定义数据结构对象SolrResultInfo<T> resultInfo = new SolrResultInfo<>();// 查询条件构造Query q = new SimpleQuery(query);// 分页查询q.setOffset(offset); // 开始索引(默认 0)q.setRows(rows); // 每页记录数(默认 10)// 条件查询q.addCriteria(criteria);Page<T> pages = solrTemplate.query(solrCore, q, clz);resultInfo.setTotal(pages.getTotalElements());resultInfo.setList(pages.getContent());return resultInfo;}/*** 根据ID获取记录** @param id 索引ID* @param clz Class对象* @param <T> 泛型* @return*/@Overridepublic <T> T getById(String id, Class<T> clz) {Optional<T> bean = solrTemplate.getById(solrCore, id, clz);return bean.isPresent() ? bean.get() : null;}}
■ 返回对象封装类(SolrResultInfo.java)
import java.util.List;/*** Description 查询Solr返回的对象,对象类型为T的集合,还包含Solr中符合条件记录总数*/public class SolrResultInfo<T> {private List<T> list = null;private Long total = null;public List<T> getList() {return list;}public void setList(List<T> list) {this.list = list;}public Long getTotal() {return total;}public void setTotal(Long total) {this.total = total;}}
■ 工具类(CommonUtils.java)
import com.google.common.collect.Lists;import com.google.common.collect.Maps;import org.apache.commons.beanutils.BeanUtils;import org.springframework.cglib.beans.BeanMap;import java.util.List;import java.util.Map;/*** Description 工具类(Map与JavaBean互转)*/public class CommonUtils {/*** 把一个Map转换成指定类型的JavaBean对象** @param map Map对象* @param clazz Class对象* @param <T> 泛型* @return*/public static <T> T mapToBean(Map<String, ?> map, Class<T> clazz) {try {T bean = clazz.newInstance();BeanUtils.populate(bean, map);return bean;} catch (Exception e) {throw new RuntimeException(e);}}/*** 将List<Map<String,Object>>转换为List<T>** @param maps Map对象列表* @param clazz Class对象* @return* @throws InstantiationException* @throws IllegalAccessException*/public static <T> List<T> mapsToBeans(List<Map<String, Object>> maps, Class<T> clazz)throws InstantiationException, IllegalAccessException {List<T> list = Lists.newArrayList();if (maps != null && maps.size() > 0) {Map<String, Object> map;T bean;for (int i = 0, size = maps.size(); i < size; i++) {map = maps.get(i);bean = clazz.newInstance();list.add((T) mapToBean(map, bean.getClass()));}}return list;}/*** 将对象装换为map** @param bean* @return*/public static <T> Map<String, Object> beanToMap(T bean) {Map<String, Object> map = Maps.newHashMap();if (bean != null) {BeanMap beanMap = BeanMap.create(bean);for (Object key : beanMap.keySet()) {map.put(key + "", beanMap.get(key));}}return map;}/*** 将List<T>转换为List<Map<String, Object>>** @param beanList* @return*/public static <T> List<Map<String, Object>> beansToMap(List<T> beanList) {List<Map<String, Object>> list = Lists.newArrayList();if (beanList != null && beanList.size() > 0) {Map<String, Object> map;T bean;for (int i = 0, size = beanList.size(); i < size; i++) {bean = beanList.get(i);map = beanToMap(bean);list.add(map);}}return list;}}
4. 测试
■ 测试类(SolrTemplateServiceTests.java)
import com.lonton.bigdata.vo.Article;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.data.solr.core.query.Criteria;import org.springframework.data.solr.core.query.SimpleFilterQuery;import org.springframework.util.Assert;import java.lang.reflect.InvocationTargetException;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;@SpringBootTestpublic class SolrTemplateServiceTests {@Autowiredprivate SolrTemplateService service;@Testpublic void saveOrUpdate() {Article bean = new Article();bean.setId(1);bean.setContentId(99L);bean.setTitle("测试-SolrTemplateService.saveOrUpdate");bean.setContent("测试-作为一个中国人,我感到很自豪");bean.setType(1);bean.setCreateAt(1578912619999L);bean.setPublishAt(1578912619999L);assert service.saveOrUpdate(bean);}@Testpublic void saveOrUpdateByMap() {Map<String, Object> map = new HashMap<>();map.put("id", 1);map.put("contentId", 101L);map.put("title", "北京");map.put("content", "saveOrUpdateByMap作为一个中国人,我感到很自豪。");map.put("type", 1);map.put("createAt", 1578912614123L);map.put("publishAt", 1578912614125L);assert service.saveOrUpdate(map, Article.class);}@Testpublic void deleteById() {saveOrUpdate();assert service.deleteById("1");}@Testpublic void deleteByQuery() {saveOrUpdate();assert service.deleteByQuery("id:[1 TO 12]");}@Testpublic void deleteAll() {assert service.deleteByQuery("*:*");}@Testpublic void batchSaveOrUpdate() {List<Article> entities = new ArrayList<>();for (int i = 1; i <= 35; i++) {entities.add(new Article(i, i * 10L, "我是中国人,打倒小日本" + i, "作为一个中国人,我感到很自豪。", 1, 1578912614123L, 1578912614123L));}assert service.batchSaveOrUpdate(entities);}@Testpublic void queryForPage() {batchSaveOrUpdate();SolrResultInfo<Article> resultInfo = service.queryForPage(Article.class, (2 - 1) * 5L, 5);Long total = resultInfo.getTotal();System.out.println("total:" + total);if (total > 0) {for (Article article : resultInfo.getList()) {System.out.println(article);}}Assert.isTrue(total.intValue() == 35, "异常:返回数据与实际不符!");}@Testpublic void queryHeightLight() throws InvocationTargetException, IllegalAccessException {batchSaveOrUpdate();List<String> hlFieldsList = new ArrayList<>();hlFieldsList.add("title");hlFieldsList.add("content");SolrResultInfo<Article> resultInfo = service.queryHeightLight(Article.class, "中国人", hlFieldsList);Long total = resultInfo.getTotal();System.out.println("total:" + total);if (total > 0) {for (Article article : resultInfo.getList()) {System.out.println(article);}}}@Testpublic void query() throws InvocationTargetException, IllegalAccessException {batchSaveOrUpdate();saveOrUpdate();// 高亮设置List<String> hlFieldsList = new ArrayList<>();hlFieldsList.add("title");hlFieldsList.add("content");// 关键字Criteria criteria = new Criteria("keywords").is("中国");// 过滤条件List<SimpleFilterQuery> fqList = new ArrayList<>();SimpleFilterQuery fq1 = new SimpleFilterQuery(new Criteria("content_id").between(70, 150));SimpleFilterQuery fq2 = new SimpleFilterQuery(new Criteria("type").is(1));SimpleFilterQuery fq3 = new SimpleFilterQuery(new Criteria("content").contains("测试"));fqList.add(fq1);fqList.add(fq2);fqList.add(fq3);// 执行查询SolrResultInfo<Article> resultInfo = service.query(Article.class, criteria, hlFieldsList, fqList, (1 - 1) * 5L, 5);Long total = resultInfo.getTotal();System.out.println("total:" + total);if (total > 0) {for (Article article : resultInfo.getList()) {System.out.println(article);}}}@Testvoid getById() {saveOrUpdate();Article bean = service.getById("1", Article.class);System.out.println(bean);Assert.notNull(bean, "异常:查询结果为空!");}}
5. 完整示例代码
源码:solr-springdata-example01-src.zip
方式2:Spring + Spring Data Solr
1. 添加pom.xml依赖
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>4.3.18.RELEASE</version></dependency><dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-solr</artifactId><version>4.0.10.RELEASE</version></dependency><dependency><groupId>org.apache.solr</groupId><artifactId>solr-solrj</artifactId><version>7.7.2</version></dependency>
2. Solr配置**
单机(application-solr.xml) ```xml <?xml version=”1.0” encoding=”UTF-8”?>
- **Solr配置(solr.properties)**
spring.data.solr.core=db_sync
- **集群(application-solrcloud.xml)**```xml<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><!-- 集群版solr服务配置 --><bean id="emptyBuilder" class="org.apache.solr.client.solrj.impl.CloudSolrClient.Builder"/><bean id="builder" factory-bean="emptyBuilder" factory-method="withZkHost"><constructor-arg value="192.168.0.101:2181,192.168.0.102:2181,192.168.0.103:2181" type="java.lang.String"/></bean><bean id="cloudSolrServer" factory-bean="builder" factory-method="build"><property name="defaultCollection" value="db_sync"/></bean><bean id="solrTemplate" class="org.springframework.data.solr.core.SolrTemplate"><constructor-arg ref="cloudSolrServer"/></bean><bean id="solrTemplateService" class="com.lonton.bigdata.service.impl.SolrTemplateServiceImpl"/></beans>
注意:由于CloudSolrClient是以非静态工程方法实例化的一个对象。所以我们不能用Spring构造实例。只能用Spring的非静态工程实例化Solr。
3. 编写关键类
■ 配置类(Article.java)
import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import org.apache.solr.client.solrj.beans.Field;import org.springframework.data.solr.core.mapping.SolrDocument;import java.io.Serializable;/*** Description VO-文章类*/@Data@AllArgsConstructor@NoArgsConstructor@SolrDocument(solrCoreName = "db_sync")public class Article implements Serializable {// 必须实现可序列化接口,如果要在网络上传输@Field("id")private Integer id;@Field("content_id")private Long contentId;@Field("title")private String title;@Field("content")private String content;@Field("type")private Integer type;@Field("create_at")private Long createAt;@Field("publish_at")private Long publishAt;@Overridepublic String toString() {return "Article{" +"id=" + id +", contentId=" + contentId +", title='" + title + '\'' +", content='" + content + '\'' +", type=" + type +", createAt=" + createAt +", publishAt=" + publishAt +'}';}}
■ 服务接口(SolrTemplateService.java)
import org.springframework.data.solr.core.query.Criteria;import org.springframework.data.solr.core.query.SimpleFilterQuery;import java.lang.reflect.InvocationTargetException;import java.util.List;import java.util.Map;public interface SolrTemplateService {/*** 将JavaBean对象添加(新增或修改)到Solr中** @param bean VO/DTO/Pojo/Domain对象* @param <T> 泛型(对象类型)* @return*/<T> boolean saveOrUpdate(T bean);/*** 将Map对象添加(新增或修改)到Solr中** @param map Map对象* @param clz JavaBean的Class对象* @param <T> 泛型(对象类型)* @return*/<T> boolean saveOrUpdate(Map map, Class<T> clz);/*** 通过文档ID删除Solr中的文档** @param id 索引ID* @return*/boolean deleteById(String id);/*** 通过查询删除Solr中对应的数据集合** @param query 查询条件*/boolean deleteByQuery(String query);/*** 批量新增或更新记录** @param entities 新增或更新对象列表* @param <T> 泛型* @return*/<T> boolean batchSaveOrUpdate(List<T> entities);/*** 分页查询** @param clz 泛型类对应java.lang.Class* @param offset 开始索引(默认:0)* @param rows 每页显示记录数(默认:10)* @return*/<T> SolrResultInfo<T> queryForPage(Class<T> clz, Long offset, Integer rows);/*** 高亮查询** @param clz 泛型类对应java.lang.Class* @param keywords 关键字* @param hlFieldsList 高亮字段列表* @param <T> 泛型* @return*/<T> SolrResultInfo<T> queryHeightLight(Class<T> clz, String keywords, List<String> hlFieldsList) throws InvocationTargetException, IllegalAccessException;/*** 通过泛型获取Solr中的对象集合** @param clz 泛型类对应java.lang.Class* @param criteria 查询条件* @param hlFieldsList 高亮显示数据域名称,是List<String>集合* @param fqList 过滤条件* @param offset 开始索引(默认:0)* @param rows 每页显示记录数(默认:10)* @return*/<T> SolrResultInfo<T> query(Class<T> clz, Criteria criteria, List<String> hlFieldsList, List<SimpleFilterQuery> fqList, Long offset, Integer rows) throws InvocationTargetException, IllegalAccessException;/*** 根据ID获取记录** @param id 索引ID* @param clz Class对象* @param <T> 泛型* @return*/<T> T getById(String id, Class<T> clz);}
■ 服务接口实现类(SolrTemplateServiceImpl.java)
import com.lonton.bigdata.service.SolrResultInfo;import com.lonton.bigdata.service.SolrTemplateService;import com.lonton.bigdata.util.CommonUtils;import org.apache.commons.beanutils.BeanUtils;import org.apache.solr.client.solrj.response.UpdateResponse;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.InitializingBean;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.PropertySource;import org.springframework.core.env.Environment;import org.springframework.data.domain.Page;import org.springframework.data.solr.core.SolrTemplate;import org.springframework.data.solr.core.query.*;import org.springframework.data.solr.core.query.result.HighlightEntry;import org.springframework.data.solr.core.query.result.HighlightPage;import org.springframework.data.solr.core.query.result.ScoredPage;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;import java.lang.reflect.InvocationTargetException;import java.util.List;import java.util.Map;import java.util.Optional;@Service@PropertySource("classpath:solr.properties")public class SolrTemplateServiceImpl implements SolrTemplateService, InitializingBean {private static final Logger log = LoggerFactory.getLogger(SolrTemplateServiceImpl.class);private String solrCore;public String getSolrCore() {return solrCore;}public void setSolrCore(String solrCore) {this.solrCore = solrCore;}@Resourceprivate Environment environment;@Autowiredprivate SolrTemplate solrTemplate;/*** 将JavaBean对象添加(新增或修改)到Solr中** @param bean VO/DTO/Pojo/Domain对象* @param <T> 泛型(对象类型)* @return*/@Override@Transactional(rollbackFor = Exception.class)public <T> boolean saveOrUpdate(T bean) {try {// 将对象存储到索引库中UpdateResponse resp = solrTemplate.saveBean(solrCore, bean);// 提交solrTemplate.commit(solrCore);if (resp.getStatus() == 0) {log.debug("saveOrUpdate successfully!");return true;}} catch (Exception e) {log.error(e.getMessage());e.printStackTrace();}log.debug("saveOrUpdate failure!");return false;}/*** 将Map对象添加(新增或修改)到Solr中** @param map Map对象* @param clz JavaBean的Class对象* @param <T> 泛型(对象类型)* @return*/@Override@Transactional(rollbackFor = Exception.class)public <T> boolean saveOrUpdate(Map map, Class<T> clz) {try {T bean = CommonUtils.mapToBean((Map<String, ?>) map, clz);// 将对象存储到索引库中UpdateResponse resp = solrTemplate.saveBean(solrCore, bean);// 提交solrTemplate.commit(solrCore);if (resp.getStatus() == 0) {log.debug("saveOrUpdate successfully!");return true;}} catch (Exception e) {log.error(e.getMessage());e.printStackTrace();}log.debug("saveOrUpdate failure!");return false;}/*** 通过文档ID删除Solr中的文档** @param id 索引ID* @return*/@Override@Transactional(rollbackFor = Exception.class)public boolean deleteById(String id) {try {UpdateResponse resp = solrTemplate.deleteByIds(solrCore, id);solrTemplate.commit(solrCore);if (resp.getStatus() == 0) {log.debug("delete successfully!");return true;}} catch (Exception e) {log.error(e.getMessage());e.printStackTrace();}log.debug("delete failure!");return false;}/*** 通过查询删除Solr中对应的数据集合** @param query 查询条件*/@Override@Transactional(rollbackFor = Exception.class)public boolean deleteByQuery(String query) {try {UpdateResponse resp = solrTemplate.delete(solrCore, new SimpleQuery(query));solrTemplate.commit(solrCore);if (resp.getStatus() == 0) {log.debug("delete successfully!");return true;}} catch (Exception e) {log.error(e.getMessage());e.printStackTrace();}log.debug("delete failure!");return false;}/*** 批量新增或更新记录** @param entities 新增或更新对象列表* @param <T> 泛型* @return*/@Overridepublic <T> boolean batchSaveOrUpdate(List<T> entities) {try {// 将对象存储到索引库中UpdateResponse resp = solrTemplate.saveBeans(solrCore, entities);// 提交solrTemplate.commit(solrCore);if (resp.getStatus() == 0) {log.debug("batchSaveOrUpdate successfully!");return true;}} catch (Exception e) {log.error(e.getMessage());e.printStackTrace();}log.debug("batchSaveOrUpdate failure!");return false;}/*** 分页查询** @param clz 泛型类对应java.lang.Class* @param offset 开始索引(默认:0)* @param rows 每页显示记录数(默认:10)* @return*/@Overridepublic <T> SolrResultInfo<T> queryForPage(Class<T> clz, Long offset, Integer rows) {// 定义返回自定义数据结构对象SolrResultInfo<T> resultInfo = new SolrResultInfo<T>();Query query = new SimpleQuery("*:*");query.setOffset(offset); // 开始索引(默认 0)query.setRows(rows); // 每页记录数(默认 10)ScoredPage<T> page = solrTemplate.queryForPage(solrCore, query, clz);resultInfo.setTotal(page.getTotalElements());resultInfo.setList(page.getContent());return resultInfo;}/*** 高亮查询** @param clz 泛型类对应java.lang.Class* @param keywords 关键字* @param hlFieldsList 高亮字段列表* @param <T> 泛型* @return*/@Overridepublic <T> SolrResultInfo<T> queryHeightLight(Class<T> clz, String keywords, List<String> hlFieldsList) throws InvocationTargetException, IllegalAccessException {// 定义返回自定义数据结构对象SolrResultInfo<T> resultInfo = new SolrResultInfo<T>();// 封装高亮条件HighlightQuery q = new SimpleHighlightQuery();HighlightOptions highlightOptions = new HighlightOptions();// 高亮显示字段for (String s : hlFieldsList) {highlightOptions.addField(s);}// 高亮样式highlightOptions.setSimplePrefix("<em style='color: red'>");highlightOptions.setSimplePostfix("</em>");q.setHighlightOptions(highlightOptions);Criteria criteria = new Criteria("keywords").is(keywords);q.addCriteria(criteria);HighlightPage<T> pages = solrTemplate.queryForHighlightPage(solrCore, q, clz);// *********************** 设置高亮结果 ***********************for (HighlightEntry<T> h : pages.getHighlighted()) { // 循环高亮入口集合T t = h.getEntity(); // 获取原实体类if (h.getHighlights().size() > 0 && h.getHighlights().get(0).getSnipplets().size() > 0) {for (HighlightEntry.Highlight highlight : h.getHighlights()) {for (String value : highlight.getSnipplets()) {// 替换高亮部分的内容BeanUtils.setProperty(t, highlight.getField().getName(), value);}}}}resultInfo.setTotal(pages.getTotalElements());resultInfo.setList(pages.getContent());return resultInfo;}/*** 通过泛型获取Solr中的对象集合** @param clz 泛型类对应java.lang.Class* @param criteria 查询条件* @param hlFieldsList 高亮显示数据域名称,是List<String>集合* @param fqList 过滤条件* @param offset 开始索引(默认:0)* @param rows 每页显示记录数(默认:10)* @return*/@Overridepublic <T> SolrResultInfo<T> query(Class<T> clz, Criteria criteria, List<String> hlFieldsList, List<SimpleFilterQuery> fqList, Long offset, Integer rows) throws InvocationTargetException, IllegalAccessException {// 定义返回自定义数据结构对象SolrResultInfo<T> resultInfo = new SolrResultInfo<>();// 封装高亮条件HighlightQuery q = new SimpleHighlightQuery();// 过滤条件for (SimpleFilterQuery simpleFilterQuery : fqList) {q.addFilterQuery(simpleFilterQuery);}// 分页查询q.setOffset(offset);q.setRows(rows);// 高亮显示字段HighlightOptions highlightOptions = new HighlightOptions();for (String s : hlFieldsList) {highlightOptions.addField(s);}// 高亮样式highlightOptions.setSimplePrefix("<em style='color: red'>");highlightOptions.setSimplePostfix("</em>");q.setHighlightOptions(highlightOptions);q.addCriteria(criteria);HighlightPage<T> pages = solrTemplate.queryForHighlightPage(solrCore, q, clz);// *********************** 设置高亮结果 ***********************for (HighlightEntry<T> h : pages.getHighlighted()) { // 循环高亮入口集合T t = h.getEntity(); // 获取原实体类if (h.getHighlights().size() > 0 && h.getHighlights().get(0).getSnipplets().size() > 0) {for (HighlightEntry.Highlight highlight : h.getHighlights()) {for (String value : highlight.getSnipplets()) {// 替换高亮部分的内容BeanUtils.setProperty(t, highlight.getField().getName(), value);}}}}resultInfo.setTotal(pages.getTotalElements());resultInfo.setList(pages.getContent());return resultInfo;}private <T> SolrResultInfo<T> queryNoHeightLight(Class<T> clz, String query, Criteria criteria, List<String> hlFieldsList, Long offset, Integer rows) {// 定义返回自定义数据结构对象SolrResultInfo<T> resultInfo = new SolrResultInfo<>();// 查询条件构造Query q = new SimpleQuery(query);// 分页查询q.setOffset(offset); // 开始索引(默认 0)q.setRows(rows); // 每页记录数(默认 10)// 条件查询q.addCriteria(criteria);Page<T> pages = solrTemplate.query(solrCore, q, clz);resultInfo.setTotal(pages.getTotalElements());resultInfo.setList(pages.getContent());return resultInfo;}/*** 根据ID获取记录** @param id 索引ID* @param clz Class对象* @param <T> 泛型* @return*/@Overridepublic <T> T getById(String id, Class<T> clz) {Optional<T> bean = solrTemplate.getById(solrCore, id, clz);return bean.isPresent() ? bean.get() : null;}@Overridepublic void afterPropertiesSet() throws Exception {setSolrCore(environment.getRequiredProperty("spring.data.solr.core"));}}
import java.util.List;/*** Description 查询Solr返回的对象,对象类型为T的集合,还包含Solr中符合条件记录总数*/public class SolrResultInfo<T> {private List<T> list = null;private Long total = null;public List<T> getList() {return list;}public void setList(List<T> list) {this.list = list;}public Long getTotal() {return total;}public void setTotal(Long total) {this.total = total;}}
■ 工具类(CommonUtils.java)
import com.google.common.collect.Lists;import com.google.common.collect.Maps;import org.apache.commons.beanutils.BeanUtils;import org.springframework.cglib.beans.BeanMap;import java.util.List;import java.util.Map;/*** Description 工具类(Map与JavaBean互转)*/public class CommonUtils {/*** 把一个Map转换成指定类型的JavaBean对象** @param map Map对象* @param clazz Class对象* @param <T> 泛型* @return*/public static <T> T mapToBean(Map<String, ?> map, Class<T> clazz) {try {T bean = clazz.newInstance();BeanUtils.populate(bean, map);return bean;} catch (Exception e) {throw new RuntimeException(e);}}/*** 将List<Map<String,Object>>转换为List<T>** @param maps Map对象列表* @param clazz Class对象* @return* @throws InstantiationException* @throws IllegalAccessException*/public static <T> List<T> mapsToBeans(List<Map<String, Object>> maps, Class<T> clazz)throws InstantiationException, IllegalAccessException {List<T> list = Lists.newArrayList();if (maps != null && maps.size() > 0) {Map<String, Object> map;T bean;for (int i = 0, size = maps.size(); i < size; i++) {map = maps.get(i);bean = clazz.newInstance();list.add((T) mapToBean(map, bean.getClass()));}}return list;}/*** 将对象装换为map** @param bean* @return*/public static <T> Map<String, Object> beanToMap(T bean) {Map<String, Object> map = Maps.newHashMap();if (bean != null) {BeanMap beanMap = BeanMap.create(bean);for (Object key : beanMap.keySet()) {map.put(key + "", beanMap.get(key));}}return map;}/*** 将List<T>转换为List<Map<String, Object>>** @param beanList* @return*/public static <T> List<Map<String, Object>> beansToMap(List<T> beanList) {List<Map<String, Object>> list = Lists.newArrayList();if (beanList != null && beanList.size() > 0) {Map<String, Object> map;T bean;for (int i = 0, size = beanList.size(); i < size; i++) {bean = beanList.get(i);map = beanToMap(bean);list.add(map);}}return list;}}
4. 测试
■ 测试类(SolrTemplateServiceTests.java)
import com.lonton.bigdata.vo.Article;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.solr.core.query.Criteria;import org.springframework.data.solr.core.query.SimpleFilterQuery;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import org.springframework.util.Assert;import java.lang.reflect.InvocationTargetException;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;@RunWith(SpringJUnit4ClassRunner.class)// 加载集群配置@ContextConfiguration(locations = {"classpath*:application.xml", "classpath*:application-solrcloud.xml"})// 加载单机配置//@ContextConfiguration(locations = {"classpath*:application.xml", "classpath*:application-solr.xml"})public class SolrTemplateServiceTests {@Autowiredprivate SolrTemplateService service;@Testpublic void saveOrUpdate() {Article bean = new Article();bean.setId(1);bean.setContentId(99L);bean.setTitle("测试-SolrTemplateService.saveOrUpdate");bean.setContent("测试-作为一个中国人,我感到很自豪");bean.setType(1);bean.setCreateAt(1578912619999L);bean.setPublishAt(1578912619999L);assert service.saveOrUpdate(bean);}@Testpublic void saveOrUpdateByMap() {Map<String, Object> map = new HashMap<>();map.put("id", 1);map.put("contentId", 101L);map.put("title", "北京");map.put("content", "saveOrUpdateByMap作为一个中国人,我感到很自豪。");map.put("type", 1);map.put("createAt", 1578912614123L);map.put("publishAt", 1578912614125L);assert service.saveOrUpdate(map, Article.class);}@Testpublic void deleteById() {saveOrUpdate();assert service.deleteById("1");}@Testpublic void deleteByQuery() {saveOrUpdate();assert service.deleteByQuery("id:[1 TO 12]");}@Testpublic void deleteAll() {assert service.deleteByQuery("*:*");}@Testpublic void batchSaveOrUpdate() {List<Article> entities = new ArrayList<>();for (int i = 1; i <= 35; i++) {entities.add(new Article(i, i * 10L, "我是中国人,打倒小日本" + i, "作为一个中国人,我感到很自豪。", 1, 1578912614123L, 1578912614123L));}assert service.batchSaveOrUpdate(entities);}@Testpublic void queryForPage() {batchSaveOrUpdate();SolrResultInfo<Article> resultInfo = service.queryForPage(Article.class, (2 - 1) * 5L, 5);Long total = resultInfo.getTotal();System.out.println("total:" + total);if (total > 0) {for (Article article : resultInfo.getList()) {System.out.println(article);}}Assert.isTrue(total.intValue() == 35, "异常:返回数据与实际不符!");}@Testpublic void queryHeightLight() throws InvocationTargetException, IllegalAccessException {batchSaveOrUpdate();List<String> hlFieldsList = new ArrayList<>();hlFieldsList.add("title");hlFieldsList.add("content");SolrResultInfo<Article> resultInfo = service.queryHeightLight(Article.class, "中国人", hlFieldsList);Long total = resultInfo.getTotal();System.out.println("total:" + total);if (total > 0) {for (Article article : resultInfo.getList()) {System.out.println(article);}}}@Testpublic void query() throws InvocationTargetException, IllegalAccessException {batchSaveOrUpdate();saveOrUpdate();// 高亮设置List<String> hlFieldsList = new ArrayList<>();hlFieldsList.add("title");hlFieldsList.add("content");// 关键字Criteria criteria = new Criteria("keywords").is("中国");// 过滤条件List<SimpleFilterQuery> fqList = new ArrayList<>();SimpleFilterQuery fq1 = new SimpleFilterQuery(new Criteria("content_id").between(70, 150));SimpleFilterQuery fq2 = new SimpleFilterQuery(new Criteria("type").is(1));SimpleFilterQuery fq3 = new SimpleFilterQuery(new Criteria("content").contains("测试"));fqList.add(fq1);fqList.add(fq2);fqList.add(fq3);// 执行查询SolrResultInfo<Article> resultInfo = service.query(Article.class, criteria, hlFieldsList, fqList, (1 - 1) * 5L, 5);Long total = resultInfo.getTotal();System.out.println("total:" + total);if (total > 0) {for (Article article : resultInfo.getList()) {System.out.println(article);}}}@Testpublic void getById() {saveOrUpdate();Article bean = service.getById("1", Article.class);System.out.println(bean);Assert.notNull(bean, "异常:查询结果为空!");}}
5. 完整示例代码
源码:solr-springdata-example02-src.zip
参考
语雀:Spring Data Solr入门
https://www.yuque.com/carve/lhrlki/0d12e69e3bfb541d5262b49699daae30
https://www.yuque.com/itmoon/fzz84n/zso8rp
博客园:Spring Data Solr操作Solr的简单案例
https://www.cnblogs.com/blazeZzz/p/9526178.html
博客园:SpringBoot整合Spring Data Solr
https://www.cnblogs.com/kazetotori/p/8549458.html
博客园:Solr7.x学习(8)-使用spring-data-solr
https://www.cnblogs.com/zhi-leaf/p/11612367.html
CSDN:solr 7.X 与spring-data 3.X整合
https://blog.csdn.net/alistair_chow/article/details/80801402
