目录
- 一、引言
- 二、集成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_ | + |