在前面的文章中,我们介绍了 H2 的一些特性以及 为什么H2 适合应用在测试环境中。H2 不但可以作为嵌入式数据库、内存数据库使用。在适当的场景下可以选择使用 H2 替换掉 SQLite,还可利用 H2 内存数据库的特点,将它还提供了全文检索的功能。
H2 内置了两个全文检索(FullText Search)的实现:
- Native FullText Search。使用 H2 中内置的全文检索,将索引存储在数据库指定的表中。
- Apache Lucene FullText Search。 H2 使用 Java 来进行变得,因此可以依赖第三方库来实现功能的扩展,在
1 命令行中使用 Native FullText Search
下面的例子中有主要涉及到两个表:Car(汽车)、Brand(厂商),其中涉及到一些关键词两个表中都涉及到,通过全文检索能够快速定位到数据在这两表中的位置。
1.1 创建表
创建 cars 和 brands 的表结构。
create SCHEMA TEST_SCHEMA;
create table TEST_SCHEMA.cars
(
id bigint generated always as identity not null,
name varchar(20),
introduce varchar(200),
primary key (id)
);
create table TEST_SCHEMA.brands
(
id bigint generated always as identity not null,
name varchar(20),
primary key (id)
);
创建后如下图:
1.2 创建索引
使用 FT_INIT() 来进行全文检索的初始化,初始化过程指定使用 H2 内置的全文检索功能。
create alias if not exists FT_INIT for "org.h2.fulltext.FullText.init";
CALL FT_INIT();
执行完语句之后会创建名字为 FT
的 Schema,在 FT 中会创建几个新的表,其中 INDEXS
中存储的是建立索引的规则。
指定建立索引的表和列。
CALL FT_CREATE_INDEX('TEST_SCHEMA', 'CARS', NULL);
CALL FT_CREATE_INDEX('TEST_SCHEMA', 'BRANDS', NULL);
FT_CREATE_INDEX
函数的
第一个参数指定的建立索引的 SCHEMA Name;
第二个参数是建立索引的 TABLE Name;
第三个参数是建立索引的列表,当为 NULL 时表示为所有列建立索引。
1.3 插入数据并查询索引
insert into TEST_SCHEMA.cars values (1, 'benz A200', 'Benz A200 L Car'), (2, 'BMW 3', 'BMW 3 2.0L');
insert into TEST_SCHEMA.brands values (1, 'benz'), (2, 'BMW');
插入数据后,结构如下:
搜索之前我们先确定要得到的结果,通过上图,我们知道,包含关键字 benz 的关键字记录一共 有两条。
- cars 表中的 id 为 1 的记录,出现在 name、introduce 两列中,
- brands 表中 id 为 1 的记录,出现在 name 列中。
查询关键字 benz
应该得到 2 条记录;
SELECT * FROM FT_SEARCH_DATA('benz', 0, 0);
搜索结果包含 5 个字段:
SCHEMA: 搜索到的记录所属的 Schema 名称;
TABLE: 搜索到的记录所属的 table 名称
COLUMNS: 搜索到的结果定位的 column 名
KEYS: 搜索到的结果记录对应的地址
SCORE: 搜索到的结果评分,在 H2 的 Native FullText Search 中 score 的值始终为 1.0
查询结果如下:
另外搜索的结果是忽略大小写的,一次搜索 BENZ
会得到的同样的搜索结果。
经过尝试,H2 内置的全文检索是按照英文字符进行分词的,数字和字母分词,如果是中文依然按照英文字符进行分词。
例如:
"马自达,创驰蓝天" 分词后为 "马自达,创驰蓝天"
"创驰蓝天,2.5L" 分词后为"创驰蓝天","2","5","L","创驰蓝天,2",创驰蓝天,2.5","创驰蓝天,2.5L", "2.5", "5L", "2.5"。
了解分词之后规则之后,在一些简单的场景中就可以使用这种简单的全文检索功能。
1.4 删除索引
# 删除指定的库
call FT_DROP('TEST_SCHEMA', 'CARS');
# 删除全部索引
call FT_DROP_ALL();
2 Java 代码中使用 H2 的全文检索功能
Spring Boot 2.x 中使用的数据库连接池为 HikariCP,
application.properties
spring.datasource.schema=schema.sql
spring.datasource.data=data.sql
spring.datasource.type=org.h2.jdbcx.JdbcDataSource
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
schema.sql
create table cars
(
id bigint generated always as identity not null,
name varchar(20),
introduce varchar(200),
primary key (id)
);
create table brands
(
id bigint generated always as identity not null,
name varchar(20),
primary key (id)
);
# 使用 H2 Native FullText Search 初始化
create alias if not exists FT_INIT for "org.h2.fulltext.FullText.init";
CALL FT_INIT();
# 创建索引
CALL FT_CREATE_INDEX('PUBLIC', 'CARS', NULL);
CALL FT_CREATE_INDEX('PUBLIC', 'BRANDS', NULL);
data.sql
insert into cars values (1, 'benz A200', 'Benz A200 L Car'), (2, 'BMW 3', 'BMW 3 2.0L');
insert into brands values (1, 'benz'), (2, 'BMW');
测试代码
@SpringBootTest
class FullTextSearchTests {
@Autowired
private FullTextService fullTextService;
@Test
void should_got_2_record_when_fulltext_search_given_2_cars_records_and_2_brands_records() throws SQLException {
List<FullTextSearchResult> results = fullTextService.search("benz");
then(results.size()).isEqualTo(2);
}
}
其他依赖的类:
Brand.java
@Data
@Entity
@Table(name = "brands")
public class Brand {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
}
Car.java
@Data
@Entity
@Table(name = "cars")
public class Car {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private String introduce;
}
BrandRepository.java
@Repository
public interface BrandRepository extends JpaRepository<Brand, Long> {
}
CarRepository.java
@Repository
public interface CarRepository extends JpaRepository<Car, Long> {
}
FullTextSearchResult.java 将全文检索搜索结果封装为该类。
@Builder
@Data
public class FullTextSearchResult {
private String schema;
private String table;
private String columns;
private String keys;
private BigDecimal score;
}
FullTextService.java
@Service
public class FullTextService {
public static final int SEARCH_RESULT_LIMIT = 0;
public static final int SEARCH_RESULT_OFFSET = 0;
public static final int SCHEMA_INDEX = 1;
public static final int TABLE_INDEX = 2;
public static final int COLUMNS_INDEX = 3;
public static final int KEYS_INDEX = 4;
public static final int SCORE_INDEX = 5;
@Autowired
private DataSource dataSource;
public List<FullTextSearchResult> search(String keyword) throws SQLException {
List<FullTextSearchResult> results = new ArrayList<>();
ResultSet resultSet = FullText.searchData(
dataSource.getConnection(),
keyword,
SEARCH_RESULT_LIMIT,
SEARCH_RESULT_OFFSET);
while (resultSet.next()) {
String schemaName = resultSet.getString(SCHEMA_INDEX);
String tableName = resultSet.getString(TABLE_INDEX);
Object[] columns = (Object[]) resultSet.getArray(COLUMNS_INDEX).getArray();
String column = (String) columns[0];
Object[] keys = (Object[]) resultSet.getArray(KEYS_INDEX).getArray();
String key = (String) keys[0];
BigDecimal score = resultSet.getBigDecimal(SCORE_INDEX);
results.add(
FullTextSearchResult.builder()
.schema(schemaName)
.table(tableName)
.columns(column)
.keys(key)
.score(score)
.build());
}
return results;
}
}
在提取全文检索的结果时 H2 提供的类并不能方便的使用。因此可以添加 FullText 的代理类,将常用的方法进行封装。
3 使用 Apache Lucene 的全文检索
由于 H2 是使用 Java 编写的,因此只需要引入 Apache Lucene 的类,即可进行数据库的扩展。与Native FullText Search 不同,使用 Apache Lucene 会讲索引储存在 Lucene 之中,并可以根据 Lucene 提供的特性进行分词和索引的功能扩展。
另外当前最新版本的 H2 数据库支持 Apache Lucene 5.5 以及 8.0.x 版本。
3.1 命令行中使用 Apache Lucene 创建索引
初始化使用:org.h2.fulltext.FullTextLucene.init
create alias if not exists FTL_INIT for "org.h2.fulltext.FullTextLucene.init";
CALL FTL_INIT();
其他操作均以 FTL_ 开头的函数来进行操作,例如: FTL_SEARCH_DATA()
3.2 H2 提供的了对 Apahce Lucene 操作的封装类
可以使用 fulltext.FullTextLucene.searchData
类进行数据的检索。
更多 API 可参考H2 Database Java doc