Apache Solr是一个搜索引擎。Spring boot为Solr client version5 提供了起步配置,spring-boot-starter-data-solr。添加Spring boot起步maven依赖:
org.springframework.boot
spring-boot-starter-data-solr
单独使用时maven配置:
org.apache.solr
solr-solrj
7.5.0
配置SolrClient
可以使用XML和注解的方式配置SolrClient。
XML配置
Java注解配置
使用注解配置Solr repositories。如下:
@Configuration
@EnableSolrRepositories
public class SolrConfiguration {
@Bean
public SolrClient getSolrClient(){
HttpSolrClient client = new HttpSolrClient.
Builder("http://192.168.152.133:8983/solr").build();
return client;
}
@Bean
public SolrOperations getSolrOperations(SolrClient solrClient){
return new SolrTemplate(solrClient);
}
}
定义实体
在配置通过注解@EnableSolrRepositories已经启用solrRepository,使用SolrDocument注解配置Solr entity
@SolrDocument(collection = "productCl")
public class Product {
private @Id @Indexed String productId;
private @Indexed String name;
private @Indexed String categories;
private @Indexed Float price;
private @Indexed List authors;
private @Indexed boolean available;
//setter getter 省略
}
创建SolrRepository
SolrCrudRepository继承自SolrRepository,SolrRepository又继承自PagingAndSortingRepository,自定义的mySolrRepository需继承自SolrCrudRepository,通过命名的方式和@Query注解的方式实现查询接口。
@Repository
public interface MySolrRepository extends SolrCrudRepository{
public List findByNameAndAvailable(String name,boolean available);
@Query(fields={"name","categories","productId"},defaultOperator=Operator.AND)
public List findByNameIn(String name);
public List findByName(String name,Pageable page);
}
在Service中使用调用SolrRepository方法时,可以使用事物支持。如下:
@Transactional
public Product save(Product product){
Product pr = repository.save(product);
return pr;
}
Document映射
完成数据库中对象到实体之间的映射要完成两个步骤:
1,使用实体暴露的初始化函数创建实体实例
2,填充实例暴露的所有属性;
对象的创建
Spring data会自动检测持久化对象的构造函数来实例对象实例,基于以下的策略:
1,如果存在无参构造函数,就会使用这个,并忽略其他构造函数;
2,如果只有一个带参构造函数,则使用这个;
3,如果有多个带参构造函数,需要使用@PersistenceConstructor标识哪个可以被Spring data使用。
为了避免使用反射的开销,Spring data默认会在运行期使用工厂类创建对象实例,直接调用实体类的构造器,但是这样的实体类需满足如下条件:
1,不能是被private修饰的类
2,不能包含静态的内部类
3,不能是CGLib代理类
4,被Spring data使用的构造函数必须事public类型
如果实体类包含以上其中一点,Spring data就会使用反射创建实体实例。
属性填充
一旦创建了实体的实例,Spring Data就会填充该类的所有剩余持久属性。 除非已经由实体的构造函数填充(即通过其构造函数参数列表使用),否则将首先填充identifier属性以允许循环对象引用的解析。之后,在实体实例上设置尚未由构造函数填充的所有non-transient属性,这些属性的填充按照如下的策略:
1,如果属性是不可变的但是公开了一个write方法,使用write来创建一个具有新属性值的新实体实例。
2,如果属性提供了setter或则getter方法,将会调用setter方法。
3,默认情况下,直接设置属性值。
MappingSolrConverter
MappingSolrConverter允许你为SolrDocument和SolrInputDocument注册自定义的转化器,转换器完全和DocumentObjectBinder兼容,使用@Indexed注解的属性需要使用readonly=true以防止写入Solr。添加CustomConversions
@Bean
public SolrCustomConversions getConversion(){
SolrCustomConversions conversions = new SolrCustomConversions(new ArrayList<>());
return conversions;
}
添加转换器
@Bean
public SolrConverter getSolrConverter(){
SimpleSolrMappingContext context = new SimpleSolrMappingContext();
MappingSolrConverter converter = new MappingSolrConverter(context);
converter.setCustomConversions(getConversion());
return converter;
}
在SolrTemplate中添加Converter:
@Bean
public SolrOperations getSolrOperations(SolrClient solrClient){
SolrTemplate tmp = new SolrTemplate(solrClient);
tmp.setSolrConverter(getSolrConverter());
return tmp;
}
Solr操作的其他支持
使用SolrDocument注解
指定实体类在Solr中对应的集合名称,可以使用静态值或则SpEl表达式:
//直接指定值
@SolrDocument(collection = "techproducts")
class StaticCollectionName { ... }
//使用Spel表达式,使用引用值
@SolrDocument(collection = "#{@someBean.getCollectionName()}")
class DynamicCollectionName { ... }
局部更新
使用PartialUpdate实现局部更新操作。例如:
public void PatialUpdateTest(Product product){
PartialUpdate update = new PartialUpdate("productId", "123");
update.setValueOfField("name", "newValue");
operations.saveBean("product", update);
}
分片支持查询
SolrRepository不能实现分片查询,但是可以使用SolrTemplate实现。
public void facetSearchTest(){
FacetQuery query = new SimpleFacetQuery(new
Criteria(Criteria.WILDCARD).expression(Criteria.WILDCARD))
.setFacetOptions(new FacetOptions().addFacetOnField("name").setFacetLimit(5));
FacetPage page = operations.queryForFacetPage("productCl", query, Product.class);
System.out.println(page);
}
Range Faceting
在FacetOptions上设置查询范围,通过SolrTemplate查询。
public FacetPage facetSearchByRange(){
FacetOptions options = new FacetOptions()
.addFacetByRange(new FacetOptions.
FieldWithNumericRangeParameters("price", 5, 20, 5).
setHardEnd(true).
setInclude(FacetRangeInclude.ALL)
);
options.setFacetMinCount(0);
FacetQuery query = new SimpleFacetQuery(new
SimpleStringCriteria("*:*")).setFacetOptions(options);
FacetPage page = this.operations.queryForFacetPage("productCl", query, Product.class);
return page;
}
两种设置范围查询的实现:
1,数值类型的范围:使用数值类型的范围查询,实现类org.springframework.data.solr.core.query.FacetOptions.FieldWithNumericRangeParameters,参数是start value,end value,和一个gap。
2,日期范围:使用org.springframework.data.solr.core.query.FacetOptions.FieldWithDateRangeParameters类,参数是开始日期(start date),结束日期(end date)和一个gap(间隔)。可以使用org.apache.solr.util.DateMathParser来定义gap。
另外也可以使用org.springframework.data.solr.core.query.FacetOptions.FieldWithRangeParameters来配置范围参数,相关配置如下:
Hard end:setHardEnd(Boolean):定义是否应该结束最后一个范围,即使结束不满足条件。
Include:setInclude(org.apache.solr.common.params.FacetParams.FacetRangeInclude):定义边界的处理方式。
Other:setOther(org.apache.solr.common.params.FacetParams.FacetRangeOther):其他选项定义。
Terms 支持
使用SolrTemplate可以实现Terms查询,但是不能使用SolrRepository,返回TermPage对象。例如下:
public TermsPage findByTerms(){
TermsQuery query = SimpleTermsQuery.queryBuilder().fields("name").build();
TermsPage page = operations.queryForTermsPage("productCl", query);
return page;
}
查询结果分组
查询结果分组也不能使用SolrRepository实现,但是可以使用SolrTemplate,返回数据为GroupPage。如下:
public GroupPage findByGroup(){
Field field = new SimpleField("price");
Function func = ExistsFunction.exists("categories");
Query query = new SimpleQuery("available:true");
SimpleQuery groupQuery = new SimpleQuery(new SimpleStringCriteria("*:*"));
GroupOptions groupOptions = new GroupOptions().addGroupByField(field)
.addGroupByFunction(func)
.addGroupByQuery(query);
groupQuery.setGroupOptions(groupOptions);
GroupPage page = operations.queryForGroupPage("productCl", query, Product.class);
GroupResult fieldGroupResult = page.getGroupResult(field);
GroupResult funcGroupResult = page.getGroupResult(func);
GroupResult queryGroupResult = page.getGroupResult(query);
return page;
}