H2
使用h2做测试很常用,也很简单。不过使用h2写demo也很方便,这种情况下,就需要了解更多h2相关的内容。
依赖
1
2
3
4
5
6
7
<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${version}</version>
<scope>test</scope>
</dependency>
默认是test,说明h2默认是测试时用的,而不是用在正常环境中。如果用非test启动服务的话,会报错找不到org.h2.Driver。
h2的访问使用JDBC,具体的连接池可以用hikari等。这就是统一接口的好处啊!
URL
h2的url大全:http://www.h2database.com/html/features.html#database_url
存储形式
- 内存:不持久化,关闭JVM即销毁;
- 文件:持久化,关闭JVM后文件还在;
从url就能指定使用哪种方式:
jdbc:h2:mem:<databaseName>
:内存存储;jdbc:h2:[file:][<path>]<databaseName>
:文件存储;
模式
https://www.h2database.com/html/features.html#connection_modes
- embedded:h2和应用程序绑定在一起;
- server:一个独立的h2数据库服务,使用TCP/IP从远端访问数据库;
Embedded mode
h2是否是嵌入模式和是否使用内存存储是两码事。嵌入只是说h2和程序猿自己开发的应用链接在一起,成为一个可执行文件。至于数据库的数据文件存在哪儿,内存还是文件,都行。
参考:https://stackoverflow.com/a/48833387/7676237
所以两两组合,可以组合出四种方式的h2:
- embedded in-memory
- embedded persistent
- client/server in-memory
- client/server persistent
embeded + in memory
https://www.h2database.com/html/features.html#in_memory_databases
in-memory如果不指定db名,比如jdbc:h2:mem:
,只能使用一个线程访问该数据库,如果有另一个线程,访问的是新的数据库。类似于Java的匿名类。如果想多个线程访问同一个数据库,需要给数据库url加上数据库名称。比如jdbc:h2:mem:pokemon
。有了名字大家就都可以访问它了。
使用in memory很方便,但是不能跨进程连接。毕竟两个进程的jdbc:h2:mem:db1
指的不是同一款内存:
Sometimes multiple connections to the same in-memory database are required. In this case, the database URL must include a name. Example:
jdbc:h2:mem:db1
. Accessing the same database using this URL only works within the same virtual machine and class loader environment.
如果想远程访问另一个进程的in memory h2,就需要使用tcp连接,提供那个进程的ip和port:
To access an in-memory database from another process or from another computer, you need to start a TCP server in the same process as the in-memory database was created. The other processes then need to access the database over TCP/IP or TLS, using a database URL such as: jdbc:h2:tcp://localhost/mem:db1.
默认当没有连接时,数据库就关了。 如果是in memory数据库,内容就丢失了。如果相当等jvm关闭时才关闭数据库,拼接;DB_CLOSE_DELAY=-1
,比如jdbc:h2:mem:pokemon;DB_CLOSE_DELAY=-1
。
embeded + 文件
两个以文件作为存储的h2服务也不能同时访问同一份文件,会报错:Database may be already in use。
所以一个h2服务只要是嵌入模式,其实无论使用哪种存储方式,都不能让另一个进程远程访问它的数据。必须使用server模式,再开启一个tcp服务,通过tcp给别人传数据。
server mode
tcp server
server mode就是一个进程启动h2(一般是以embeded的形式启动),并提供tcp访问,别的进程无论是本机的还是外网的,都可以通过tcp获取数据。
如何开启一个server模式的h2:http://www.h2database.com/html/tutorial.html#using_server
一个在spring boot中开启h2 tcp server的示例:https://stackoverflow.com/a/55847035/7676237
web server - h2 console
h2不止有tcp server,供远程访问,还有一个内置的web server,可以使用http访问。也就是说可以拿浏览器直接访问,所以它又被称为h2 console。
它其实就是一个servlet:
1
2
3
4
5
6
7
8
package org.h2.server.web;
/**
* This servlet lets the H2 Console be used in a standard servlet container
* such as Tomcat or Jetty.
*/
public class WebServlet extends HttpServlet {
}
这个servlet被放在tomcat容器里,处理http请求。
兼容mysql
https://www.h2database.com/html/features.html#compatibility
mode设为mysql。同时,因为mysql忽略大小写,所以可以把case insensitive也加上:
1
jdbc:h2:~/test;MODE=MySQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE
另外mysql的文本比较也忽略大小写(???),所以h2需要使用SET IGNORECASE TRUE
让自己也忽略大小写,从而让=, LIKE, REGEXP
达到和mysql一样的效果。
非测试场景的h2使用
h2一般被拿来做test用,但是有时候写一些简单的示例小程序,也可以把他作为数据库来用。此时就不再把scope声明为test,可以使用默认的compile,或者runtime也行,毕竟编码的时候用不到它,运行的时候有就行了:
1
2
3
4
5
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
springboot使用h2非常简单,用户名和密码可设可不设:
1
2
3
4
5
6
7
8
# embedded in-memory; close only when jvm exit; use MySQL mode
spring.datasource.url=jdbc:h2:mem:pokemon;DB_CLOSE_DELAY=-1;MODE=MySQL
# embedded local file
spring.datasource.driverClassName=org.h2.Driver
#spring.datasource.username=sa
#spring.datasource.password=password
# sql schema
spring.datasource.schema=classpath:sql/USER-schema.sql,classpath:sql/USER-data.sql
但不方便的地方在于,这是一个embeded模式启动的数据库,我们在开发的时候,没法让别的进程连上去查看其中的内容。
牛逼的是,spring boot的auto configuration功能搞了个H2ConsoleAutoConfiguration
,可以给springboot的h2开启web server(即h2 console),让我们可以方便地使用浏览器查看h2数据库的内容:
1
2
3
4
5
6
7
8
9
10
h2:
# h2 web consloe 是一个数据库GUI管理应用,程序运行时,会自动启动h2 web consloe
console:
# 开启 h2 web console ,默认开启
enabled: true
# 可以通过 url/h2-console 访问 h2 web
path: /h2-console
settings:
# 配置后 h2 web console 就可以在远程访问,否则只能在本机访问
web-allow-others: true
配置非常简单,enable=true,然后就可以通过http://host:port/h2-console
访问springboot启动的内嵌数据库了。
原理
看一下这个autoconfig的内容就明白了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* {@link EnableAutoConfiguration Auto-configuration} for H2's web console.
*
* @author Andy Wilkinson
* @author Marten Deinum
* @author Stephane Nicoll
* @since 1.3.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(WebServlet.class)
@ConditionalOnProperty(prefix = "spring.h2.console", name = "enabled", havingValue = "true", matchIfMissing = false)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(H2ConsoleProperties.class)
public class H2ConsoleAutoConfiguration {
private static final Log logger = LogFactory.getLog(H2ConsoleAutoConfiguration.class);
@Bean
public ServletRegistrationBean<WebServlet> h2Console(H2ConsoleProperties properties,
ObjectProvider<DataSource> dataSource) {
String path = properties.getPath();
String urlMapping = path + (path.endsWith("/") ? "*" : "/*");
ServletRegistrationBean<WebServlet> registration = new ServletRegistrationBean<>(new WebServlet(), urlMapping);
H2ConsoleProperties.Settings settings = properties.getSettings();
if (settings.isTrace()) {
registration.addInitParameter("trace", "");
}
if (settings.isWebAllowOthers()) {
registration.addInitParameter("webAllowOthers", "");
}
dataSource.ifAvailable((available) -> {
try (Connection connection = available.getConnection()) {
logger.info("H2 console available at '" + path + "'. Database available at '"
+ connection.getMetaData().getURL() + "'");
}
catch (SQLException ex) {
// Continue
}
});
return registration;
}
}
首先,条件注解声明,spring.h2.console.enabled
为true才生效。
然后构造了一个ServletRegistrationBean
:
1
ServletRegistrationBean<WebServlet> registration = new ServletRegistrationBean<>(new WebServlet(), urlMapping);
把设置的path(默认就是/h2-console
)和h2 console对应的servlet绑定。
ServletRegistrationBean,看它的名字就知道,是用来注册servlet的bean。既然注册了这个servlet,那springboot就有这个servlet的功能了。
最后http请求访问这个path,就会由这个servlet来服务了。
spring security
h2有自己的登录界面。如果springboot集成了spring security,需要先登录验证spring security,再登录h2 console。两套登录,没必要。另外登陆完h2之后,所有静态资源无法加载。(暂时不清楚为啥)
所以记得把spring security对h2-console
这个path的校验关掉:https://stackoverflow.com/a/59560136/7676237
登录h2 console的信息:
- url:
jdbc:h2:mem:pokemon
,依然是mem格式的连接。因为实际上是让和h2同一个JVM里的servlet线程去连接h2,他们的mem指的是同一个JVM的mem; - username/password: 就是springboot配置里的
参阅:
- https://www.baeldung.com/spring-boot-h2-database
- https://stackoverflow.com/a/28950817/7676237
- https://stackoverflow.com/a/43862903/7676237