This is a relational model of the same schema that Solr currently ships with. 我们使用这个例子来为我们的DataImportHandler建data-config.xml。 我们已经使用这个结构在HSQLDB上建立了一个数据库. 好,现在开始了, 跟着下面的步骤走:
下载 example-solr-home.jar 并 使用 jar解压 jar -xvf example-solr-home.jar ,解压到你的本地系统. 这个jar文件包含了一个完整的solrhome(里面的配置文件很齐全了)和一个RSS的例子。它也包含了一个hssqldb数据库的例子.
在 example-solr-home目录, 这里有一个 solr.war . 拷贝 这个 war 文件到你的 tomcat/jetty webapps 文件夹. 这个 war file 也包含了hsqldb的JDBC driver. 如果你想在你已经有了的solr项目中部署,你只需要将 'dataimport.jar' 拷贝到 你的solr项目的 WEB-INF/lib 目录下。
使用example-data-config目录下的solr目录作为你solrhome
访问 http://localhost:8983/solr/dataimport 验证一下配置
访问 http://localhost:8983/solr/dataimport?command=full-import 执行一个“完全导入”
上面给出的solr目录是一个多核的solr home。它有两个核,一个是DB example,一个是RSSexample(新属性)。
这个例子的data-config.xml 如下:
<dataConfig>
<dataSource driver="org.hsqldb.jdbcDriver" url="jdbc:hsqldb:/temp/example/ex" user="sa" />
<document name="products">
<entity name="item" query="select * from item">
<field column="ID" name="id" />
<field column="NAME" name="name" />
<field column="MANU" name="manu" />
<field column="WEIGHT" name="weight" />
<field column="PRICE" name="price" />
<field column="POPULARITY" name="popularity" />
<field column="INSTOCK" name="inStock" />
<field column="INCLUDES" name="includes" />
<entity name="feature" query="select description from feature where item_id='${item.ID}'">
<field name="features" column="description" />
</entity>
<entity name="item_category" query="select CATEGORY_ID from item_category where item_id='${item.ID}'">
<entity name="category" query="select description from category where id = '${item_category.CATEGORY_ID}'">
<field column="description" name="cat" />
</entity>
</entity>
</entity>
</document>
</dataConfig>
这里, 根实体是一个名叫“item”的表,它的主键是id。我们使用语句 "select * from item"读取数据. 每一项都拥有多个特性。看下面feature实体的查询语句
<entity name="feature" query="select description from feature where item_id='${item.id}'">
<field name="feature" column="description" />
</entity>
feature表中的外键item_id跟item中的主键连在一起从数据库中取得该row的数据。相同地,我们将item和category连表 (它们是多对多的关系)。注意,我们是怎样使用中间表和标准sql连表的
<entity name="item_category" query="select category_id from item_category where item_id='${item.id}'">
<entity name="category" query="select description from category where id = '${item_category.category_id}'">
<field column="description" name="cat" />
</entity>
</entity>
短一点的 data-config
在上面的例子中,这里有好几个从域到solr域之间的映射。如果域的名字和solr中域的名字是一样的话,完全避免使用在实体中配置域也是可以的。 当然,如果你需要使用转换器的话,你还是需要加上域实体的。
下面是一个更短的版本
<dataConfig>
<dataSource driver="org.hsqldb.jdbcDriver" url="jdbc:hsqldb:/temp/example/ex" user="sa" />
<document>
<entity name="item" query="select * from item">
<entity name="feature" query="select description as features from feature where item_id='${item.ID}'"/>
<entity name="item_category" query="select CATEGORY_ID from item_category where item_id='${item.ID}'">
<entity name="category" query="select description as cat from category where id = '${item_category.CATEGORY_ID}'"/>
</entity>
</entity>
</document>
</dataConfig>
使用“增量导入”命令
你可以通过访问URL http://localhost:8983/solr/dataimport?command=delta-import 来使用增量导入。操 作将会新起一个线程,response中的属性statue也将显示busy now。操作执行的时间取决于你的数据集的大小。在任何时候,你都可以通过访问 http://localhost:8983/solr/dataimport 来查看状态。
当 增量导入被执行的时候,它读取存储在conf/dataimport.properties中的“start time”。它使用这个时间戳来执行增量查询,完成之后,会更新这个放在conf/dataimport.properties中的时间戳。
Delta-Import 例子
我们将使用跟“完全导入”中相同的数据库。注意,数据库已经被更新了,每个表都包含有一个额外timestamp类型的列 叫做last_modified。或许你需要重新下载数据库,因为它最近被更新了。我们使用这个时间戳的域来区别出那一行是上次索引以来有更新的。
看看下面的这个 data-config.xml
<dataConfig>
<dataSource driver="org.hsqldb.jdbcDriver" url="jdbc:hsqldb:/temp/example/ex" user="sa" />
<document name="products">
<entity name="item" pk="ID" query="select * from item"
deltaQuery="select id from item where last_modified > '${dataimporter.last_index_time}'">
<entity name="feature" pk="ITEM_ID"
query="select description as features from feature where item_id='${item.ID}'">
</entity>
<entity name="item_category" pk="ITEM_ID, CATEGORY_ID"
query="select CATEGORY_ID from item_category where ITEM_ID='${item.ID}'">
<entity name="category" pk="ID"
query="select description as cat from category where id = '${item_category.CATEGORY_ID}'">
</entity>
</entity>
</entity>
</document>
</dataConfig>
注意到item实体的 属性deltaquery了吗,它包含了一个能够查出最近更新的sql语句。注意,变量{dataimporter.last_index_time } 是DataImporthandler传过来的变量,我们叫它时间戳,它指出“完全导入”或者“部分导入”的最后运行时间。你可以在data- config.xml文件中的sql的任何地方使用这个变量,它将在processing这个过程中被赋值。
注意
上面例子中deltaQuery 只能够发现item中的更新,而不能发现其他表的。你可以像下面那样在一个sql语句中指定所有的表的更新。这里要特别说明一下的就是,它的细节对于一个 使用者来说是一个不错的练习。
deltaQuery="select id from item where id in
(select item_id as id from feature where last_modified > '${dataimporter.last_index_time}')
or id in
(select item_id as id from item_category where item_id in
(select id as item_id from category where last_modified > '${dataimporter.last_index_time}')
or last_modified > '${dataimporter.last_index_time}')
or last_modified > '${dataimporter.last_index_time}'"
写一个类似上面的庞大的deltaQuery 并不是一件很享受的工作,我们还是选择其他的方法来达到这个目的
<dataConfig>
<dataSource driver="org.hsqldb.jdbcDriver" url="jdbc:hsqldb:/temp/example/ex" user="sa" />
<document>
<entity name="item" pk="ID" query="select * from item"
deltaQuery="select id from item where last_modified > '${dataimporter.last_index_time}'">
<entity name="feature" pk="ITEM_ID"
query="select DESCRIPTION as features from FEATURE where ITEM_ID='${item.ID}'"
deltaQuery="select ITEM_ID from FEATURE where last_modified > '${dataimporter.last_index_time}'"
parentDeltaQuery="select ID from item where ID=${feature.ITEM_ID}"/>
<entity name="item_category" pk="ITEM_ID, CATEGORY_ID"
query="select CATEGORY_ID from item_category where ITEM_ID='${item.ID}'"
deltaQuery="select ITEM_ID, CATEGORY_ID from item_category where last_modified > '${dataimporter.last_index_time}'"
parentDeltaQuery="select ID from item where ID=${item_category.ITEM_ID}">
<entity name="category" pk="ID"
query="select DESCRIPTION as cat from category where ID = '${item_category.CATEGORY_ID}'"
deltaQuery="select ID from category where last_modified > '${dataimporter.last_index_time}'"
parentDeltaQuery="select ITEM_ID, CATEGORY_ID from item_category where CATEGORY_ID=${category.ID}"/>
</entity>
</entity>
</document>
</dataConfig>
属性:
baseUrl (可选): 在Dev/QA/Prod 环境中,host/port改变时,你会用到它。使用这个属性,你可以找出配置到solrconfig.xml的变化。
encoding (可选): 默认情况下,encoding是response 头使用的encoding.你可以使用这个属性去覆盖默认值。
connectionTimeout (可选):默认值是5000ms
readTimeout (可选): 默认值是10000ms
在 data-config.xml中的配置
一个 xml/http data source中的实体有下面一些属性,也可以有上面提到的默认属性。
processor (必需的) : 它的值应该是"XPathEntityProcessor"
url (必需的) : REST API要使用这个api. (能够被模板化). 假设数据源是一个文件,那么url应该是这个文件的位置。
stream (可选) : 如果xml很大,那么它应该设为true
forEach (必需的) : xpath表达式,通过这个表达式可以取得想要的值。 如果这里有多个想要的值,那么将xpath表达式用“|”分开。如果useSolrAddSchema设为true的话,这个是可以被忽略的。
xsl (可选):使用xsl对xml进行预处理。你需要提供一个文件系统的全路径,或者一个 url。
useSolrAddSchema (可选): Set it's value to 'true' if the xml that is fed into this processor has the same schema as that of the solr add xml. No need to mention any fields if it is set to true.
域能够有以下这些属性 (此外还有那些默认值):
xpath (必需的) : 记录中的一列,也就是域的xpath表达式 . 如果 该域并不来自任何的一个单一的xml属性,xpath是可以被忽略的. 我们可以通过转化器来使用多个xml属性来合成该域。如果一个域被声明成多值的,如果xpath表达式生成的也是多值的,那么 XPathEntityProcessor将会自动处理它,而不需要我们做额外的工作。
commonField : 能够被设为 (true或者false),假设这个是true值,一旦一个记录中有这样的域,那么其他记录被写索引的时候,这个域也会跟着记录被写到索引里面。
如果 一个API支持分块数据(当一个数据集太大时),可能需要多次调用才能完成这个处理过程。XPathEntityprocessor 通过转换器支持这个特性。如果转换器返回的的行带有属性“hasMore”,并且这个属性的值等于true,那么Processor 将会使用同样的url模板发出令一次请求(实际的url是需要重新计算的)。一个转换器也可以传递一个完整的url路径,这个url被包含在属性 “nextUrl”中,nextUrl的值必需是一个完整的url。
XPathEntityProcessor 通过实现streaming parser来支持取得xpath子集的操作。完整的xpath是不被支持的,但是常见的应用都是受支持的。
HttpDataSource 例子
下载 在DB 部分中的“完全导入”例子,试着去体验一下。我们将在这里例子中为slashotRSS建立索引。
这个例子的data-config配置看起来像这样。
<dataConfig>
<dataSource type="HttpDataSource" />
<document>
<entity name="slashdot"
pk="link"
url="http://rss.slashdot.org/Slashdot/slashdot"
processor="XPathEntityProcessor"
forEach="/RDF/channel | /RDF/item"
transformer="DateFormatTransformer">
为7278241个文章建立索引大概花了2个小时40分,内存使用量的峰值在4G左右。
使用“增量导入”命令
只有SqlEntitiProcessor支持增量数据!XPathEntityProcessor还没有实现它。所以,不幸运的是,现在还不能为 “增量导入”提供支持。如果你想要在XPathEntityProcessor中实现这些方法,你可以在EntityProcessor.java中看看 这些方法的解释。
Extending the tool with APIs
我们所展现的例子确实没有多大价值,单靠配置xml文件就满足所有的需求是不可能的。所以我们提供了一些抽象类,可以通过这些方法来提高功能。
Transformer
每一条从数据库中取得的数据能够被直接处理掉,或者通过它创建一个全新的域,它设置能够返回多行数据。配置文件必须像下面那样设置。
<entity name="foo" transformer="com.foo.Foo" ... />
注意-- trasformer的值必须是一个可以使用的classname。如果class包是'org.apache.solr.handler.dataimport' ,包名可以被忽略。solr.也是可以使用的,如果这个class在solr的一个包下的话。这个规则适应所有的可插入的类,像 DataSource、EntityProcessor、Evaluator。
类Foo必须继承抽象类org.apache.solr.hander.dataimport.Transformer.这个类只有一个抽 象方法。
transformer这个属性可以有多个transformers() (比 如 transformer="foo.X,foo.Y" ) 之间用逗号隔开。 transformers 会形成一条处理链。它们将会按照它们的排列顺序起作用。
public abstract class Transformer {
/**
* The input is a row of data and the output has to be a new row.
*
* @param context The current context
* @param row A row of data
* @return The changed data. It must be a Map if it returns
* only one row or if there are multiple rows to be returned it must
* be a List>
*/
public abstract Object transformRow(Map row, Context context);
}
这里模板的规则跟‘query’、‘url’的规则是一样的。它主要能帮我们将多个值连到一起,或者忘域值注入其他的字符。这个转换器只对拥有属性 ‘template’的域起作用。
属性
template : 模板字符串。上面的例子中有两个占位符,‘${e.name}和${eparent.surname}’。 In the above example there are two placeholders '${e.name}' and '${eparent.surname}' . 两个值都必须存在,否则这个模板将不会起作用。
自定义模板转换器
如果你需要在将数据送给solr之前,对数据进行一些处理,你可以写一个你自己的转换器。让我们来看一个例子。在我们的schema中我们有一个单 值的域叫做‘artistName’,类型是String。这个域的值包含了多个单词,例如‘Celine Dion’,这里有一个问题 ,这个值包含一些开头空格和结尾空格,这些空格不是我们想要的。solr的WhitespaceAnalyze在这里用不上,因为,我们并不想把这个字符 串切词了。一个可以选择的解决方案就是自己写一个TrimTransformer。
一个简单的TrimTransformer
package foo;
public class TrimTransformer {
public Object transformRow(Map row) {
String artist = row.get("artist");
if (artist != null)
row.put("ar", artist.trim());
for (Map field : fields) {
// Check if this field has trim="true" specified in the data-config.xml
String trim = field.get("trim");
if ("true".equals(trim)) {
// Apply trim on this field
String columnName = field.get("column");
// Get this field's value from the current row
String value = row.get(columnName);
// Trim and put the updated value back in the current row
if (value != null)
row.put(columnName, value.trim());
}
}
千万要注意rootEntiry这个属性,由这个处理器所产生的域有fileAbsolutePath,fileSize,fileLastModified,fileName .
CachedSqlEntityProcessor
应该说,这是SqlEntityProcessor的一个扩展,这个处理器通过缓存一些行,来减少数据库查询。它几乎对根实体没有用,因为这个实体 中只有一个sql语句被执行了。
Example 1.
<entity name="x" query="select * from x">
<entity name="y" query="select * from y where xid=${x.id}" processor="CachedSqlEntityProcessor">
</entity>
<entity>
这个例子的用法跟下面的是一样的,一个查询被执行完,它的结果被存储起来,下次这个查询再被执行的的时候,它将会从缓存中取出结果并返回。
Example 2:
<entity name="x" query="select * from x">
<entity name="y" query="select * from y" processor="CachedSqlEntityProcessor" where="xid=x.id">
</entity>
<entity>
这个例子跟前一个的区别在于属性‘where’。这个例子中,查询语句将从表中取回所有的数据,并把他们都放在缓存中。其中的关键就在域 属性‘where’。缓存使用y中的xid作为键值,实体被查询的时候x.id的值就会被计算出来,我们首先会在缓存中找匹配的数据,接着返回。
在属性where中,=号之前的值是y中的列,=号之后的值是计算出来的要在缓存中查找的值。
DataSource(数据源)
org.apache.solr.handler.dataimport.DataSource 能被继承。
public abstract class DataSource {
/**
* Initializes the DataSource with the Context
and
* initialization properties.
*
* This is invoked by the DataImporter
after creating an
* instance of this class.
*
* @param context
* @param initProps
*/
public abstract void init(Context context, Properties initProps);
/**
* Get records for the given query.The return type depends on the
* implementation .
*
* @param query The query string. It can be a SQL for JdbcDataSource or a URL
* for HttpDataSource or a file location for FileDataSource or a custom
* format for your own custom DataSource.
* @return Depends on the implementation. For instance JdbcDataSource returns
* an Iterator>
*/
public abstract T getData(String query);
/**
* Cleans up resources of this DataSource after use.
*/
public abstract void close();
}
它必须在数据源的定义部分被配置。
<dataSource type="com.foo.FooDataSource" prop1="hello"/>
JdbcdataSource
这个是默认的,它的声明如下:
public class JdbcDataSource extends DataSource >>
它可以一条一条的遍历数据库,每一行数据被当作一个Map。
HttpDataSource
XPathEntityProcessor使用这个数据源 . 它的声明如下:
public class HttpDataSource extends DataSource
FileDataSource
这个很像HttpDataSource . 它的声明如下:
public class FileDataSource extends DataSource
The attributes are:
basePath : (可选的) ,得到所需要的值时必须的基本路径。
encoding : (可选的)当文件编码跟平台编码不一样的时候,应当设定这个值。
Boosting , Skipping documents(提高文档的得分,或者跳过文档)
我们还可以在运行的时候提高一个文档的得分,或者跳过某一个特定的文档。
可以通过自定义转化器,增加一个属性,并将它设为true,这样就可以跳过这个文档了。可以通过,增加一个属性docBoost ,属性是文档的评分的这种方式给文档打分。Write a custom Transformer to add a value $skipDoc with a value 'true' to skip that document. To boost a document with a given value add $docBoost with the boost value
在 solrconfig.xml中增加数据源
我们也可以在solrconfig.xml中配置数据源,属性是一样的,只是方式稍微有点不同。