spring中使用solr的代码实现

本文介绍了在Spring环境下,如何利用Solr的原生API访问已经搭建好的Solr集群,包括引入Solr库,配置FactoryBean,创建SolrServer,以及在Service层的操作要点。注意Solr的查询优化,如q和fq的区别,选择合适的分词器,定期执行optimize以保持高效读写,并推荐使用createIndexBatch方法批量写入。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在介绍solr的使用方法之前,我们需要安装solr的服务端集群。基本上就是安装zookeeper,tomcat,jdk,solr,然后按照需要配置三者的配置文件即可。由于本人并没有具体操作过如何进行solr集群的搭建。所以关于如何搭建solr集群,读者可以去网上查看其它资料,有很多可以借鉴。这里只介绍搭建完solr集群之后,我们客户端是如何访问solr集群的。

之前介绍过,spring封装nosql和sql数据库的使用,都是通过xxxTemplate。solr也不例外。

我们需要引入solr的jar包

<dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-solr</artifactId>
            <version>1.0.0.RELEASE</version>
        </dependency>
然后引入solr在spring中封装的配置

    <bean id="orderInfoSolrServer" class="com.xxxx.SolrCloudServerFactoryBean">
        <property name="zkHost" value="${solr.zkHost}"/>
        <property name="defaultCollection" value="orderInfo"/>
        <property name="zkClientTimeout" value="6000"/>
    </bean>
    <bean id="solrTemplate" class="org.springframework.data.solr.core.SolrTemplate" scope="singleton">
        <constructor-arg ref="orderInfoSolrServer" />
    </bean>

    <bean id="solrService" class="com.xxxx.SolrServiceImpl">
        <property name="solrOperations" ref="solrTemplate" />
    </bean>
然后重写我们的SolrServiceImpl就可以了。

但是,本文我们不用spring中封装的xxxTemplate这种格式做讲解。个人在使用spring封装solr的方式的时候遇到了各种各样的问题,可能是能力太low架控不了吧。下面我们主要讲解下如何使用solr的原生api进行访问。

首先:

引入solr的原生代码api的jar包

        <dependency>
            <groupId>org.apache.solr</groupId>
            <artifactId>solr-solrj</artifactId>
            <version>4.7.2</version>
        </dependency>
其次:

在spring的配置文件中配置我们solr的FactoryBean类,此类是作为我们编写自己业务service类的属性来操作solr。

    <bean id="orderInfoSolrServer" class="com.xxxx.SolrCloudServerFactoryBean">
        <property name="zkHost" value="${solr.zkHost}"/>
        <property name="defaultCollection" value="orderInfo"/>
        <property name="zkClientTimeout" value="6000"/>
    </bean>
solr.zkHost是我们配置的zookeeper集群

orderInfo是我们存储在solr中的数据结构bean

再次:

编写我们的SolrCloudServerFactoryBean类,其中使用了spring的FactoryBean<SolrServer>,和InitializingBean。关于这两者的含义读者可以参考其他资料,基本意思是spring容器在注册该bean之前,需要进行的一些初始化操作。通过afterPropertiesSet方法可以看到我们在使用solr之前做的一些初始化操作。

package com.jd.fms.prism.solr.service;

import org.apache.http.client.HttpClient;

/**
 * solrj spring integration
 *
 * @author bjchenrui
 */
public class SolrCloudServerFactoryBean implements FactoryBean<SolrServer>, InitializingBean {

	private CloudSolrServer cloudSolrServer;

	private String zkHost;

	private String defaultCollection;

	private int maxConnections = 1000;

	private int maxConnectionsPerHost = 500;

	private int zkClientTimeout = 10000;

	private int zkConnectTimeout = 10000;

	private Lock lock = new ReentrantLock();

	public SolrServer getObject() throws Exception {
		return cloudSolrServer;
	}

	public Class<SolrServer> getObjectType() {
		return SolrServer.class;
	}

	public boolean isSingleton() {
		return true;
	}

	public void afterPropertiesSet() throws Exception {
		ModifiableSolrParams params = new ModifiableSolrParams();
		params.set(HttpClientUtil.PROP_MAX_CONNECTIONS, maxConnections);
		params.set(HttpClientUtil.PROP_MAX_CONNECTIONS_PER_HOST, maxConnectionsPerHost);
		HttpClient client = HttpClientUtil.createClient(params);
		LBHttpSolrServer lbServer = new LBHttpSolrServer(client);
		lock.lock();
		try {
			if(cloudSolrServer == null) {
				cloudSolrServer = new CloudSolrServer(zkHost, lbServer);
			}
		} finally {
			lock.unlock();
		}

		cloudSolrServer.setDefaultCollection(defaultCollection);
		cloudSolrServer.setZkClientTimeout(zkClientTimeout);
		cloudSolrServer.setZkConnectTimeout(zkConnectTimeout);
	}

	public void setCloudSolrServer(CloudSolrServer cloudSolrServer) {
		this.cloudSolrServer = cloudSolrServer;
	}

	public void setZkHost(String zkHost) {
		this.zkHost = zkHost;
	}

	public void setDefaultCollection(String defaultCollection) {
		this.defaultCollection = defaultCollection;
	}

	public void setMaxConnections(int maxConnections) {
		this.maxConnections = maxConnections;
	}

	public void setMaxConnectionsPerHost(int maxConnectionsPerHost) {
		this.maxConnectionsPerHost = maxConnectionsPerHost;
	}

	public void setZkClientTimeout(int zkClientTimeout) {
		this.zkClientTimeout = zkClientTimeout;
	}

	public void setZkConnectTimeout(int zkConnectTimeout) {
		this.zkConnectTimeout = zkConnectTimeout;
	}

}
最后:

现在就可以编写我们的service类了,这里就是我们具体如何操作solr的地方。

package com.jd.fms.prism.solr.service.impl;

import com.jd.fms.prism.common.utils.DateUtil;

@Service("orderInfoSolrService")
public class OrderInfoNativeSolrServiceImpl {
    
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DateUtil.FORMATER11);
    private static SimpleDateFormat simpleDateFormat1 = new SimpleDateFormat(DateUtil.FORMATER4);
    
    @Resource(name = "orderInfoSolrServer")
    private SolrServer solrServer;

    /**
     * 创建索引
     *
     * @param orderInfo
     */
    public void creatIndex(OrderInfo orderInfo) throws IOException, SolrServerException {
        solrServer.addBean(orderInfo);
        solrServer.commit();
    }
    /**
     * 查询条件的生成。支持字段的精确查询,模糊查询,范围查询。
     * @param orderIdfilter
     * @param queryObj
     * @param queryTimeList
     * @param sorts
     * @return
     * @throws Exception
     */
	public SolrQuery iniFilter(String orderIdfilter,OrderInfo queryObj,List<QueryTime> queryTimeList, Sort... sorts) throws Exception {
		SolrQuery sQuery = new SolrQuery();
        String queryQ = "validTag:1";
        sQuery.setQuery(queryQ);
        StringBuilder filter =  new StringBuilder();
        if(null != orderIdfilter){
        	filter.append(orderIdfilter);
        	queryObj.setOrderId(null);
        }
        //添加过滤条件
        Field[] fields = queryObj.getClass().getDeclaredFields();

        String fieldName = "";
        String fieldValue = "";
        for (Field field:fields){
            if(field.isAnnotationPresent(org.apache.solr.client.solrj.beans.Field.class)){
                field.setAccessible(true);
                fieldName = field.getName();
                fieldValue = String.valueOf(field.get(queryObj));
                if (null != fieldValue &&  !"null".equals(fieldValue) && !"".equals(fieldValue) &&  !"0.0".equals(fieldValue)){
                    //如果是会员类型,则添加模糊查询
                	if(fieldName.equals("memberId") || fieldName.equals("orderType")){
                		fieldValue = "*" + fieldValue + "*";
                    }
                	filter.append(fieldName + ":" + fieldValue).append(" AND ");
                }
            }
        }
        if(queryTimeList!=null && queryTimeList.size() > 0) {
            Iterator<QueryTime> iterator = queryTimeList.iterator();
            while(iterator.hasNext()) {
                QueryTime queryTime = iterator.next();
                String beginDate = simpleDateFormat.format(queryTime.getBeginTime().getTime());
                String endDate = simpleDateFormat.format(queryTime.getEndTime().getTime());
                filter.append(queryTime.getFieldName() + ":" + "[" + beginDate + " TO " + endDate + "] AND ");
            }
        }
        if(filter.length()>0){
        	filter.delete(filter.length()-5, filter.length());
        }
        sQuery.addFilterQuery(filter.toString());
        if(sQuery.toString().equals("")){
            sQuery.setQuery("*:*");
        }
        return sQuery;
	}
    /**
     * 查询代码,可以看到我们可以在solr中做聚合,做排序。而且整个过程都是秒级的。
     * @param map
     * @param queryObj
     * @param queryTimeList
     * @param page
     * @param sorts
     * @return
     * @throws Exception
     */
    public Page<OrderInfo> query(Map map,OrderInfo queryObj, List<QueryTime> queryTimeList, Pageable page, Sort... sorts) throws Exception {
    	SolrQuery sQuery = iniFilter(null,queryObj,queryTimeList);

        //添加分页
        if(page != null){
            sQuery.setStart(page.getPageNumber()*page.getPageSize());
            sQuery.setRows(page.getPageSize());
        }
        //添加排序
        /*if (null != sorts){
            sQuery.setSort("orderId",SolrQuery.ORDER.asc);
        }*/
        QueryResponse response = null;
        sQuery.setGetFieldStatistics("orderPrice");
        sQuery.setGetFieldStatistics("duePrice");
        sQuery.setGetFieldStatistics("diffPrice");
        try {
            response = solrServer.query(sQuery);
        } catch (SolrServerException e) {
            e.printStackTrace();
        }
        SolrDocumentList list = response.getResults();
        Map<String, FieldStatsInfo> mapSum = response.getFieldStatsInfo();
        String orderPriceSum = null;
        if(mapSum.get("orderPrice") != null && !mapSum.get("orderPrice").toString().equals("") ){
            orderPriceSum = mapSum.get("orderPrice").getSum().toString();
        }
        String duePriceSum = null;
        if(mapSum.get("duePrice") != null && !mapSum.get("duePrice").toString().equals("") ){
            duePriceSum = mapSum.get("duePrice").getSum().toString();
        }
        String diffPriceSum = null;
        if(mapSum.get("diffPrice") != null && !mapSum.get("diffPrice").toString().equals("") ){
            diffPriceSum = mapSum.get("diffPrice").getSum().toString();
        }
        List<OrderInfo> list1 = new ArrayList<OrderInfo>();
        DocumentObjectBinder binder = new DocumentObjectBinder();
        Iterator iterator = list.iterator();
        while(iterator.hasNext()){
            OrderInfo orderInfo = binder.getBean(OrderInfo.class, (SolrDocument) iterator.next());
            list1.add(orderInfo);
        }
        map.put("orderPriceSum", orderPriceSum);
        map.put("duePriceSum", duePriceSum);
        map.put("diffPriceSum", diffPriceSum);
        Page<OrderInfo> pageList = new PageImpl<OrderInfo>(list1,page,list.getNumFound());
        return pageList;
    } 
    /**
     * 我们可以按照key值进行主键查询。
     * @param id
     * @return
     * @throws Exception
     */
    
    public List<OrderInfo> queryByOrderId(String id) throws Exception {
        SolrQuery sQuery = new SolrQuery();
        String filter = "orderId" + ":" + id;
        sQuery.setQuery(filter);
        QueryResponse response = null;
        try {
            response = solrServer.query(sQuery);
        } catch (SolrServerException e) {
            e.printStackTrace();
        }
        SolrDocumentList list = response.getResults();
        List<OrderInfo> list1 = new ArrayList<OrderInfo>();
        DocumentObjectBinder binder = new DocumentObjectBinder();
        Iterator iterator = list.iterator();
        while(iterator.hasNext()){
            OrderInfo orderInfo = binder.getBean(OrderInfo.class, (SolrDocument) iterator.next());
            list1.add(orderInfo);
        }
        return list1;
    }

    
    public void deleteAll(OrderInfo orderInfo) throws IOException, SolrServerException {
        String sQuery = "*:*";
        solrServer.deleteByQuery(sQuery);
    }

    
    public void deleteById(String id) {
    }

    
    public void createIndexBatch(List<OrderInfo> orderInfoList) throws IOException, SolrServerException {
        solrServer.addBeans(orderInfoList);
        solrServer.commit();

    }

    
    public void deleteBySolrQuery(String solrQuery) throws IOException, SolrServerException {
        solrServer.deleteByQuery(solrQuery);
        solrServer.commit();
    }

    public SolrServer getSolrServer() {
        return solrServer;
    }

    public void setSolrServer(SolrServer solrServer) {
        this.solrServer = solrServer;
    }
}
当然solr的api不止于此,我们此处只是罗列了一些比较常用的使用方法。对于solr的查询,有以下几点需要注意。

1.    solr生成查询语句的时候,是有q查询和fq查询之分的。哪些查询条件放在q查询里,哪些查询条件放在fq查询里,对查询的效率还是有较大的影响的。一般固定不变的查询条件放在q查询里,经常变化的查询条件放在fq里。上述代码中validTag:1就放在了q查询里,循环里的字符串filter则放在了我们的fq查询里。

2.    solr查询时,要了解solr服务器集群的配置文件中使用的是什么样的分词器,不同分词器对模糊查询的结果是有影响的。比如常见的IK分词器和标准分词器(如果我们有一个字段的名称为:我是中国人,ik分词器在solr里的存储就成为了“我”,“中国人”,“中国”,“国人”。而标准分词器则会存储为“我”,“是”,“中”,“国”,“人”。如果我们使用全称查询,即查询:我是中国人,两者是没有问题的。但是使用模糊查询,比如查询“*我是*”,则两个分词器分词都查不出来结果,而如果我们的查询条件是“*中国人*”则在ik分词器下可以查询出结果,在标准分词器下查不出结果。)

3.    使用solr的过程中,需要定时执行solr的optimize函数来清理磁盘碎片,否则会影响读写效率。对于optimize的参数建议为(false,false,5)。

4.    写solr数据的时候,尽量使用createIndexBatch方法,这是因为solr在执行写入的时候,写入一条数据和写入多条数据都需要全量建索引,其执行时间是差不多的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值