H2 全文检索功能

在前面的文章中,我们介绍了 H2 的一些特性以及 为什么H2 适合应用在测试环境中。H2 不但可以作为嵌入式数据库、内存数据库使用。在适当的场景下可以选择使用 H2 替换掉 SQLite,还可利用 H2 内存数据库的特点,将它还提供了全文检索的功能。

H2 内置了两个全文检索(FullText Search)的实现:

  1. Native FullText Search。使用 H2 中内置的全文检索,将索引存储在数据库指定的表中。
  2. 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)
);

创建后如下图:

00.png

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 中存储的是建立索引的规则。

01.png

指定建立索引的表和列。

CALL FT_CREATE_INDEX('TEST_SCHEMA', 'CARS', NULL);
CALL FT_CREATE_INDEX('TEST_SCHEMA', 'BRANDS', NULL);

FT_CREATE_INDEX 函数的

​ 第一个参数指定的建立索引的 SCHEMA Name;

​ 第二个参数是建立索引的 TABLE Name;

​ 第三个参数是建立索引的列表,当为 NULL 时表示为所有列建立索引。

02.png

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');

插入数据后,结构如下:

03.png

搜索之前我们先确定要得到的结果,通过上图,我们知道,包含关键字 benz 的关键字记录一共 有两条。

  1. cars 表中的 id 为 1 的记录,出现在 name、introduce 两列中,
  2. 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

查询结果如下:

04.png

另外搜索的结果是忽略大小写的,一次搜索 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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,392评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,258评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,417评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,992评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,930评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,199评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,652评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,327评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,463评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,382评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,432评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,118评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,704评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,787评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,999评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,476评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,057评论 2 341