升级H2数据库2.x版本遇见的问题

一、引言

之前在跑代码单元测试时,一直用的内存数据库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_ +

猜你喜欢

转载自blog.csdn.net/luo15242208310/article/details/130024341