特征列表
与其他数据库引擎的对比
H2案例
连接模式
JDBC数据库连接URL说明
连接本地内嵌数据库
内存数据库
数据库文件加密
数据库文件锁定
仅打开存在的数据库
关闭数据库
忽略未知参数设置
打开连接时设置参数
更新记入索引
指定文件读写模式
多连接
数据库文件说明
日志记录与恢复
兼容性
自动重连
自动切换到混合模式
使用跟踪选项
使用第三方日志包
只读数据库
Zip或Jar文件只读数据库
智能磁盘监控
包含计算列方法列的索引
复合索引
使用密码
用户自定义方法和存储过程
触发器
压缩数据库
缓存设置
· 超快的数据库引擎
· 开源
· 纯JAVA编写
· 支持标准SQL和JDBC
· 支持内嵌模式、服务器模式和集群
· 高强度的安全保障
· 支持PostgreSQL的ODBC驱动
· 多种并发机制
其他特征
· 支持磁盘和内存数据库,支持只读数据库,支持临时表
· 支持事务(读提交和序列化事务隔离),支持2阶段提交
· 支持多连接,支持表级锁
· 使用基于成本的优化机制,对于复杂查询使用零遗传算法进行管理
· 支持可滑动可更新的结果集,支持大型结果集、支持结果集排序,支持方法返回结果集
· 支持数据库加密(使用AES或XTEA进行加密),支持SHA-256密码加密,提供加密函数,支持SSL
SQL支持
· 支持多个schemas和信息schema
· 支持数据完整性约束,外键约束,主键约束
· 支持内连接和外连接,子查询,只读视图和内联视图
· 支持触发器,JAVA方法,存储过程
· 大量内置方法,包括XML和无损数据压法方法
· 广泛的数据类型,包括大数据类型(BLOB/CLOB)和数组
· 序列和自增字段,计算字段(可用于索引)
· ORDER BY, GROUP BY, HAVING, UNION, LIMIT, TOP
· 排序规则的支持,以及用户和角色的支持
· 兼容 IBM DB2, Apache Derby, HSQLDB, MS SQL Server, MySQL, Oracle, 和PostgreSQL等多种数据库.
安全属性
· 已经包含了一个SQL注入问题的解决方案
· 用户密码使用SHA-256进行加密
· 服务器连接模式,用户的密码在网络上传输时已经不是明文(即使使用非安全连接,但不包括将密码写在连接URL中时,即当把密码写在URL中时,传输过程中密码是明文)
· 所有的数据库文件(包括备份数据导出的脚本文件)都可以使用 AES-256和XTEA进行加密
· 远程的JDBC驱动支持基于SSL/TLS的TCP/IP连接
· 内置的WEB服务支持基于SSL/TLS的连接
· 密码能被发送到数据库通过字符数组而不是字符串
其他特征和工具
· 小尺寸(不到1MB),低内存需求
· 多种索引类型(b-tree, tree, hash)
· 支持复合索引
· 支持CSV (逗号分隔)文件
· 支持连接表,内置了虚拟范围表
· 支持执行计划EXPLAIN PLAN,多种跟踪选项
· 能延迟关闭数据库或禁用数据库,以提高性能
· 基于WEB的控制台应用程序
· 数据库可以导出为SQL脚本文件
· 包含一个恢复工具,可以转储数据库
· 支持变量(如计算汇总数)
· 自动完成语法分析和优化
· 只使用很少的数据库文件
· 使用校验和保障每条记录和日志的完整性
与其他数据库引擎的对比
特征 |
H2 |
||||
纯JAVA |
Yes |
Yes |
Yes |
No |
No |
内嵌入Java应用 |
Yes |
Yes |
Yes |
No |
No |
性能(内嵌模式)
|
Fast |
Slow |
Fast |
N/A |
N/A |
内存数据库模式 |
Yes |
Yes |
Yes |
No |
No |
事务隔离 |
Yes |
Yes |
No |
Yes |
Yes |
成本优化器 |
Yes |
Yes |
No |
Yes |
Yes |
解释计划(Explain Plan) |
Yes |
No |
Yes |
Yes |
Yes |
集群 |
Yes |
No |
No |
Yes |
Yes |
数据库加密 |
Yes |
Yes |
No |
No |
No |
连接表 |
Yes |
No |
Partially *1 |
Partially *2 |
No |
ODBC驱动 |
Yes |
No |
No |
Yes |
Yes |
全文检索 |
Yes |
No |
No |
Yes |
Yes |
用户自定义数据类型 |
Yes |
No |
No |
Yes |
Yes |
每数据库文件数 |
Few |
Many |
Few |
Many |
Many |
表级锁 |
Yes |
Yes |
No |
Yes |
Yes |
行级锁 |
Yes *9 |
Yes |
No |
Yes |
Yes |
多种并发机制 |
Yes |
No |
No |
No |
Yes |
基于角色的安全机制 |
Yes |
Yes *3 |
Yes |
Yes |
Yes |
可更新结果集 |
Yes |
Yes *7 |
No |
Yes |
Yes |
序列(Sequences) |
Yes |
No |
Yes |
No |
Yes |
限制和偏移(Limit and Offset) |
Yes |
No |
Yes |
Yes |
Yes |
临时表 |
Yes |
Yes *4 |
Yes |
Yes |
Yes |
信息 Schema |
Yes |
No *8 |
No *8 |
Yes |
Yes |
计算列 |
Yes |
No |
No |
No |
Yes *6 |
不区分大小写列 |
Yes |
No |
Yes |
Yes |
Yes *6 |
自定义聚合方法 |
Yes |
No |
No |
Yes |
Yes |
尺寸 (jar/dll 大小) |
~1 MB *5 |
~2 MB |
~700 KB |
~4 MB |
~6 MB |
*1 HSQLDB 支持文本表
*2 MySQL 支持标记为'federated tables'的链接表
*3 Derby 有一个选项可以支持基于角色的安全和密码认证
*4 Derby 仅支持全局的临时表
*5 H2的jar文件包含了调试信息,其他数据的jar文件中不不包括
*6 PostgreSQL 支持方法索引
*7 Derby 仅支持在查询结果集未被排序的情况下的可更新
*8 Derby 和 HSQLDB 都不支持标准的信息schema表
*9 H2 在多并发的情况下支持行级锁
Derby 和 HSQLDB
在进程非正常退出(如断电)情况下,H2在不需要任何用户干预的情况能安全的恢复。对于Derby 和HSQLDB ,却是要一些人工干预的('Another instance of Derby may have already booted the database' / 'The database is already in use by another process')。
DaffodilDb 和 One$Db
这两个数据库看上去好像已经停止了开发,最后的发布时间是2006年2月。
McKoi
这个数据库看上去好像已经停止了开发,最后的发布时间是2004年8月。
H2案例
H2现在已经有大量的应用在使用,具体可以参见下面的列表。
连接模式
支持多种连接模式:
· 内嵌模式(通过JDBC进行本地连接)
· 服务器模式(通过JDBC或ODBC或TCP/IP进行远程连接)
· 混合模式(同时支持本地和远程连接)
内嵌模式
内嵌模式下,应用和数据库同在一个JVM中,通过JDBC进行连接。内嵌模式是最快和最容易的连接模式。它的缺点是任何时候数据库只能在一台虚拟机(和加载类)。像所有的模式,持久数据库和内存数据库都被支持。没有打开连接数和打开数据库数量方面的限制。
服务器模式
使用服务器模式(有时被称为远程模式或是C/S模式)时,应用可以通过JDBC或ODBC打开一个远程的数据库。服务器可以启动在同一个虚拟机或是不同的虚拟机上,也可以启动在不同的计算机上。大量的应用可以同时连接到同一个数据库上。服务器模式相比内嵌模式性能慢一些,因为所有的数据都需要通过TCP/IP进行传输。像所有的模式一样,支持持久数据库和内存数据库。没有打开连接数和打开数据库数量方面的限制。
混合模式
混合模式是内嵌模式和服务器模式的组合。第一个应用通过内嵌模式与数据库建立连接,同时也作为一个服务器启动,于是另外的应用(运行在不同的进程或是虚拟机上)可以同时访问同样的数据。第一个应用的本地连接与嵌入式模式的连接性能一样的快,而远程连接有点慢。
服务器能通过应用来进行启动和停止(使用服务API),或是是自动的方式(自动混合模式)。当使用自动混合模式,所有客户端都需要使用同样的URL进行连接(不管它是一个本地还是一个远程连接。
·
数据库连接 URL说明数据库支持多种连接模式和连接设置,不同的连接模式和连接设置是通过不同的URL来区分的,URL中的设置是不区分大小写。
Topic |
URL Format and Examples |
jdbc:h2:[file:][<path>]<databaseName> |
|
jdbc:h2:mem: |
|
jdbc:h2:mem:<databaseName> |
|
|
jdbc:h2:tcp://<server>[:<port>]/[<path>]<databaseName> |
|
jdbc:h2:ssl://<server>[:<port>]/<databaseName> |
jdbc:h2:<url>;CIPHER=[AES|XTEA] |
|
jdbc:h2:<url>;FILE_LOCK={NO|FILE|SOCKET} |
|
jdbc:h2:<url>;IFEXISTS=TRUE |
|
jdbc:h2:<url>;DB_CLOSE_ON_EXIT=FALSE |
|
jdbc:h2:<url>[;USER=<username>][;PASSWORD=<value>] |
|
jdbc:h2:<url>;LOG=2 |
|
jdbc:h2:<url>;TRACE_LEVEL_FILE=<level 0..3> |
|
jdbc:h2:<url>;IGNORE_UNKNOWN_SETTINGS=TRUE |
|
jdbc:h2:<url>;ACCESS_MODE_LOG=rws;ACCESS_MODE_DATA=rws |
|
jdbc:h2:zip:<zipFileName>!/<databaseName> |
|
jdbc:h2:<url>;MODE=<databaseType> |
|
jdbc:h2:<url>;AUTO_RECONNECT=TRUE |
|
jdbc:h2:<url>;AUTO_SERVER=TRUE |
|
jdbc:h2:<url>;<setting>=<value>[;<setting>=<value>...] |
连接本地内嵌数据库
通过数据库URL(jdbc:h2:[file:][<path>]<databaseName>)连接一个本地数据库。前缀file:是可选的,如果没有或是使用相对路径,当前的工作目录就是起始目录。路径和数据库名是否大小写敏感由操作系统决定,但是推荐使用小写字母。数据库名至少包含三个字符(限制来源于File.createTempFile)。如果使用相对路径,可以使用 ~/, 例如: jdbc:h2:~/test。
内存数据库
在某些使用情况下(如快速原型,测试,高性能作业,只读数据库),可能不需要持久化数据,或者是不需要将改变的数据库持久化。H2支持内存数据库模式,内存数据库模式下,数据并不被持久化。
在一些情况下,只有一个连接能访问内存数据库,这意味着打开的数据库是私有的,这种情况下,可以通过特定的URL(jdbc:h2:mem:)来打开数据库,用这种特殊的URL打开的数据库,如果在同一个虚拟机里同时打开两个连接意味着打开了两个不同的(私有)数据库。
有时,需要多个连接访问同一个内存数据库。在这种情况下,数据库的URL必须包含一个名字,例如:jdbc:h2:mem:db1,这种方式访问同一个数据库只能在同一个虚拟机和相同的类加载环境。
内存数据库也可以通过TCP/IP 或者 SSL/TLS进行远程访问(或者相同机器上的其他多个进程进行访问)如这个数据库URL: jdbc:h2:tcp://localhost/mem:db1.
缺省情况下,关闭数据库的最后连接,数据库就被关闭掉了。对于内存数据库,这意味着数据全部丢失,为了保持数据库打开,需要增加参数;DB_CLOSE_DELAY=-1到数据库URL中,这样只要虚拟机未退出内存数据库的数据就一直保留着。如使用下面URL:jdbc:h2:mem:test;DB_CLOSE_DELAY=-1.
数据库文件加密
数据库文件可以被加密,目前支持两种加密算法: AES 和 XTEA。为了能进行文件加密,你需要在连接数据库时指定特定的加密算法(设置 'cipher'的值)和文件密码(不同于用户密码之外的一个密码)。
创建一个文件加密的数据库
缺省情况下,如果数据库不存在,数据库将被自动创建。创建一个加密的数据库,连接到一个存在的数据库。
连接到一个加密数据库
加密算法需要被设置在URL中,文件密码在密码域里是一个特殊的部分,在用户密码的前面,一个空格将文件密码和用户密码进行分割,文件密码不能包含空格,文件密码和用户密码区分大小写,下面是连接到加密数据库的例子:
Class.forName("org.h2.Driver");
String url = "jdbc:h2:~/test;CIPHER=AES";
String user = "sa";
String pwds = "filepwd userpwd";
conn = DriverManager.
getConnection(url, user, pwds);
加密和解密数据库
加密一个已经存在的数据库,可以通过ChangeFileEncryption工具,这个工具也可以解密一个已经被加密的数据库,还可以更换加密文件的密钥。该工具可以通过H2的控制台来使用,也可以使用命令行工具来使用。下面的命令将加密在用户目录下的数据库test,使用密码filepwd和加密算法AES:
java -cp h2*.jar org.h2.tools.ChangeFileEncryption -dir ~ -db test -cipher AES -encrypt filepwd
当数据库打开时,一个锁文件被创建,告诉其他进程,这个数据库已经被占用,如果数据库关闭,或是数据库进程终止,锁文件将被自动删除。
下面是几个锁定文件的方法:
· 默认的方式是,通过看门狗线程检查文件锁来保护数据库文件,看门狗线程每秒读锁定的文件,检查数据库文件是否可用。
· 第二种方式是服务器端口方式,打开一个服务器端口,服务器端口方法不要求每秒都去读锁文件,服务器端口只能使用在数据库文件只能在一台计算机(总是同样一台计算机)上访问时使用。
· 也可以不使用文件锁打开数据库,在这种情况下,需要应用来保护数据库文件。
打开数据可以使用不同的文件锁模式,可以通过设置参数FILE_LOCK来指定,下面的代码是使用服务器端口锁方式打开数据库:
String url = "jdbc:h2:~/test;FILE_LOCK=SOCKET";
下面的代码强制数据库不去创建锁文件。但是,这种模式是不安全的,另一个进程可以打开同样的数据库,可能会引起数据库的混乱:
String url = "jdbc:h2:~/test;FILE_LOCK=NO";
关于文件锁保护的更多信息,请参考高级/文件锁保护。
仅打开存在的数据库
默认情况下,当一个应用调用DriverManager.getConnection(url, ...)后获得一个数据库连接,如果连接的URL指定的数据库不存在,一个新的空的数据库将被自动创建,在一些情况下,不要求创建新的数据库,只允许打开已经存在的数据库,可以通过在数据库URL中增加;IFEXISTS=TRUE来实现,在数据库URL中增加了;IFEXISTS=TRUE后,如果连接一个不存在的数据库,将抛出一个异常,只有当数据库已经存在,连接才能成功,完整的URL例子如下:
String url = "jdbc:h2:/data/sample;IFEXISTS=TRUE";
通常,当数据库的最后一个连接关闭时,数据库也被关闭了。在一些情况下,它是不能满足应用需求的,如有些应用无法保持最后一个连接一直打开。自动关闭数据库可以被延迟或屏蔽,可以通过SQL语句 SET DB_CLOSE_DELAY <seconds>来设置。参数 <seconds>指定最后一个连接关闭后保持数据库的秒数。下面的语句设置数据库的最后一个连接关闭后,数据库将仍打开10秒,即10秒后数据库才自动关闭:
SET DB_CLOSE_DELAY 10
如果这个值设置为-1,自动关闭将被屏蔽。默认情况下,这个值为0,即数据库最后一个连接关闭后数据库也将被关闭。这个参数的设置是持久性的,并且只能由管理员设置。也可以通过设置数据库URL来设置这些参数:jdbc:h2:~/test;DB_CLOSE_DELAY=10.
虚拟机退出时并不关闭数据库
默认情况下,连接数据库的最后连接关闭时数据库也被关闭了,但是如果最后的连接一直未被关闭,当虚拟机正常退出时,用shutdown hook自动关闭数据库。在某些情况下,数据库还不能被关闭,如虚拟机退出后数据库仍然被使用(如存储退出进程到数据库中)。在这种情况下,可以通过设置数据库URL来屏蔽数据库的自动关闭,打开数据库的第一个连接需要设置屏蔽选项(后面的连接无法改变这个选项),屏蔽 For those cases, the automatic closing of the database can be disabled in the database URL. The first connection (the one that is opening the database) needs to set the option in the database URL (it is not possible to change the setting afterwards). The database URL to disable database closing on exit is:
String url = "jdbc:h2:~/test;DB_CLOSE_ON_EXIT=FALSE";
通常,因为性能的考虑,改变并不记录到索引文件。当索引文件损坏或丢失时打开数据库,索引文件将根据数据重建。当数据库没有被正常关闭时,索引文件可能会出现错误,数据库没有被正常关闭可能是因为断电或者是非正常的程序终止。在一些情况下,如非常大的数据库(超过几百MB),重建索引文件需要花费很长的时间,在这种情况下,较好的方式是将改变记入索引文件,从一个损坏的索引文件进行恢复是快速的。要将改变记入索引文件,需要增加LOG=2到URL中,例如: jdbc:h2:~/test;LOG=2。这个设置需要在连接的时候被指定,使用这个选项时,数据库的更新性能下降。
忽略未知参数设置
一些应用(如OpenOffice.org框架)在连接数据库时会附加一些参数。这些参数被传递的原因不得而知。如参数 PREFERDOSLIKELINEENDS 和 IGNOREDRIVERPRIVILEGES,他们根本无视改善与OpenOffice.org的兼容性。当应用将一些其他参数连接到数据库时,通常数据库会抛出参数不被支持的异常,可以通过在数据库URL上增加;IGNORE_UNKNOWN_SETTINGS=TRUE来忽略这些附加的参数。
打开连接时设置参数
除上面已经描述的外,其他数据库设置也可以通过在数据库中的URL附加;setting=value来设置,上面的设置方式等同于在数据库连接后通过SET语句来设置参数。关于可以设置的参数列表,查看SQL语法。
指定文件读写模式
数据库打开日志、数据和索引文件通常文件访问权限为rw,这意味着有读写权限(除了只读数据库的访问权限为r)。要使用只读模式打开数据库,如果文件不是只读的,可以使用ACCESS_MODE_DATA=r。文件访问权限还支持rws和rwd,被用于日志文件的访问权限可以通过设置ACCESS_MODE_LOG来指定,针对数据和索引文件的访问权限通过设置ACCESS_MODE_DATA来指定,这些设置必须在数据库URL中进行指定:
String url = "jdbc:h2:~/test;ACCESS_MODE_LOG=rws;ACCESS_MODE_DATA=rws";
更多信息请参考持久性问题。在许多操作系统上,访问权限 rws 并不保证数据被写入磁盘。
多连接
同一时间打开多个数据库
一个应用可以同时打开多个数据库,包括多个连接到同一个数据库,打开数据库的数量受限于可用内存。
多个连接到同一个数据库:客户端/服务器
如果要从不同进程,不同计算机访问同一个数据库,你需要使用C/S模式,在这种情况下,一个进程充当服务器,其他进程(可能驻留在另一台计算机上)通过TCP/IP(或者基于TCP/IP上的SSL/TLS)连接到服务器。
多线程支持
数据库是多线程安全的,这意味着,如果一个应用是多线程的,它不需要担心多线程访问数据库的同步问题,在内部,大部分访问同一个数据库的请求都已经被同步了,这意味着一个应用能使用多线程同时访问同一个数据库,然而,如果一个线程在执行一个长时间的查询时,其他线程需要等待。
锁定,超时锁,死锁
数据库使用表级锁给每个连接保持数据一致性,保护两种锁:读锁(共享锁)和写锁(独占锁),当事务被提交或回滚时,所有的锁都被释放。当使用缺省事务隔离级别'read committed'时,读锁在每个语句完成后都已经被释放。
如果一个连接要从一张表中读取数据,这张表上不能有写锁,一个读锁将被加到这张表上,如果这张表上有写锁,这个连接就要等到其他连接释放这个写锁,如果一个连接不能在指定时间内得到一个锁,一个锁的超时异常将被抛出。
通常,SELECT语句将产生读锁,还包括子查询语句。修改数据将使用写锁,也可以不修改数据而独占锁定一张表,使用语句SELECT ... FOR UPDATE。这个语句COMMIT 和 ROLLBACK释放全部已经打开的锁。命令 SAVEPOINT 和 ROLLBACK TO SAVEPOINT 并不影响到锁。当自动提交模式被改变的时候,锁也被全部释放。当锁的自动提交模式是true(缺省值)时,每个语句执行完后,锁都被释放。下面是各语句产生锁的列表:
锁类型 |
SQL语句 |
Read |
SELECT * FROM TEST; |
Write |
SELECT * FROM TEST WHERE 1=0 FOR UPDATE; |
Write |
INSERT INTO TEST VALUES(1, 'Hello'); |
Write |
ALTER TABLE TEST ...; |
锁的超时时间可以设置为每个连接不一样,通过SQL命令SET LOCK_TIMEOUT <milliseconds>来设置。每个连接的锁的初始超时时间(缺省时间)(针对每个新的连接的超时时间)可以通过SQL命令SET DEFAULT_LOCK_TIMEOUT <milliseconds>来设置,这个缺省的超时时间是持久的。
数据库文件说明
有许多针对持久数据库的文件。不像其他数据库,H2并没有单独的表或索引存储文件,而只有下面一些文件被创建:一个数据文件,一个索引文件,一个日志文件和一个锁文件(仅当数据库在被使用时存在),另外,每个大到一定尺寸的大对象(CLOB/BLOB)都会创建一个文件,针对大的结果集的临时文件,如果数据库的跟踪选项被打开,跟踪文件将被创建,下面是数据库创建的文件列表:
文件名 |
说明 |
文件数量 |
test.h2.db |
数据库文件. |
每数据1个 |
test.data.db |
数据文件. |
每数据1个 |
test.index.db |
索引文件. |
每数据1个 |
test.0.log.db |
事务日志文件. |
每数据0个或更多 |
test.lock.db |
数据库锁文件. |
每数据1个 |
test.trace.db |
跟踪文件. |
每数据1个 |
test.lobs.db/1.t15.lob.db |
大对象. |
每个值1个 |
test.123.temp.db |
临时文件. |
每对象1个 |
移动和重命名数据库文件
数据库名字和位置并没有存储在数据库文件中,当数据库被关闭后,文件可以被移动到其他目录去,也可以被改名(只要所有文件具有相同名称的开始)。因为没有指定为特定的平台文件格式,这些文件可以没有问题的移动到其他操作系统上。
备份
当数据库关闭后,就可以备份数据库文件了,注意的是,索引文件不需要备份,因为它包含冗余数据,并且当索引文件不存在时,它们会被自动重建。当数据库处于运行状态时,备份数据库可以通过SQL命令SCRIPT来完成。
日志记录与恢复
当数据库中的数据被修改时,这些改变的提交将被记入磁盘日志文件(除内存对象外),为了提高磁盘读写的性能,数据改变的持久化通常被延迟。如果断电,数据和索引文件没有及时更新,但是因为改变已经记入日志文件,数据库再下次再次打开时,这些数据的改变将会被自动重新提交。
默认情况下,索引文件的改变并没被记入日志,如果数据库被打开并请求恢复,索引文件将从头开始重建。
通常每个数据库只有一个日志文件,文件在数据库成功关闭前一直增长,然后就是被删除,如果文件太大,数据库将切换另一个日志文件(增长序列号),也可能通过命令CHECKPOINT强制切换日志文件。
如果数据库文件破坏,因为记录的校验和不匹配(如这个文件被其他应用编辑过),数据库可以使用恢复模式打开,在这种情况下,数据库的错误只会被记录不会被抛出,数据库这个时候可以备份成脚本文件,并能尽快重建。使用恢复模式打开数据库可以在数据库URL中包含;RECOVER=1,如: jdbc:h2:~/test;RECOVER=1。在这种情况下,索引将被重建,摘要不能被读(对象分别表),于是数据库打开需要花费更长的时间。
兼容性
所有数据库引擎的行为都有一些不同,H2支持ANSI SQL标准,尽可能的与其他数据兼容,但是仍然有些不同:
在MySQL中,字段名默认是不区分大小写的,而H2默认是区分大小写的,H2也可以通过设置参数来支持字段名不区分大小写,要创建的表的字段名对大小写不敏感,需要在数据库URL中增加 IGNORECASE=TRUE (如: jdbc:h2:~/test;IGNORECASE=TRUE).
兼容性模式
H2数据库可以模拟特定数据库的某些功能,但不是所有的属性和差异都能被提供,下面是当前支持的兼容模式及与普通数据库的差异:
DB2 兼容模式
使用IBM的DB2的兼容模式,可以通过设定数据库的URL为jdbc:h2:~/test;MODE=DB2,或是通过SQL语句SET MODE DB2.
· 别名字段, ResultSetMetaData.getColumnName() 返回别名,getTableName() 返回 null。
· 支持语法[OFFSET .. ROW] [FETCH ... ONLY] 作为替代 LIMIT .. OFFSET的语法。
· 关联NULL值与其他值的结果。
Derby 兼容模式
使用Apache的Derby的兼容模式,可以通过设定数据库的URL为 jdbc:h2:~/test;MODE=Derby,或是通过SQL语句SET MODE Derby.
· 别名字段,ResultSetMetaData.getColumnName()返回别名,getTableName() 返回 null。
· 唯一索引,NULL值是不相同的,这要求每列只有一行能为 NULL 值。
· 关联NULL值与其他值的结果。
HSQLDB 兼容模式
使用HSQLDB的兼容模式,可以通过设定数据库的 URL为 jdbc:h2:~/test;MODE=HSQLDB,或是通过SQL语句 SET MODE HSQLDB.
· 别名字段,ResultSetMetaData.getColumnName() 返回别名,getTableName() 返回 null.
· 在做十进制数据的精度转换时,只能在新精度比当前精度更小是才能转换,通常,精度转换后0会被增加。
· 唯一索引,NULL值是不相同的,这要求每列只有一行能为 NULL 值。
MS SQL Server 兼容模式
使用MS SQL Server的兼容模式,可以通过设定数据库的 URL 为 jdbc:h2:~/test;MODE=MSSQLServer,或是通过SQL语句 SET MODE MSSQLServer.
· 别名字段,ResultSetMetaData.getColumnName() 返回别名,getTableName() 返回 null。
· 标识符可以通过使用方括号引用,如[Test]。
· 唯一索引,NULL值是不相同的,这要求每列只有一行能为 NULL 值。
· 关联NULL值与其他值的结果。
MySQL 兼容模式
使用MySQL的兼容模式,可以通过设定数据库的 URL 为 jdbc:h2:~/test;MODE=MySQL,或是通过SQL语句 SET MODE MySQL.
· 在插入数据时,如果定义一个列为 NOT NULL,在NULL值插入时,0(或者是空串,或是是当前时间戳)被替代插入,通常,这样是不被允许的,会抛出异常。
· 在CREATE TABLE语句中允许创建索引。
· 元数据查询返回的是小写标识符。
· 转换浮点数为整数时,小数不是被截断,而是四舍五入。
· 关联NULL值与其他值的结果。
Oracle 兼容模式
使用Oracle的兼容模式,可以通过设定数据库的 URL 为 jdbc:h2:~/test;MODE=Oracle ,或是通过SQL语句 SET MODE Oracle.
· 别名字段,ResultSetMetaData.getColumnName() 返回别名,getTableName() 返回 null.
· 唯一索引,在所有列上多行为空是被允许的,但是它不允许多行有相同的值。
· 关联NULL值与其他值的结果。
PostgreSQL 兼容模式
使用PostgreSQL 的兼容模式,可以通过设定数据库的 URL 为 jdbc:h2:~/test;MODE=PostgreSQL ,或是通过SQL语句 SET MODE PostgreSQL.
· 别名字段,ResultSetMetaData.getColumnName() 返回别名,getTableName() 返回 null.
· 转换浮点数为整数时,小数不是被截断,而是四舍五入.
自动重连
自动重连特征能让JDBC驱动在连接丢失后重新连接到数据库,自动重连仅发生在自动提交被允许的情况下,如果自动提交被屏蔽,一个异常将会抛出。
自动重连将打开一个新的session,在自动重连后,自定义变量,本地临时表(不包括数据)都将被重建,系统表INFORMATION_SCHEMA.SESSION_STATE包含所有客户端被重建的状态。
自动切换到混杂模式
多个进程能够访问事先并没有手动启动的为服务器模式的数据库,通过在数据库URL中增加 ;AUTO_SERVER=TRUE就可以做到,无论数据库是否被打开,使用相同的数据库URL都可以访问。
当使用这种方式,第一个连接到数据库的连接可以是内嵌模式,连接后数据库服务也在内部启动,如果数据库已经在另一个进程内打开,服务器模式将被自动打开。
第一个连接使用内嵌模式,速度快于服务器模式,因此,主要应用程序应该尽可能首先使用内嵌模式打开数据库。第一个连接自动在一个随机端口上启动服务器,此服务器允许远程连接,然而仅能连接到这个数据库(以确保在客户端能读取.lock.db文件和发送存储在服务器上的随机密钥),当第一个连接关闭时,服务器也停止,如果另有(远程的)连接仍然打开着,他们中的一个将自动启动一个服务(自动重连被自动打开)。
所有的进程都必须能访问数据库文件,如果第一个连接被关闭(启动服务的连接),其他连接打开的事务将全部被回滚。客户端/服务器(使用 jdbc:h2:tcp:// 或 ssl://)的连接不被支持,这种模式也不支持内存数据库。
下面是自动切换到混杂模式的例子,Application 1 和 2 不必启动在同一台计算机,但是他们需要能访问数据库文件,Application 1 和 2可以是典型的两个不同进程(他们也可以运行在同一个进程)。
// Application 1:
DriverManager.getConnection("jdbc:h2:/data/test;AUTO_SERVER=TRUE");
// Application 2:
DriverManager.getConnection("jdbc:h2:/data/test;AUTO_SERVER=TRUE");
为了找到应用的问题,有时需要去跟踪数据库操作的情况,数据库提供了下面跟踪的选项:
· 跟踪 System.out 的信息,将信息导出到文件
· 支持跟踪级别: OFF, ERROR, INFO, DEBUG
· 最大跟踪文件尺寸可以设置
· 可以根据跟踪文件产生JAVA源代码
· 可以在运行的过程中通过创建文件启动跟踪
跟踪选项
最简单的设置跟踪选项的方式是通过设置数据库URL,有两个选项,一个是针对 System.out (TRACE_LEVEL_SYSTEM_OUT)的跟踪级别,一个是针对文件跟踪的级别 (TRACE_LEVEL_FILE),级别可分为:0是OFF,1是ERROR(缺省值),2是INFO ,3是DEBUG ,数据库URL可以设置这两个级别:
jdbc:h2:~/test;TRACE_LEVEL_FILE=3;TRACE_LEVEL_SYSTEM_OUT=3
The trace level can be changed at runtime by executing the SQL command SET TRACE_LEVEL_SYSTEM_OUT level (for System.out tracing) or SET TRACE_LEVEL_FILE level (for file tracing). Example:
SET TRACE_LEVEL_SYSTEM_OUT 3
设置跟踪文件的最大尺寸
当使用较高的跟踪级别时,跟踪文件增长的非常快,跟踪文件的默认最大尺寸是16MB,如果跟踪文件超过了这个限制,它将被重命名为.old文件,一个新的文件将被创建,如果另一个文件已经存在,它将被删除,可以通过修改TRACE_MAX_FILE_SIZE 来设置新的文件大小尺寸,这个值的大小是MB,可以通过命令SET TRACE_MAX_FILE_SIZE mb来设置参数,如:
SET TRACE_MAX_FILE_SIZE 1
生产JAVA代码
当跟踪级别被设置为 INFO 和 DEBUG时,JAVA代码被自动生成出来,主要是为简化问题的查找,这个跟踪文件看上去像:
...
12-20 20:58:09 jdbc[0]:
/**/dbMeta3.getURL();
12-20 20:58:09 jdbc[0]:
/**/dbMeta3.getTables(null, "", null, new String[]{"TABLE", "VIEW"});
...
可以使用ConvertTraceFile 来过滤JAVA源代码,命令使用如下:
java -cp h2*.jar org.h2.tools.ConvertTraceFile
-traceFile "~/test.trace.db" -javaClass "Test"
生产的文件 Test.java 包含JAVA代码,有可能产生的代码太大(JAVA的方法对代码的尺寸有限制),这需要源代码被多种方法分割,密码不在跟踪文件产生的源代码文件中。
缺省情况下,数据库使用自己的'trace‘来跟踪,使用'trace'而不是用'log’是为了避开与事务日志出现混淆,跟踪信息可以被写到文件和通过 System.out输出,在大部分情况下,这些是满足要求的,但是,有时需要使用和应用一致的日志方式,如Log4j,为此,数据库支持 SLF4J。
SLF4J是针对各类日志API提供了一个简单的接口规范,SLF4J支持像 Logback, Log4j, Jakarta Commons Logging (JCL), Java logging, x4juli, 和 Simple Log等众多日志.
为了使SLF4J可用,在数据库URL中设置文件跟踪级别为4.
jdbc:h2:~/test;TRACE_LEVEL_FILE=4
在数据库已经打开后,不能改变数据库的日志记录设施,这意味着在数据库打开后执行SQL语句 SET TRACE_LEVEL_FILE 4 没有效果。使用 SLF4J,需要将jar文件增加到classpath中,如果日志不能工作,请检查 <database>.trace.db 文件中的错误消息。
只读数据库
如果数据库文件是只读的,这个数据库也是只读的,只读数据库是不能创建新表,不能修改数据,仅能使用SELECT 和 CALL语句。创建只读数据库,关闭数据库后日志文件变得更小,并不需要删除日志文件,数据库文件的只读依赖操作系统,有两种方式判断打开的数据库是否为只读数据库:通过调用 Connection.isReadOnly() 和执行SQL语句 CALL READONLY()来判断。
Zip或Jar文件只读数据库
为了创建一个zip文件里的只读数据库,首先创建一个正常的持久数据库,然后创建数据库的一个备份,如果你使用的数据库名 test,通过备份工具或 SQL语句BACKUP备份数据:
BACKUP TO 'data.zip'
压缩文件的数据库不支持修改,这需要关闭数据库的全部连接,打开一个单独的连接,然后执行语句,此后,可以退出,直接通过ZIP文件打开数据库,使用下面的URL:
jdbc:h2:zip:~/data.zip!/test
在ZIP文件中的数据库是只读的,相比正常文件的数据库,ZIP文件中的数据库的查询性能更慢,因为随机存储访问,在ZIP文件中是不允许的(仅支持流模式的顺序访问),性能影响的深度取决于查询和数据,数据库不能读到内存中,但是大数据库被很好的支持,索引跟正常数据库一样被使用。
智能磁盘监控
数据库需要更多的磁盘空间时,如果数据库监听被安装,它将被启动。应用可以删除临时文件,或者是显示消息等待用户解决问题。安装监听,运行SQL语句 SET DATABASE_EVENT_LISTENER 或是用一个特别的数据库URL jdbc:h2:~/test;DATABASE_EVENT_LISTENER='com.acme.DbListener' (类名需要使用引号). 参见 DatabaseEventListener API.
打开一个损害的数据库
当数据库的启动信息(SQL启动脚本)损坏后,数据库就无法启动,但是数据库可以通过指定一个数据库事件监听器打开数据库,异常将被时间监听器记录,但是数据库打开将被继续。
包含计算列方法列的索引
方法索引并没有被直接支持,但是可以通过计算列来模拟,如要创建一个索引针对列的大写字符串,可以先增加一个计算列,这个计算列的值就是那个列的大写,再创建索引针对新增加的列:
CREATE TABLE ADDRESS(
ID INT PRIMARY KEY,
NAME VARCHAR,
UPPER_NAME VARCHAR AS UPPER(NAME)
);
CREATE INDEX IDX_U_NAME ON ADDRESS(UPPER_NAME);
当插入数据时,这个新增的计算列并不要求填入数据(也不允许),因为这个列是自动填充的,但是在查询中,你可以使用这个列。
INSERT INTO ADDRESS(ID, NAME) VALUES(1, 'Miller');
SELECT * FROM ADDRESS WHERE UPPER_NAME='MILLER';
H2提供了高性能的多维(空间)查询工具,数据库并没有提供专门的空间索引(R树或类似的),而是使用了B树,每条记录的多维键值都被转换(或映射)为一维键值,这个值是一个空间填充算法的值。
目前,Z-order (也被称为N-order 或是 Morton-order) 被使用, Hilbert 曲线也能使用,但是实施起来更复杂。多维转换为一维的算法被称为bit-interleaving。转换后的值通过B树建立索引(通常是用计算列)。
使用这样的方法把索引建立在第一个列上,可以大幅的提升性能,具体提升的幅度依赖数据和维数,通常比factor 5更高。数据库提供了通过特定的多维查询生产SQL查询的工具,这个工具并不依赖具体的数据库,很容易把它移植到其他数据库,关于怎么样使用这个工具,请查看列子代码 TestMultiDimension.java.
使用密码
使用安全密码
记住再好的加密方法和加密协议都不能保护弱密码,所以不要使用字典能找到的字符串作为密码,即使在这些字符串后追加数字也不能保障安全性,好的办法是使用一句话的单词首字母,并且能使用大小写和创造性的加入特殊字符,如:
i'sE2rtPiUKtT 就是从句子 it's easy to remember this password if you know the trick 抽取出的密码。
密码:使用字符数组取代字符串
JAVA的字符串是不可修改的,并且不能由应用安全的销毁掉,在一个字符串创建后,在垃圾收集器收集它之前,它将一直存在于主内存,但是垃圾收集器并不能被应用所控制,也就是说当字符串应该被垃圾收集器收集时也可能它还是存在于内存中,也有可能存储密码的主内存被交换到磁盘上(因为没有足够可用的主内存)。
攻击者可以访问操作系统的交换文件,这样就可能看到用户的密码,建议使用字符数组存放密码,字符数组可以在使用完后被干净的清除掉(用0填充),字符数组存放的密码也不会被存储到交换文件中。
用户密码和文件密码都支持使用字符数组,下面是列子:
import java.sql.*;
import java.util.*;
public class Test {
public static void main(String[] args) throws Exception {
Class.forName("org.h2.Driver");
String url = "jdbc:h2:~/test";
Properties prop = new Properties();
prop.setProperty("user", "sa");
System.out.print("Password?");
char[] password = System.console().readPassword();
prop.put("password", password);
Connection conn = null;
try {
conn = DriverManager.getConnection(url, prop);
} finally {
Arrays.fill(password, (char) 0);
}
conn.close();
}
}
这个例子要求使用 Java 1.6。使用Swing的地方,请使用 javax.swing.JPasswordField。
在URL中传递用户名和密码
可以通过分割参数传递用户名和密码,如 Connection conn = DriverManager. getConnection("jdbc:h2:~/test", "sa", "123"),也可以通过URL传递用户名和密码,如 Connection conn = DriverManager. getConnection("jdbc:h2:~/test;USER=sa;PASSWORD=123"),URL上的用户名和密码参数将覆盖分割传递的参数。
用户自定义方法和存储过程
除了内嵌的方法外,数据库还支持用户自定义的JAVA方法,在数据库内,JAVA方法也能当作存储过程使用,但在使用之前,这个方法必须要先被申明(或注册),方法可以通过源代码来定义,也可以通过引用一个已经编译的类,但是这个类必须要加到classpath中。
引用一个已编译的方法
引用一个方法,要求这个类必须已经被编译,并且包含在运行数据库的classpath 中,只有静态的public 的JAVA方法能被引用,如:
package acme;
import java.math.*;
public class Function {
public static boolean isPrime(int value) {
return new BigInteger(String.valueOf(value)).isProbablePrime(100);
}
}
被引用的方法要通过调用CREATE ALIAS ... FOR 进行注册:
CREATE ALIAS IS_PRIME FOR "acme.Function.isPrime";
一个简单的实例,请参看 src/test/org/h2/samples/Function.java.
使用源代码申明一个方法
当使用源代码申明一个方法时,数据库将使用SUN的JAVA编译器(类com.sun.tools.javac.Main)进行编译,如果包tools.jar不在classpath 中,javac将作为一个独立的进程启动来进行编译。当源代码被存储在数据库中时,数据库在被重新打开后将被重新编译。源代码通过美元符号来表示结束,后面再使用分号是一个好习惯,如:
CREATE ALIAS NEXT_PRIME AS $$
String nextPrime(String value) {
return new BigInteger(value).nextProbablePrime().toString();
}
$$;
方法名(上面的例子是nextPrime)将被忽略。缺省情况下 java.util, java.math, java.sql 3个包将被导入,其他包需要自己导入,它必须声明在开始和标签@CODE之间,如:
CREATE ALIAS IP_ADDRESS AS $$
import java.net.*;
@CODE
String ipAddress(String host) throws Exception {
return InetAddress.getByName(host).getHostAddress();
}
$$;
下面的模板被用于创建一个已经编译好的JAVA类:
package org.h2.dynamic;
< import statements before the tag @CODE; if not set:
import java.util.*;
import java.math.*;
import java.sql.*;
>
public class <aliasName> {
public static <sourceCode>
}
方法数据类型映射
接受非NULL参数的方法,如果有一个参数为NULL,这个方法将被能被调用,如果这个方法被调用,将返回NULL值,如果方法中有一个参数是NULL,你需要使用java.lang.Integer代替。
SQL类型和JAVA类型可以互相映射,映射规则和JDBC API的相同,具体的细节,请参看数据类型。有两种特殊的类型,一种是 java.lang.Object,它被映射为 OTHER (一种序列化对象),但是 java.lang.Object 并不能匹配所有的SQL类型(没有匹配所有SQL类型的类型被支持);另一种类型是 Object[],任何类的数组被映射为 ARRAY。
需要数据库连接的方法
如果JAVA方法的第一个参数为java.sql.Connection,数据库连接将被赋予,在方法返回值前,这个连接并不需要关闭。在SQL语句中调用方法,连接参数并不需要(也不可能)被提供。
抛出异常的方法
如果一个方法抛出异常,当前的SQL语句将被回滚,异常将抛给应用。
返回一个结果集的方法
方法可以返回一个结果集,如下面的方法:
public static ResultSet query(Connection conn, String sql) throws SQLException {
return conn.createStatement().executeQuery(sql);
}
CREATE ALIAS QUERY FOR "org.h2.samples.Function.query";
CALL QUERY('SELECT * FROM TEST');
使用SimpleResultSet
方法可以通过SimpleResultSet工具创建结果集:
import org.h2.tools.SimpleResultSet;
...
public static ResultSet simpleResultSet() throws SQLException {
SimpleResultSet rs = new SimpleResultSet();
rs.addColumn("ID", Types.INTEGER, 10, 0);
rs.addColumn("NAME", Types.VARCHAR, 255, 0);
rs.addRow(0, "Hello");
rs.addRow(1, "World");
return rs;
}
CREATE ALIAS SIMPLE FOR "org.h2.samples.Function.simpleResultSet";
CALL SIMPLE();
把方法作为表使用
返回结果集的方法可以像表一样使用,在下面的例子中,方法至少被调用两次,第一次是生成字段名(在编译时如果不知道将被设置为NULL),然后,执行语句去获得数据(如果在一个联合查询中可以会多次),如果这个方法被调用去获取字段列,传递到这个方法中的连接的URL是jdbc:columnlist:connection,否则,连接URL是 jdbc:default:connection。
public static ResultSet getMatrix(Connection conn, Integer size)
throws SQLException {
SimpleResultSet rs = new SimpleResultSet();
rs.addColumn("X", Types.INTEGER, 10, 0);
rs.addColumn("Y", Types.INTEGER, 10, 0);
String url = conn.getMetaData().getURL();
if (url.equals("jdbc:columnlist:connection")) {
return rs;
}
for (int s = size.intValue(), x = 0; x < s; x++) {
for (int y = 0; y < s; y++) {
rs.addRow(x, y);
}
}
return rs;
}
CREATE ALIAS MATRIX FOR "org.h2.samples.Function.getMatrix";
SELECT * FROM MATRIX(4) ORDER BY X, Y;
数据库支持在行被修改、插入和删除之前及之后的JAVA触发器调用,触发器可以用于复杂的一致性检查,或更新数据库中的相关数据,也可以通过触发器来模拟实体视图,这里有一个完整的例子,参见 src/test/org/h2/samples/TriggerSample.java。JAVA触发器必须实现接口org.h2.api.Trigger。触发器的类也必须被提交到classpath 中,以使数据库引擎能使用(当使用服务器模式时,它必须在服务器的classpath中)。
import org.h2.api.Trigger;
...
public class TriggerSample implements Trigger {
public void init(Connection conn, String schemaName, String triggerName,
String tableName, boolean before, int type) {
public void fire(Connection conn,
Object[] oldRow, Object[] newRow)
throws SQLException {
}
}
触发器中的连接可以被用于查询和更新表中的数据,触发器在使用前需要在数据库中定义:
CREATE TRIGGER INV_INS AFTER INSERT ON INVOICE
FOR EACH ROW CALL "org.h2.samples.TriggerSample"
触发器可以通过抛出异常SQLException 否决数据的改变。
压缩数据库
数据库文件中的未使用空间将自动被重用,重建索引最简单的方法是在数据库关闭后删除 .index.db文件。在某些情况下(如在删除了大量数据时),有时需要压缩数据库的尺寸(紧凑数据库),下面有一段代码可以实现:
public static void compact(String dir, String dbName,
String user, String password) throws Exception {
String url = "jdbc:h2:" + dir + "/" + dbName;
String file = "data/test.sql";
Script.execute(url, user, password, file);
DeleteDbFiles.execute(dir, dbName, true);
RunScript.execute(url, user, password, file, null, false);
}
也可以参见示例应用程序 org.h2.samples.Compact。命令 SCRIPT / RUNSCRIPT 也能通过创建数据库备份,然后重建数据库达到同样的效果。
缓存设置
数据库保留经常使用的数据和索引页在主内存中,用于缓存这些数据的主内存的数量可以通过设置 CACHE_SIZE 来改变。可以通过设置数据库的连接URL(如jdbc:h2:~/test;CACHE_SIZE=131072)来设置这个值,也可以在运行状态下通过命令SET CACHE_SIZE size 来进行修改。
二级引用缓存也被支持,在这个缓存中的行只能被垃圾收集在内存将耗尽时去收集,默认情况下,二级缓存是禁止的,要去开启它,需要使用前缀SOFT_的设置,如: jdbc:h2:~/test;CACHE_TYPE=SOFT_LRU.
关于更多的读写页的信息,以及查询当前使用的二级缓存的算法,可以通过 SELECT * FROM INFORMATION_SCHEMA.SETTINGS 获得,将列出读写 The number of pages read / written is listed for the data and index file.