目录
- 一、引言
- 二、集成H2基础配置
- 三、升级H2版本2.x遇到的问题
- 报错1
- 报错2
- 三、H2关键字
一、引言
之前在跑代码单元测试时,一直用的内存数据库H2代替实际的Mysql数据库,如此便省去了对Dao的大量mock代码,类似于在跑Junit单元测试时直接跑了集成测试。但是H2的语法和Mysql还是有细微差别的,可使用Mysql兼容模式,实际测试时除了个别Mysql的函数如FIND_IN_SET等不支持,其他基本的SQL语句都还是支持的。
注:
H2兼容的数据库模式包括:Mysql、MariaDB、PostgreSQL、Oracle、MS SQL、HSQLDB、DB2、Derby,
具体说明参见:http://h2database.com/html/features.html#compatibility
二、集成H2基础配置
Spring JUnit集成H2代替Mysql的相关配置如下:
maven依赖:
<properties>
<!-- 后续会介绍升级到2.0.206版本 -->
<h2.version>1.4.200</h2.version>
</properties>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
<scope>test</scope>
</dependency>
application-test.yaml配置:
spring:
# Sql初始化配置
sql:
init:
# 导入h2 table定义
schema-locations: classpath:h2/demo-schema.sql
# 导入h2 数据定义
data-locations: classpath:h2/demo-data.sql
# 数据库配置
datasource:
type: com.zaxxer.hikari.HikariDataSource
# ============================================================
# ============= 使用H2内存数据库 ================================
# ============================================================
driver-class-name: org.h2.Driver
# 使用h2内存数据(以mysql兼容模式运行)
url: jdbc:h2:mem:rbac;MODE=MySQL;DATABASE_TO_LOWER=TRUE
username: root
password: 123456
在跑单元测试时,可通过@ActiveProfiles(“test”)激活application-test.yaml配置:
@ActiveProfiles("test")
@SpringBootTest
public class MyBaseTest {
@BeforeEach
void setUp() {
...
}
}
---
@ActiveProfiles("test")
@SpringBootTest
public class MyAppTest extends MyBaseTest {
@Test
void testMyFunc() {
...
}
}
配置spring.sql.init.schema-locations | data-locations对应的即为H2内存数据库的schema定义(table定义)和数据,
以上配置中的h2/demo-schema.sql、h2/demo-data.sql可通过Idea插件Mysql-to-H2将原始的Mysql语句转换为H2 Sql语句,以避免Sql语法不兼容.

三、升级H2版本2.x遇到的问题
测试用例在H2版本1.4.200时运行都没有问题,后续将H2版本升级为2.x版本(2.0.206):
<properties>
<h2.version>2.0.206</h2.version>
</properties>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
<scope>test</scope>
</dependency>
升级后同样的代码运行后出现以下异常:
报错1
Cause: org.h2.jdbc.JdbcSQLSyntaxErrorException:
Syntax error in SQL statement "SELECT\000a \000a DD.ID AS ID,\000a DD.DICT_DETAIL_CODE AS VALUE[*],\000a DD.DICT_DETAIL_NAME AS NAME,\000a DD.PARENT_CODE AS PARENT_ID,\000a DD.DICT_TYPE_CODE AS TYPE\000a \000a FROM SYSTEM_DICT_DETAIL DD"; \
expected "identifier"; SQL statement:
select
dd.id as id,
dd.dict_detail_code as value,
dd.dict_detail_name as name,
dd.parent_code as parent_id,
dd.dict_type_code as type
from system_dict_detail dd [42001-206]
该测试用例对应Mybatis Mapper XML中的SQL语句为:
select
dd.id as id,
dd.dict_detail_code as value,
dd.dict_detail_name as name,
dd.parent_code as parent_id,
dd.dict_type_code as type
from system_dict_detail dd
注意上述异常中的关键词expected "identifier";,查询了 Github/h2/issues/3363后发现:
- H2的2.x版本要求对保留的关键字使用
双引号包围 - 又或者可通过jdbc url上的
;NON_KEYWORDS=KEYWORD1,KEYWORD2格式对指定保留关键字进行排除

上述SQL语句出问题的地方就是使用了保留关键字value,所以才出现了expected "identifier";异常,
select
...
-- 注意as后面的value,此value与H2关键字冲突
dd.dict_detail_code as value,
...
可通过双引号包围关键字的方式避免报错,修改后Sql语句如下:
select
...
-- 注意as后面的h2关键字value,可使用英文双引号包围"value"
dd.dict_detail_code as "value",
...
注:
上述sql语句本身使用关键字(如value)作为列名就有问题,实际开发时不推荐此种方式。
报错2
异常和 报错1 类似,也包含expected "identifier";提示,具体对应的Mybatis Mapper XML中的SQL语句为:
select
u.id,
u.name,
...
from system_user u
...
比较坑的是Sql语句中的表名system_user在H2中也是保留关键字,
起初直接使用双引号包围表名"system_user",修改如下:
select
...
from "system_user" u
...
此种方式在H2中运行没问题,但是在Mysql中 运行时报语法错误。表名使用双引号包围
最终使用了在jdbc url上添加;NON_KEYWORDS=SYSTEM_USER对关键字system_user进行排除的方式,即解决了H2报错的问题,也保证了原语句在Mysql中的执行,具体jdbc url配置如下:
spring:
datasource:
url: "jdbc:h2:mem:rbac;MODE=MySQL;DATABASE_TO_LOWER=TRUE;NON_KEYWORDS=SYSTEM_USER"
三、H2关键字
H2保留的Keyword具体说明参见:https://h2database.com/html/advanced.html#keywords
| Keyword | H2 | SQL Standard | |||||
|---|---|---|---|---|---|---|---|
| 2016 | 2011 | 2008 | 2003 | 1999 | 92 | ||
| ALL | + | + | + | + | + | + | + |
| AND | + | + | + | + | + | + | + |
| ANY | + | + | + | + | + | + | + |
| ARRAY | + | + | + | + | + | + | |
| AS | + | + | + | + | + | + | + |
| ASYMMETRIC | + | + | + | + | + | NR | |
| AUTHORIZATION | + | + | + | + | + | + | + |
| BETWEEN | + | + | + | + | + | NR | + |
| BOTH | CS | + | + | + | + | + | + |
| CASE | + | + | + | + | + | + | + |
| CAST | + | + | + | + | + | + | + |
| CHECK | + | + | + | + | + | + | + |
| CONSTRAINT | + | + | + | + | + | + | + |
| CROSS | + | + | + | + | + | + | + |
| CURRENT_CATALOG | + | + | + | + | |||
| CURRENT_DATE | + | + | + | + | + | + | + |
| CURRENT_PATH | + | + | + | + | + | + | |
| CURRENT_ROLE | + | + | + | + | + | + | |
| CURRENT_SCHEMA | + | + | + | + | |||
| CURRENT_TIME | + | + | + | + | + | + | + |
| CURRENT_TIMESTAMP | + | + | + | + | + | + | + |
| CURRENT_USER | + | + | + | + | + | + | + |
| DAY | + | + | + | + | + | + | + |
| DEFAULT | + | + | + | + | + | + | + |
| DISTINCT | + | + | + | + | + | + | + |
| ELSE | + | + | + | + | + | + | + |
| END | + | + | + | + | + | + | + |
| EXCEPT | + | + | + | + | + | + | + |
| EXISTS | + | + | + | + | + | NR | + |
| FALSE | + | + | + | + | + | + | + |
| FETCH | + | + | + | + | + | + | + |
| FOR | + | + | + | + | + | + | + |
| FOREIGN | + | + | + | + | + | + | + |
| FROM | + | + | + | + | + | + | + |
| FULL | + | + | + | + | + | + | + |
| GROUP | + | + | + | + | + | + | + |
| GROUPS | CS | + | + | ||||
| HAVING | + | + | + | + | + | + | + |
| HOUR | + | + | + | + | + | + | + |
| IF | + | ||||||
| ILIKE | CS | ||||||
| IN | + | + | + | + | + | + | + |
| INNER | + | + | + | + | + | + | + |
| INTERSECT | + | + | + | + | + | + | + |
| INTERVAL | + | + | + | + | + | + | + |
| IS | + | + | + | + | + | + | + |
| JOIN | + | + | + | + | + | + | + |
| KEY | + | NR | NR | NR | NR | + | + |
| LEADING | CS | + | + | + | + | + | + |
| LEFT | + | + | + | + | + | + | + |
| LIKE | + | + | + | + | + | + | + |
| LIMIT | MS | + | |||||
| LOCALTIME | + | + | + | + | + | + | |
| LOCALTIMESTAMP | + | + | + | + | + | + | |
| MINUS | MS | ||||||
| MINUTE | + | + | + | + | + | + | + |
| MONTH | + | + | + | + | + | + | + |
| NATURAL | + | + | + | + | + | + | + |
| NOT | + | + | + | + | + | + | + |
| NULL | + | + | + | + | + | + | + |
| OFFSET | + | + | + | + | |||
| ON | + | + | + | + | + | + | + |
| OR | + | + | + | + | + | + | + |
| ORDER | + | + | + | + | + | + | + |
| OVER | CS | + | + | + | + | ||
| PARTITION | CS | + | + | + | + | ||
| PRIMARY | + | + | + | + | + | + | + |
| QUALIFY | + | ||||||
| RANGE | CS | + | + | + | + | ||
| REGEXP | CS | ||||||
| RIGHT | + | + | + | + | + | + | + |
| ROW | + | + | + | + | + | + | |
| ROWNUM | + | ||||||
| ROWS | CS | + | + | + | + | + | + |
| SECOND | + | + | + | + | + | + | + |
| SELECT | + | + | + | + | + | + | + |
| SESSION_USER | + | + | + | + | + | + | |
| SET | + | + | + | + | + | + | + |
| SOME | + | + | + | + | + | + | + |
| SYMMETRIC | + | + | + | + | + | NR | |
| SYSTEM_USER | + | + | + | + | + | + | + |
| TABLE | + | + | + | + | + | + | + |
| TO | + | + | + | + | + | + | + |
| TOP | MS CS | ||||||
| TRAILING | CS | + | + | + | + | + | + |
| TRUE | + | + | + | + | + | + | + |
| UESCAPE | + | + | + | + | + | ||
| UNION | + | + | + | + | + | + | + |
| UNIQUE | + | + | + | + | + | + | + |
| UNKNOWN | + | + | + | + | + | + | + |
| USER | + | + | + | + | + | + | + |
| USING | + | + | + | + | + | + | + |
| VALUE | + | + | + | + | + | + | + |
| VALUES | + | + | + | + | + | + | + |
| WHEN | + | + | + | + | + | + | + |
| WHERE | + | + | + | + | + | + | + |
| WINDOW | + | + | + | + | + | ||
| WITH | + | + | + | + | + | + | + |
| YEAR | + | + | + | + | + | + | + |
| _ROWID_ | + | ||||||



















