Compass入门及其与Spring、iBatis的整合
文章分类:Java编程
- 开始之前
- 什么是Compass
- 与Spring、iBatis的整合
- 与Lucene的比较
- 经验总结
- 相关资源
开始之前
本文是Compass的入门指引,通过实例介绍了Compass与iBatis、Spring的整合,适合不了解Compass的读者,但要求读者了解Lucene、Spring和iBatis,写过一些简单的应用。
文中使用的软件包:
什么是Compass
Compass是一个Java搜索框架。它封装了Lucene,增加了一些Lucene不支持的特性(例如实时更新索引),支持各种数据(Java对象、xml、json)到索引的映射,支持各种数据源(JDBC, Hibernate, iBatis)。
图解(看得烦的直接跳过看下面的例子吧):
- Compass - 一般在程序启动时建立并被整个程序共享,主要用于建立CompassSession并通过其管理索引数据。
- CompassSession - 用于处理数据的session。
- CompassTransaction - 手动进行事务管理,如果不使用,Compass会自动管理事务。
- CompassTemplate - 将session和transaction透明化。
- 数据到索引的各种映射 - OSEM, XSEM, JSEM, RSEM。支持通过程序、XML、JSON进行配置。
- CompassGps - Gps的核心模块,管理GpsDevice,有两种实现:SingleCompassGps和DualCompassGps。
- CompassGpsDevice - 处理各种数据源到索引的操作:JDBC, Hibernate, iBatis等。不能独立使用而必须融合到CompassGps中。
与Spring、iBatis的整合
建索引
1、假设Spring + iBatis的框架已经搭建好。
2、配置Domain的OSEM
- @Searchable(alias="user")
- public class User {
- @SearchableId
- private int id;
- @SearchableProperty(index=Index.ANALYZED, store=Store.YES)
- private String name; // 姓名
- @SearchableProperty(index=Index.NOT_ANALYZED, store=Store.YES)
- private String gender; // 性别
- @SearchableProperty(index=Index.NOT_ANALYZED, store=Store.YES)
- private int age; // 年龄
- public User() {
- }
- public User(String name, String gender, int age) {
- setName(name);
- setGender(gender);
- setAge(age);
- }
- public int getId() {
- return id;
- }
- public void setId(int id) {
- this.id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String getGender() {
- return gender;
- }
- public void setGender(String gender) {
- this.gender = gender;
- }
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- }
@Searchable(alias="user")
public class User {
@SearchableId
private int id;
@SearchableProperty(index=Index.ANALYZED, store=Store.YES)
private String name; // 姓名
@SearchableProperty(index=Index.NOT_ANALYZED, store=Store.YES)
private String gender; // 性别
@SearchableProperty(index=Index.NOT_ANALYZED, store=Store.YES)
private int age; // 年龄
public User() {
}
public User(String name, String gender, int age) {
setName(name);
setGender(gender);
setAge(age);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
其实就是加几个Annotation而已。看到Index.ANALYZED、Store.YES这些东西,用过Lucene的应该大概都明白了吧。
- @Searchable - 指明该类可被映射至索引,alias参数是这一类索引对象的别名。
- @SearchableId - 索引对象的id,在同一类索引对象(同一个alias)中唯一标识一个对象。
- @SearchableProperty - 指示一个类属性如何被索引,index和store参数类似Lucene。
3、建立LocalCompassBean,配置索引文件存放路径和进行映射的domain。
- <bean id="compass" class="org.compass.spring.LocalCompassBean">
- <property name="compassSettings">
- <props>
- <!-- 索引文件保存路径 -->
- <prop key="compass.engine.connection">/home/index/compasstest</prop>
- </props>
- </property>
- <property name="classMappings"> <!-- 进行映射的domain -->
- <list>
- <value>ren.domain.User</value>
- <value>ren.domain.Book</value>
- </list>
- </property>
- </bean>
4、建立SqlMapClientGpsDevice,配置iBatis的sqlMapClient和获取数据进行索引的SQL语句id。
- <bean id="ibatisGpsDevice" class="org.compass.gps.device.ibatis.SqlMapClientGpsDevice">
- <property name="name" value="ibatis" />
- <property name="sqlMapClient">
- <ref bean="sqlMapClient" /> <!-- 引用项目中已经定义的ibatis的sqlMapClient -->
- </property>
- <property name="selectStatementsIds"> <!-- 对这些SQL查询的结果进行索引 -->
- <list>
- <value>user.getAllUsers</value>
- <value>book.getAllBooks</value>
- </list>
- </property>
- </bean>
5、建立CompassGps(SingleCompassGps或DualCompassGps),引用前面的compass和device。
- <bean id="compassGps" class="org.compass.gps.impl.SingleCompassGps"
- init-method="start" destroy-method="stop">
- <property name="compass" ref="compass" />
- <property name="gpsDevices">
- <list>
- <ref local="ibatisGpsDevice"/>
- </list>
- </property>
- </bean>
6、最后,直接调用CompassGps.index()方法建立索引。
- @Component
- @Qualifier("indexBuilder")
- public class IndexBuilder {
- @Autowired
- @Qualifier("compassGps")
- private CompassGps compassGps;
- public void buildIndex() {
- compassGps.index(); // 一行代码搞定
- }
- }
@Component
@Qualifier("indexBuilder")
public class IndexBuilder {
@Autowired
@Qualifier("compassGps")
private CompassGps compassGps;
public void buildIndex() {
compassGps.index(); // 一行代码搞定
}
}
查索引
1、建立CompassTemplate,引用LocalCompassBean。
- <bean id="compassTemplate" class="org.compass.core.CompassTemplate">
- <property name="compass">
- <ref bean="compass" />
- </property>
- </bean>
2、使用CompassTemplate.execute(CompassCallback<T>)进行查询。
- @Component
- @Qualifier("indexSearcher")
- public class IndexSearcher {
- @Autowired
- @Qualifier("compassTemplate")
- private CompassTemplate compassTemplate;
- /**
- * 搜索用户
- */
- public List<User> searchUser(final String name, final String gender, final int age) {
- return compassTemplate.execute(new CompassCallback<List<User>>() {
- public List<User> doInCompass(CompassSession session) throws CompassException {
- CompassQueryBuilder builder = session.queryBuilder();
- String queryString = "";
- if (!StringUtils.isBlank(name)) {
- queryString += "and user.name:" + name;
- }
- if (!StringUtils.isBlank(gender)) {
- queryString += "and user.gender:" + gender;
- }
- if (age > 0) {
- queryString += "and user.age:" + age;
- }
- CompassQuery query = builder.queryString(queryString).toQuery();
- query.addSort("user.age", SortPropertyType.INT, SortDirection.REVERSE);
- CompassHits hits = query.hits();
- List<User> list = new ArrayList<User>();
- for (CompassHit hit : hits) {
- list.add((User)hit.data());
- }
- return list;
- }
- });
- }
- }
@Component
@Qualifier("indexSearcher")
public class IndexSearcher {
@Autowired
@Qualifier("compassTemplate")
private CompassTemplate compassTemplate;
/**
* 搜索用户
*/
public List<User> searchUser(final String name, final String gender, final int age) {
return compassTemplate.execute(new CompassCallback<List<User>>() {
public List<User> doInCompass(CompassSession session) throws CompassException {
CompassQueryBuilder builder = session.queryBuilder();
String queryString = "";
if (!StringUtils.isBlank(name)) {
queryString += "and user.name:" + name;
}
if (!StringUtils.isBlank(gender)) {
queryString += "and user.gender:" + gender;
}
if (age > 0) {
queryString += "and user.age:" + age;
}
CompassQuery query = builder.queryString(queryString).toQuery();
query.addSort("user.age", SortPropertyType.INT, SortDirection.REVERSE);
CompassHits hits = query.hits();
List<User> list = new ArrayList<User>();
for (CompassHit hit : hits) {
list.add((User)hit.data());
}
return list;
}
});
}
}
拼查询字符串这里写得比较累赘,小朋友不要学~
与Lucene的比较
1、Compass有比Lucene更易用的API(废话,封装了Lucene嘛),例如支持直接更新记录(因为resource类似数据库记录,含有主键)。像上面的建索引过程,如果用Lucene,肯定得写很多Java代码。
2、支持整合各种ORM框架和Spring,减少了代码量。例如上面例子中整合iBatis,直接几行配置就搞定了。
3、效率问题?感觉Lucene的API用起来老是不顺手,Compass这样封装虽然方便了,但有些担心会不会降低了性能,于是做了个简单的测试,分别索引4万条记录,结果是
Compass: 12203 ms.
Lucene: 9797 ms.
Compass比Lucene慢了大约25%,当然这个测试十分粗略,结果仅供参考。
经验总结
1、对多个表建索引后进行搜索,在添加排序条件时,如果不指定SortPropertyType,那么在没有指定converter的字段上排序时会抛Exception:
java.lang.RuntimeException: field "gender" does not appear to be indexed
但如果只对单个表建索引,不会有这个问题。应该是Compass的一个bug,不知道新版本有没有解决。
2、最好自己封装排序字段和分页。
3、总结,Compass比较适用于逻辑不太复杂的应用,会比Lucene少写很多代码。但如果需要一些较为特殊的需求,或者对效率要求比较高,还是用Lucene吧。
相关资源
Compass入门指南:http://www.yeeach.com/2008/03/23/compass-%E5%85%A5%E9%97%A8%E6%8C%87%E5%8D%97/
全文检索的基本原理:http://blog.csdn.net/forfuture1978/archive/2009/10/22/4711308.aspx
大型网站的Lucene应用:http://www.luanxiang.org/blog/archives/605.html
写在后面
这篇文章是根据自己的一次演讲整理出来的,主要就是PPT内容 + 代码。演讲和写文章确实很不同,感觉很多地方还说不清楚,因为内容太多了吧,看来自己的书面组织能力还有待提高啊~