测试持久层(Dao 层)的难点在于:
单元测试必须执行隔离的代码;而持久层的代码需要和数据库进行交互。
单元测试必须快速运行;而访问数据库却相对较慢。
以上两个难点决定了嵌入式数据库(H2、HSQLDB、Derby 和 Java DB)的使用价值。嵌入式数据库使用场景较少,但是是配合 JUnit 测试持久层的最佳选择。
1. 基本使用
在 pom.xml 中添加 h2 数据库的依赖。如果是在非 Maven 项目中使用,则下载该 h2 的 .jar 包并加入项目的 classpath 中。
com.h2database
h2
1.4.200
test
嵌入式数据库 H2 有多种使用模式,也可以将数据写入到磁盘上的文件中,但是大家更关注它的『内存模式』。即,将 database 和 table 建立在内存中。
private static final String DRIVER = "org.h2.Driver";
private static final String URL = "jdbc:h2:mem:scott;MODE=MYSQL;DB_CLOSE_DELAY=-1";
private static final String USERNAME = "sa";
private static final String PASSWORD = "";
jdbc:h2:mem:testdb
这是数据库 URL 的核心部分,其中 mem 就表示使用内存模式的 H2。H2 的各种不同的使用/运行模式,主要就体现在这个部分。
数据库名(以及后续的用户名和密码)并非重要部分,因为内存模式的数据库,在使用结束后会被清除,而且对它的使用无所谓用户名密码。
MODE=MYSQL
H2 并不是唯一的嵌入式数据库,也不是唯一具有内存模式的嵌入式数据库,但是它是与 MySQL 语法最兼容的具有内存模式的嵌入式数据库(虽然仍有些许特殊区别),这也是 JUnit中 首选 H2 的原因。
DB_CLOSE_DELAY=-1
默认情况下,H2 内存中的数据库是在最后一个(或指定个数)连接断开后关闭,这时就会删除数据库及其中数据。
设置为 -1 表示并非以连接数作为判断标准,而是持续保持数据库(即便没有连接),直到程序运行结束。
Class.forName(DRIVER);
Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
Statement stmt = conn.createStatement();
stmt.execute( "...");
除了正常的使用获得数据库 Connection 对象之外,H2 自带了连接池功能。
JdbcConnectionPool cp = JdbcConnectionPool.create(URL, USERNAME, PASSWORD);
Connection conn = cp.getConnection();
2. 初始化数据库
虽然可以在 JUnit 的 Before 方法中通过执行 SQL 语句的方式在 @Test 之前去初始化数据库环境,但是 H2 有一个更好的特性能实现数据库的初始化操作:Execute SQL on connection 。
H2 支持在连接上数据库的时候就执行 SQL 语句,相当于就初始化了数据库环境:
jdbc:h2:mem:;...;INIT=RUNSCRIPT FROM '~/create.sql'
spring.datasource.url=jdbc:h2:mem:scott;MODE=MYSQL;DB_CLOSE_DELAY=-1;INIT=RUNSCRIPT FROM './src/test/resources/create.sql'
H2 internally uses Unicode, and supports all character encoding systems and character sets supported by the virtual machine you use.
可以为测试类编写父类,并实现 @Before 方法,以方便/确保于在每个 @Test 方法前执行统一的初始化数据库(初始化测试环境)的代码。
public class DaoTestBase {
private static final String DRIVER = "org.h2.Driver";
private static final String URL = "jdbc:h2:mem:testdb;MODE=MYSQL;DB_CLOSE_DELAY=-1";
private static final String USERNAME = "sa";
private static final String PASSWORD = "";
@Before
public void setUp() throws URISyntaxException, ClassNotFoundException, SQLException {
String sqlPathName = getClass().getResource("/conf/sql/xxx.sql" ).toURI().toString().substring(6);
Class.forName(DRIVER);
Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
Statement stmt = conn.createStatement();
stmt.execute( "runscript from '" + sqlPathName + "'");
stmt.close();
conn.close();
}
}
三、H2 和 MySQL 的语法兼容性
所有的数据库都会有些小区别,即便是与 MySQL『最像』的数据库,H2 与 MySQL 仍有一些小区别:
不支持表级别的 Comment(注释)
插不支持入语句的出现'
H2 UNIQUE KEY是数据库级别的,而非表级别
无法执行多个 Update 语句,即一次 update 只能插入一条数据。
列别名无法用于子查询
不支持@:语法