solrj是一个访问solr的客户端,它提供了一个接口,用于添加、更新、删除索引数据.
solrj跨版本兼容
solrj一般保持向后兼容,所以你可以使用新版本的solrj访问老版本的solr服务,也可以使用老版本的solrj访问新版本的solr.
如果混合了1.x和以后的主版本,那么你必须设置响应解析器XML:
server.setParser(new XMLResponseParser());
3.x和4.x或者以后的版本混合的solr工作正常,只要不更改这个请求writer为二进制格式.
有一个例外情况是javabin格式,兼容方式在主版本之间已经改变.
设置类路径
solrj需要的最小jar包集合:
/dist:
apache-solr-solrj-*.jar
/dist/solrj-lib:
-
- commons-codec-1.3.jar
- commons-httpclient-3.1.jar
- commons-io-1.4.jar
- jcl-over-slf4j-1.5.5.jar
- slf4j-api-1.5.5.jar
/lib:
slf4j-jdk14-1.5.5.jar
Maven
在官方maven仓库中solrj是可以使用的,加入一下依赖到pom.xml中:
<dependency> <artifactId>solr-solrj</artifactId> <groupId>org.apache.solr</groupId> <version>4.7.2</version> <type>jar</type> <scope>compile</scope> </dependency>
如果使用EmbeddedSolrServer,加入一下依赖:
<dependency> <artifactId>solr-core</artifactId> <groupId>org.apache.solr</groupId> <version>4.7.2</version> <type>jar</type> <scope>compile</scope> </dependency>
同样,如果使用了EmbeddedSolrServer,注意Servlet API的依赖:
<dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>3.0</version> </dependency>
如果碰到NoClassDefFoundError 异常,加入一下依赖:
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.6.6</version> </dependency>
HttpSolrServer
HttpSolrServer使用Apache Commons HTTP Client连接solr. 注意:在solr4.0中, CommonsHttpSolrServer 改成了 HttpSolrServer ,StreamingUpdateSolrServer 改成了ConcurrentUpdateSolrServer.
String url = "http://localhost:8983/solr"; /* * HttpSolrServer线程安全,如果使用下面构造器,必须对所有的请求重用相同的实例.如果实例在运行中创建的,它可能会导致连接泄漏. * 推荐的做法就是保持每个solr服务url的HttpSolrServer的静态实例,所有的请求都共享这个实例. * 参考https://issues.apache.org/jira/browse/SOLR-861查看更多细节. */ SolrServer server = new HttpSolrServer(url);
具体可以参考:HttpSolrServer-采用静态工厂方法,创建HttpSolrServer单实例.如果你有更好的方法,可以回复一下,互相交流.
修改其他的Connection设置:
String url = "http://localhost:8983/solr"; HttpSolrServer server = new HttpSolrServer(url); server.setMaxRetries(1); // defaults to 0. > 1 not recommended. server.setConnectionTimeout(5000); // 5 seconds to establish TCP // 下面的设置被完整的提供,它们一般不是必须选择的,最好是在查阅java文档之后来确认它们是否必须使用. server.setSoTimeout(1000); // socket read timeout server.setDefaultMaxConnectionsPerHost(100); server.setMaxTotalConnections(100); server.setFollowRedirects(false); // defaults to false // 允许压缩默认为false. // Server side must support gzip or deflate for this to have any effect. server.setAllowCompression(true);
使用
首先创建一个服务的实例:
SolrServer server = new HttpSolrServer("http://HOST:8983/solr/");
删除所有索引数据:
server.deleteByQuery( "*:*" );
构造一个document:
SolrInputDocument doc1 = new SolrInputDocument(); doc1.addField( "id", "id1", 1.0f ); doc1.addField( "name", "doc1", 1.0f ); doc1.addField( "price", 10 );
构造另外一个document,最好是批量处理document,因为每次调用solrServer,就是一次http调用.
SolrInputDocument doc2 = new SolrInputDocument(); doc2.addField( "id", "id2", 1.0f ); doc2.addField( "name", "doc2", 1.0f ); doc2.addField( "price", 20 );
创建一个document的集合:
Collection<SolrInputDocument> docs = new ArrayList<SolrInputDocument>(); docs.add( doc1 ); docs.add( doc2 );
添加集合到solrServer,并提交.
server.add( docs );
server.commit()
为了在添加documents之后立刻提交,可以这样使用:
UpdateRequest req = new UpdateRequest(); req.setAction( UpdateRequest.ACTION.COMMIT, false, false ); req.add( docs ); UpdateResponse rsp = req.process( server );
更新文档流(streamimg documents)
大多数情况下,ConcurrentUpdateSolrServer就可以满足需要.作为一种选择,下面给出的解决方法可以应用。
这是一个在一个请求里更新所有文档的最优化的方案:
HttpSolrServer server = new HttpSolrServer(); Iterator<SolrInputDocument> iter = new Iterator<SolrInputDocument>(){ public boolean hasNext() { boolean result ; // set the result to true false to say if you have more documensts return result; } public SolrInputDocument next() { SolrInputDocument result = null; // construct a new document here and set it to result return result; } }; server.add(iter);
也可以使用addBeans(Iterator<?> beansIter)来索引对象.
直接添加POJO到Solr
使用注解创建一个java bean.@Field可以用到字段(field)或者是方法(setter)上.如果索引字段名称和bean的字段名称不相同,可以使用别名来做一个映射,
import org.apache.solr.client.solrj.beans.Field; public class Item { @Field String id; @Field("cat") String[] categories; @Field List<String> features; }
或者是在setter方法中添加:
@Field("cat") public void setCategory(String[] c){ this.categories = c; }
获取SolrServer实例:
SolrServer server = getSolrServer();
创建bean实例:
Item item = new Item(); item.id = "one"; item.categories = new String[] { "aaa", "bbb", "ccc" };
添加到solr:
server.addBean(item);
添加bean集合:
List<Item> beans ; //add Item objects to the list server.addBeans(beans);
solr事务处理
solr在服务器级别实现事务处理.也就是说每次commit, optimize, or rollback都应用到了所有的请求中.更新solr最恰当的方式就是使用单独的进程来处理,以避免在提交或者是回滚时产生的竞争冲突.同样,最好是使用批处理来索引,因为提交和优化都是高消耗的程序.
读数据--数据库

import java.io.IOException; import java.net.MalformedURLException; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; import java.util.Collection; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.impl.HttpSolrServer; import org.apache.solr.common.SolrInputDocument; public class Test { private static int fetchSize = 1000; private static String url = "http://localhost:8983/solr/core1/"; private static HttpSolrServer solrCore; public Test() throws MalformedURLException { solrCore = new HttpSolrServer(url); } /** * Takes an SQL ResultSet and adds the documents to solr. Does it in batches * of fetchSize. * * @param rs * A ResultSet from the database. * @return The number of documents added to solr. * @throws SQLException * @throws SolrServerException * @throws IOException */ public long addResultSet(ResultSet rs) throws SQLException, SolrServerException, IOException { long count = 0; int innerCount = 0; Collection<SolrInputDocument> docs = new ArrayList<SolrInputDocument>(); ResultSetMetaData rsm = rs.getMetaData(); int numColumns = rsm.getColumnCount(); String[] colNames = new String[numColumns + 1]; /** * JDBC numbers the columns starting at 1, so the normal java convention * of starting at zero won't work. */ for (int i = 1; i < (numColumns + 1); i++) { colNames[i] = rsm.getColumnName(i); /** * If there are fields that you want to handle manually, check for * them here and change that entry in colNames to null. This will * cause the loop in the next section to skip that database column. */ // //Example: // if (rsm.getColumnName(i) == "db_id") // { // colNames[i] = null; // } } while (rs.next()) { count++; innerCount++; SolrInputDocument doc = new SolrInputDocument(); /** * At this point, take care of manual document field assignments for * which you previously assigned the colNames entry to null. */ // //Example: // doc.addField("solr_db_id", rs.getLong("db_id")); for (int j = 1; j < (numColumns + 1); j++) { if (colNames[j] != null) { Object f; switch (rsm.getColumnType(j)) { case Types.BIGINT: { f = rs.getLong(j); break; } case Types.INTEGER: { f = rs.getInt(j); break; } case Types.DATE: { f = rs.getDate(j); break; } case Types.FLOAT: { f = rs.getFloat(j); break; } case Types.DOUBLE: { f = rs.getDouble(j); break; } case Types.TIME: { f = rs.getDate(j); break; } case Types.BOOLEAN: { f = rs.getBoolean(j); break; } default: { f = rs.getString(j); } } doc.addField(colNames[j], f); } } docs.add(doc); /** * When we reach fetchSize, index the documents and reset the inner * counter. */ if (innerCount == fetchSize) { solrCore.add(docs); docs.clear(); innerCount = 0; } } /** * If the outer loop ended before the inner loop reset, index the * remaining documents. */ if (innerCount != 0) { solrCore.add(docs); } return count; } }
设置RequestWriter
solrj允许使用XML和二进制两种格式上传内容,默认设置是XML.下面使用的是二进制上传内容,solrj获取结果采用相同的格式.并且这样极大的提高了性能,因为它减少了XML编组开销.
server.setRequestWriter(new BinaryRequestWriter());
注意:确定solrconfig.xml中同样开启"BinaryUpdateRequestHandler"的功能:
<requestHandler name="/update/javabin" class="solr.BinaryUpdateRequestHandler" />
Solr中读取查询内容
获取一个solrServer实例:
SolrServer server = getSolrServer();
构造SolrQuery:
SolrQuery query = new SolrQuery(); query.setQuery( "*:*" ); query.addSortField( "price", SolrQuery.ORDER.asc );
执行查询:
QueryResponse rsp = server.query( query );
获取结果:
SolrDocumentList docs = rsp.getResults();
获取bean对象的结果集,bean类必须采用注解.
List<Item> beans = rsp.getBeans(Item.class);
高级使用方式
SolrJ提供了一个API来创建查询,而不是手工编码查询.下面是一个分面查询的例子:
SolrServer server = getSolrServer(); SolrQuery solrQuery = new SolrQuery(). setQuery("ipod"). setFacet(true). setFacetMinCount(1). setFacetLimit(8). addFacetField("category"). addFacetField("inStock"); QueryResponse rsp = server.query(solrQuery);
高亮--Highlighting
设置高亮参数的方式和其他常用参数的设置方式一样:
SolrQuery query = new SolrQuery(); query.setQuery("foo"); query.setHighlight(true).setHighlightSnippets(1); //set other params as needed query.setParam("hl.fl", "content"); QueryResponse queryResponse = getSolrServer().query(query);
这样取回高亮结果集:
Iterator<SolrDocument> iter = queryResponse.getResults().iterator(); while (iter.hasNext()) { SolrDocument resultDoc = iter.next(); String content = (String) resultDoc.getFieldValue("content"); String id = (String) resultDoc.getFieldValue("id"); //id is the uniqueKey field if (queryResponse.getHighlighting().get(id) != null) { List<String> highlightSnippets = queryResponse.getHighlighting().get(id).get("content"); } }
负载均衡查询
当你有多个Solr的服务器可以处理查询时,虽然你肯定喜欢选择使用的外部负载平衡器,但是solrj提供了一个简单的内置的轮询负载均衡功能LBHttpSolrServer.
使用SolrCloud
SolrJ包含了SolrCloud的一个敏捷的客户端,也就是ZooKeeper.这就意味着你的java应用程序只需要知道Zookeeper实例,并不知道你的solr实例在什么地方,因为这些可以从ZooKeeper中获取.
为了和SolrCloud交互,需要使用CloudSolrServer的实例,并且传入你得ZooKeeper主机地址(一个或者多个).除了CloudSolrServer的实例化不一样外,其他的处理方式都是一样的:
import org.apache.solr.client.solrj.impl.CloudSolrServer; import org.apache.solr.common.SolrInputDocument; CloudSolrServer server = new CloudSolrServer("localhost:9983"); server.setDefaultCollection("collection1"); SolrInputDocument doc = new SolrInputDocument(); doc.addField( "id", "1234"); doc.addField( "name", "A lovely summer holiday"); server.add(doc); server.commit();